hiredis异步操作模型

1 前言

Hiredis是一个Redis的C客户端库函数,基本实现了Redis的协议的最小集,工程上比较常使用Hiredis进行数据库的操作。本文主要介绍redis的同步操作和异步操作差异,以及如何实现一个中间层将异步网络事件处理模型与hireds适配。

2 同步与异步原理

redis对于同一条连接一般采用用串行的数据操作方式,同一条命令执行并返回再执行下一条命令。同步模型基本特征就是一条命令发送后需要等待服务器响应才能执行第二条命令。交互过程如下图所示:
在这里插入图片描述
redis异步操作,主要节约的是中间等待执行的时间,可以一次性将命令发送给服务器,服务器逐条处理返回。具体交互流程如下图所示。
在这里插入图片描述

3 Hiredis基本API

3.1 Hiredis同步API

同步API主要包括redis连接建立,发送命令和释放命令返回结果。

//1 连接建立返回redis上下文
redisContext * redisConnect ( const  char *ip, int port);
//2 命令执行,这里支持可变参数
void * redisCommand (redisContext *c, const  char *format, ...);
//3 执行命令的返回值需要手动释放
void  freeReplyObject ( void *reply);
//4 释放连接
void redisFree(redisContext *c);

(1)连接

redisContext *c = redisConnect( " 127.0.0.1 " , 6379 );
if (c == NULL || c->err) {
     if (c) {
         printf ( " Error: %s \n " , c-> errstr );
        //处理错误
    } else {
         printf ( " Can't allocate redis context \n " );
    }
}

(2)执行命令

//1 单一命令
reply = redisCommand(context, "SET foo bar");
//2 带字符串命令
reply = redisCommand(context, "SET foo %s", value);
//3 二进制命令
reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen);

(3)返回值类型
返回值是一个结构体:

typedef struct redisReply {
    int type; /* REDIS_REPLY_* */
    long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
    double dval; /* The double when type is REDIS_REPLY_DOUBLE */
    size_t len; /* Length of string */
    char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
                  REDIS_REPLY_VERB, and REDIS_REPLY_DOUBLE (in additional to dval). */
    char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null
                      terminated 3 character content type, such as "txt". */
    size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
    struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
} redisReply;

(4)值类型

REDIS_REPLY_STATUS:
//该命令以状态回复进行回复。可以使用 访问状态字符串reply->str。可以使用 访问此字符串的长度reply->len。
REDIS_REPLY_ERROR:
//该命令以错误回复。错误字符串的访问方式与REDIS_REPLY_STATUS.
REDIS_REPLY_INTEGER:
//该命令以整数回复。可以使用 reply->integertype 字段访问整数值long long。
REDIS_REPLY_NIL:
//该命令返回一个nil对象。没有可访问的数据。
REDIS_REPLY_STRING:
//批量(字符串)回复。可以使用 访问回复的值reply->str。可以使用 访问此字符串的长度reply->len。
REDIS_REPLY_ARRAY

(5)释放

void redisFree(redisContext *c);

3.2 Hiredis异步API

(1)连接

redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {
    printf("Error: %s\n", c->errstr);
    // handle error
}
//redisDisconnectCallback 
void ( const redisAsyncContext *c, int status);
//设置连接断开回调
int  redisAsyncSetDisconnectCallback (redisAsyncContext *ac, redisDisconnectCallback *fn);

(2)数据库操作

//接收返回回调redisCallbackFn 
void (redisAsyncContext *c, void *reply, void *privdata);
//异步命令
int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,
  const char *format, ...);

(3)断开连接

void redisAsyncDisconnect(redisAsyncContext *ac);

4 Hiredis异步适配层实现原理

hiredis有个adapters适配层模块,主要用于适配不同的事件触发方式,我们也可以根据自己的实际的网络模型进行封装适配层。这里以libevent为例进行说明。
(1)初始化适配接口
首先是构建libevent结构体对象,用于与redis进行数据交互转换,包括redis上下文和libevent事件和事件标志。

typedef struct redisLibeventEvents {
    redisAsyncContext *context;//异步redis上下文:redisAsyncContext 
    struct event *ev;		//事件
    struct event_base *base;//事件基
    struct timeval tv; 		//时间
    short flags;			//标志
    short state;			//状态
} redisLibeventEvents;

初始化redis适配层,主要是对于上下文的回调函数进行赋值,适配读写事件和初始化libevent。

static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
    redisContext *c = &(ac->c);
    redisLibeventEvents *e;

    /* Nothing should be attached when something is already attached */
    if (ac->ev.data != NULL)
        return REDIS_ERR;

    /* Create container for context and r/w events */
    e = (redisLibeventEvents*)hi_calloc(1, sizeof(*e));
    if (e == NULL)
        return REDIS_ERR;

    e->context = ac;

    /* Register functions to start/stop listening for events */
    ac->ev.addRead = redisLibeventAddRead;
    ac->ev.delRead = redisLibeventDelRead;
    ac->ev.addWrite = redisLibeventAddWrite;
    ac->ev.delWrite = redisLibeventDelWrite;
    ac->ev.cleanup = redisLibeventCleanup;
    ac->ev.scheduleTimer = redisLibeventSetTimeout;
    ac->ev.data = e;

    /* Initialize and install read/write events */
    e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e);
    e->base = base;
    return REDIS_OK;
}

(2)回调函数实现

static void redisLibeventDestroy(redisLibeventEvents *e) {
    hi_free(e);
}
//读写事件最终调到hiredis函数redisAsyncHandleRead和redisAsyncHandleWrite。
static void redisLibeventHandler(int fd, short event, void *arg) {
    ((void)fd);
    redisLibeventEvents *e = (redisLibeventEvents*)arg;
    e->state |= REDIS_LIBEVENT_ENTERED;

    #define CHECK_DELETED() if (e->state & REDIS_LIBEVENT_DELETED) {\
        redisLibeventDestroy(e);\
        return; \
    }

    if ((event & EV_TIMEOUT) && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
        redisAsyncHandleTimeout(e->context);
        CHECK_DELETED();
    }

    if ((event & EV_READ) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
        redisAsyncHandleRead(e->context);
        CHECK_DELETED();
    }

    if ((event & EV_WRITE) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
        redisAsyncHandleWrite(e->context);
        CHECK_DELETED();
    }

    e->state &= ~REDIS_LIBEVENT_ENTERED;
    #undef CHECK_DELETED
}
//更新事件当前的状态
static void redisLibeventUpdate(void *privdata, short flag, int isRemove) {
    redisLibeventEvents *e = (redisLibeventEvents *)privdata;
    const struct timeval *tv = e->tv.tv_sec || e->tv.tv_usec ? &e->tv : NULL;

    if (isRemove) {
        if ((e->flags & flag) == 0) {
            return;
        } else {
            e->flags &= ~flag;
        }
    } else {
        if (e->flags & flag) {
            return;
        } else {
            e->flags |= flag;
        }
    }

    event_del(e->ev);
    event_assign(e->ev, e->base, e->context->c.fd, e->flags | EV_PERSIST,
                 redisLibeventHandler, privdata);
    event_add(e->ev, tv);
}

static void redisLibeventAddRead(void *privdata) {
    redisLibeventUpdate(privdata, EV_READ, 0);
}

static void redisLibeventDelRead(void *privdata) {
    redisLibeventUpdate(privdata, EV_READ, 1);
}

static void redisLibeventAddWrite(void *privdata) {
    redisLibeventUpdate(privdata, EV_WRITE, 0);
}

static void redisLibeventDelWrite(void *privdata) {
    redisLibeventUpdate(privdata, EV_WRITE, 1);
}

static void redisLibeventCleanup(void *privdata) {
    redisLibeventEvents *e = (redisLibeventEvents*)privdata;
    if (!e) {
        return;
    }
    event_del(e->ev);
    event_free(e->ev);
    e->ev = NULL;

    if (e->state & REDIS_LIBEVENT_ENTERED) {
        e->state |= REDIS_LIBEVENT_DELETED;
    } else {
        redisLibeventDestroy(e);
    }
}

static void redisLibeventSetTimeout(void *privdata, struct timeval tv) {
    redisLibeventEvents *e = (redisLibeventEvents *)privdata;
    short flags = e->flags;
    e->flags = 0;
    e->tv = tv;
    redisLibeventUpdate(e, flags, 0);
}

5 同步与异步操作性能对比

5.1同步实现

    redisContext *c;
    redisReply *reply;
    const char *hostname = "127.0.0.1";

    int port = 6379;

    struct timeval timeout = { 1, 500000 };
 	c = redisConnectWithTimeout(hostname, port, timeout);
    if (c == NULL || c->err) {
        if (c) {
            printf("Connection error: %s\n", c->errstr);
            redisFree(c);
        } else {
            printf("Connection error: can't allocate redis context\n");
        }
        exit(1);
    }
    
    int num = 1000;
    int before = current();
    for (int i=0; i<num; i++) {
        reply = redisCommand(c,"INCR counter");
        printf("INCR counter: %lld\n", reply->integer);
        freeReplyObject(reply);
    }
    int used = current()-before;
    printf("sync %d exec redis command, used %d ms\n", num, used);
    redisFree(c);

5.2 异步实现

	struct event_base *base = event_base_new();
    redisOptions options = {0};
    REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
    struct timeval tv = {0};
    tv.tv_sec = 1;
    options.connect_timeout = &tv;

    redisAsyncContext *c = redisAsyncConnectWithOptions(&options);
    if (c->err) {
        /* Let *c leak for now... */
        printf("Error: %s\n", c->errstr);
        return 1;
    }

    redisLibeventAttach(c,base);
    redisAsyncSetConnectCallback(c,connectCallback);
    redisAsyncSetDisconnectCallback(c,disconnectCallback);
    before = current();
    num = 1000;
    for (int i = 0; i < num; i++) {
        redisAsyncCommand(c, getCallback, "count", "INCR counter");
    }
    event_base_dispatch(base);

5.3 性能对比

redis同步插入1000条数据:
在这里插入图片描述
redis异步插入1000条数据
在这里插入图片描述
性能差异在20-30倍之间。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值