hiredis中異步實現(pub/sub)

轉自:https://blog.csdn.net/l1902090/article/details/38583663

前言

    一般情況下我們使用的都是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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章