1. 程式人生 > >Redis-C客戶端-HiRedis-(二)

Redis-C客戶端-HiRedis-(二)

前幾篇介紹了redis以及phpredis,主要是因為我所在的專案組用的是php,而我接下來的一個小任務是用c++寫一個處理儲存在redis裡的業務資料的小工具,在redis官方上看推薦的c++客戶端,只有一個,而且還是2年前的一個臨時專案,而且還要依賴boost,而且看開發者的口氣,實在是覺得不敢用啊~

所以打算使用官方推薦的c客戶端:hiredis,它可是官方的哦~而且看git的提交日期,近期也有提交,頓時覺得安心啊!

下載最新版的hiredis後,解壓並make便得到了libhiredis.so和libredis.a,非常簡單~把標頭檔案也移動到自己系統的include資料夾中就可以在專案中方便的使用了。

hiredis並沒有像phpredis那樣提供了各種各樣針對redis自身結構的封裝函式,而是提供了基礎的幾個函式,例如用於連線redis,向redis提交命令,獲取返回等,把底層協議和通訊細節封裝起來,包括錯誤甄別和資料結構的轉換。總的來說,hiredis還是非常好用的。

hiredis提供的api又分為兩大塊:同步,非同步。由於我能力有限,專案也沒有非同步要求,所以非同步介面部分就不研究了,直接開啟解壓出來的example.c檔案,你就可以看到同步api是怎麼使用的了。剩下的就是redis本身的命令格式和返回值了,可以在這裡查閱。

這裡我主要備註一下官方提出的一些注意點,方便大家規避問題:

1.一旦redisCommand()發生錯誤,會返回NULL,也會導致redisContext失效,使其不能再用於執行其他操作。
2.必須呼叫freeReplyObject()來釋放reply物件,不然會造成記憶體洩露。
3.redisCommand()返回的是void *型別,需要手動進行型別轉換(reply = (redisReply *)redisCommand(…))

hiredis還提供了pipeline的支援,用於提高效能。關於redis的pipeline和事務的一些內容我在這裡有提到過,下面貼一下官方提供的用於pipeline的hiredis例子:

redisReply *
reply;
redisAppendCommand(context,"SET foo bar");
redisAppendCommand(context,"GET foo");
redisGetReply(context,&reply); // reply for SET
freeReplyObject(reply);
redisGetReply(context,&reply); // reply for GET
freeReplyObject(reply);

沒啥好說的吧,挺直觀的。
可以看一下官方解釋的一個內部機制,可能更深刻的理解這些函式的工作方式。

亂譯:

……
當任何redisCommand()被呼叫時,hredis首先根據redis協議格式化命令引數,然後把格式化後的命令存放在上下文(redisContext)的輸出緩衝中,這個輸出緩衝允許動態擴容,所以它可以存放任意數量的命令而不用擔心會溢位。

接下來呼叫redisGetReply(),這個函式做下面兩個任務:
1.如果輸入緩衝不為空,則嘗試從輸入緩衝中讀取解析單個回覆,並返回它;若為空,則執行第2步。
2.若輸入緩衝為空,則把整個輸出緩衝寫入套接字,並阻塞等待套接字,直到讀取並解析到一個回覆,並返回它。
由此可見,redisGetReply()函式既輸出命令到socket,又從socket讀入回覆並返回給呼叫者。
……

接下來輪到介紹hiredis執行完命令後的返回結構了,也就是redisReply的結構:

typedef struct redisReply {
    int type; /* REDIS_REPLY_* */
    long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
    int len; /* Length of string */
    char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */
    size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
    struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
} redisReply;

其中,type包含下面幾個值:
REDIS_REPLY_STATUS,REDIS_REPLY_ERROR,REDIS_REPLY_INTEGER,REDIS_REPLY_NIL,REDIS_REPLY_STRING,
REDIS_REPLY_STRING,REDIS_REPLY_ARRAY。

這裡並不打算逐一介紹所有的這些型別,好奇心童鞋可以點選提供的連結去github上一探究竟。只介紹一下這個:REDIS_REPLY_INTEGER。

說它之前,不得不先讓大家看一下官方的相關解釋,注意看其中“Replies”一節中的“Integer reply”段:

This type of reply is just a CRLF terminated string representing an integer, prefixed by a ":" byte. For example ":0\r\n", or ":1000\r\n" are integer replies.

亂譯:Integer Reply這種型別其實是用以:開頭,以CRLF結尾的字串來表示的

在經過hiredis的Reply Paser解析處理後,就會賦值到redisReply物件中的integer屬性,並把type屬性設定為3(REDIS_REPLY_INTEGER),大體就是這麼個意思吧。

這裡我遇到了一個問題,很奇怪,看程式碼:

...
//定義一個kv結構,v為long long int型別。
reply = (redisReply *)redisCommand(context, "SET myCounter %lld", (long long int)7);
freeReplyObject(reply);

reply = (redisReply *)redisCommand(context, "GET myCounter");
//以為是REDIS_REPLY_INTEGER,但其實是REDIS_REPLY_STRING
std::cout << "type: " << reply->type << std::endl;
freeReplyObject(reply);
...

什麼情況呢?看了一個部落格,才明白原因:

即使value值的型別是integer(至少看上去是,實際server也確實是這麼存的),但使用GET返回的值的型別(reply->type)仍是REDIS_REPLY_STRING,需要自己程式裡轉成long long。

阿西吧,原來沒理解官方說明中的最後一段文字:

The following commands will reply with an integer reply: SETNX, DEL, EXISTS, INCR, INCRBY, DECR, DECRBY, DBSIZE, LASTSAVE, RENAMENX, MOVE, LLEN, SADD, SREM, SISMEMBER, SCARD.

原來只有上述列出的命令才會返回Integer Reply。而我例子中的GET命令不再上訴範圍,它返回的是string,還要自己型別轉換!

目前我也就理解了這麼多,以後有再補充吧,就到這裡,8~