hiredis中非同步的實現小結
阿新 • • 發佈:2019-02-15
前言
一般情況下我們使用的都是hiredis的同步通訊機制,這種機制下每當你向伺服器傳送命令請求,程式都會阻塞直到收到伺服器的回覆並處理。而如果採用非同步通訊,程式就不需要阻塞等待伺服器的回覆,而是直接繼續執行後邊的程式碼,當伺服器回覆到來後由程式中預先註冊的回撥函式來處理回覆。
同步通訊下程式寫起來邏輯更清晰,程式碼量也少,但是由於每次請求都要停下來等待回覆,可能會影響程式的執行速度。非同步通訊下程式邏輯會變得很複雜,你必須考慮回撥函式的編寫,並且需要多開一個執行緒來實現非同步事件的處理,但是非同步通訊下程式在傳送完redis命令請求後不需要等待回覆,可以繼續做其他事,程式速度的提升自然不言而喻。非同步通訊比較適合程式對速度要求比較高的情況下。
hiredis中的非同步api
hiredis中有一套非同步api可供我們使用。要使用hiredis中的非同步api你必須先了解hiredis中的非同步實現。hiredis的非同步主要是通過libevent等非同步事件觸發庫來實現的。hiredis可以通過一下事件觸發庫:libae(redis自帶的非同步事件觸發庫)、libev、libuv、libevent中的一個實現。在我的實際使用中,libae庫出現了標頭檔案問題,libev出現了非同步訊息無法接受的問題,libuv沒有安裝成功,所有我最終選擇了libevent庫,而這個庫的表現也非常穩定。
要使用redis客戶端的非同步通訊,單靠hiredis自帶的那幾個api是不夠的,還需要事件觸發庫的支援 。這裡要黑一下hiredis的github主頁,上邊的非同步api說明中沒有講解hiredis非同步api所需的那些事件觸發庫,讓我想當然的以為單單依靠hiredis的自帶api就可以實現非同步,結果浪費了大量時間去除錯錯誤的程式,希望大家引以為戒。下邊就以libevent為例講一下hiredis非同步api用到的事件觸發庫。
事件觸發庫libevent
libevent是一個成熟事件觸發庫,分散式快取軟體memcached就使用了這個庫。libevent可以實現對多種事件的觸發管理。詳細的說,你可以通過libevent去對各種IO事件進行觸發註冊,之後如果該IO事件發生,libevent就會直接呼叫你之前為IO事件註冊的函式來處理這個事件。除了IO事件外,libevent還可以管理定時器事件、訊號事件,功能非常的強大。
下邊簡單講一下libevent的使用。libevent本身的使用是比較複雜的,考慮到我們的重點是hiredis,所以這裡只講hiredis中要用到的。libevent首先要設定並新增你要監聽的非同步事件,這一步hiredis已經為你做好了,只需要兩步:
base = event_base_new();//建立一個新的libevent事件處理例項。
redisLibeventAttach(ac, base);//對hiredis進行libevent事件註冊,這裡的base就是上一步建立的事件處理例項
event_base_dispatch(base);//開始libevent事件處理迴圈。執行這個函式後libevent才真正開始監聽並處理事件。在實際的hiredis使用中這個函式通常要單獨開一個執行緒去執行,因為這個函式執行後就會陷入死迴圈。
hiredis用到的libevent函式就這麼幾個,是不是覺得很簡單!
hiredis非同步APi的使用
下邊才是重點,如何使用hiredis的非同步API來實現我們要做的redi非同步通訊。
首先要建立連線:
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
這裡的建立連線跟同步下區別不大。但是需要注意的是非同步的連線函式會立刻返回,不論你的程式是否真的連上了redis伺服器。是否成功連線只能在連接回調函式中確定。所以不要指望依靠這個函式去檢查你的連線是否成功建立。
可以通過這個函式註冊連接回調函式:
redisAsyncSetConnectCallback(c, ccdbRedisAsync::connectCallback);
回撥函式需要是下邊的格式:
void ccdbRedisAsync::connectCallback(const redisAsyncContext *c, int status)
其中引數status會告訴你連線是否成功。
以下函式實現斷開連線以及相應的回撥函式:
redisAsyncDisconnect(c);//斷開連線
redisAsyncSetDisconnectCallback(c, ccdbRedisAsync::disconnectCallback);//回撥函式的格式和使用同連接回調函式
如果你想實現redis的斷線重連,那麼就可以考慮在上邊的回撥函式中實現。
注意建立連線後還要進行之前的libevent事件註冊過程。
連線建立好後解可以傳送命令了。非同步命令的傳送方式和同步很像,區別在於非同步傳送函式執行後只能得到該命令是否成功過加入傳送佇列的返回,而無法確定這個命令是否傳送成功以及命令的返回。
int redisAsyncCommand(
redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);//引數fn是回撥函式的地址,privdate可以用來儲存 任意的使用者指標,這個指標可以在回撥函式呼叫的時候得到
一般你需要設定一個回撥函式來處理命令的返回:
void(redisAsyncContext *c, void *reply, void *privdata);
其中的reply引數指向的是與同步下有相同定義的reply結構。注意此處的reply佔用的空間是會在回撥函式執行後被自動釋放的,這點要區別於同步。private引數是你傳送命令時所指定的指標,你可以把一些資訊,例如所執行的命令儲存在這個指標的空間中,這樣回撥函式被呼叫的時候你才能判斷這個回覆是由之前執行的哪個命令產生的。
上邊就是非同步redis所需的主要API了。
例程
下邊的例程來自hiredis的作者。注意這個歷程裡邊沒有為libevent事件處理單開執行緒,這在實際運用中是不多見的。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <hiredis.h>
#include <async.h>
#include <adapters/libevent.h>
//設定命令執行後的回撥函式
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r;
if (reply == NULL) return;
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
/* Disconnect after receiving the reply to GET */
redisAsyncDisconnect(c);
}
//設定連接回調函式
void connectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
printf("Connected...\n");
}
//設定斷開連接回調函式
void disconnectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
printf("Disconnected...\n");
}
int main (int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);//捕捉程式收到資料包時候的訊號
struct event_base *base = event_base_new();//新建一個libevent事件處理
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);//新建非同步連線
if (c->err) {
/* Let *c leak for now... */
printf("Error: %s\n", c->errstr);
return 1;
}
redisLibeventAttach(c,base);//將連線新增到libevent事件處理
redisAsyncSetConnectCallback(c,connectCallback);//設定連接回調
redisAsyncSetDisconnectCallback(c,disconnectCallback);//設定斷開連接回調
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));//傳送set命令
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");//傳送get命令
event_base_dispatch(base);//開始libevent迴圈。注意在這一步之前redis是不會進行連線的,前邊呼叫的命令傳送函式也沒有真正傳送命令
return 0;
}