redis简介

redis 作为一种key-value存储系统,是常用的非关系型数据库。主要功能是缓存数据,直接从内存获取待访问数据,避免需要从数据库访问数据,导致数据库io成为系统瓶颈。

redis命令简介

redis的数据存储在redis-server进程中,通过redis-cli命令可以对redis-server中的数据进行增删改查,redis-cli会与redis-server建立tcp连接,通过网络传输信息,或者使用不同语言对应的api库,库中会按照约定的协议与redis-server进行交互。常见的通过redis-cli的访问流程如下展示。

#./redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379>
127.0.0.1:6379> set testkey 123
OK
127.0.0.1:6379> get testkey
"123"
127.0.0.1:6379> quit
 

redis的常用命令列表如下

  • 字符串操作
  • SET key value (设置字符串键值对)
  • GET key
  • DEL key
  • key相关命令
  • DEL key (删除key)
  • EXISTS key (检查KEY是否存在)
  • KEYS * (列出所有KEY)
  • 哈希操作(哈希适合于存储对象)
  • HSET key field value (设置哈希表中key的字段field值为value)
  • HGET key field (返回哈希表指定字段的所有值)
  • HGETALL key (获取指定key的所有字段和值)
  • HDEL key field [filed2] (删除指定key的一个或多个字段)
  • 发布订阅操作
  • PUBLISH channel message (在channel上发布消息)
  • SUBSCRIBE channel (订阅channel)
  • PUBSUB CHANNELS (查看订阅与发布系统的状态)

redis还有其他非常多其他的操作,在此暂不列举,网上有很多资料,目前进程间互通数据的流程只是大概涉及到上述操作,故而在此列举。

使用 hiredis 库通过c/c++进行redis操作

https://github.com/redis/hiredis

hiredis库为目标比较常用的以c api封装的redis库。github上下载库后交叉编译可生成 libhiredis.a,同时需要引入hiredis.h头文件。

同步模式下的使用方式大致如下

/*
初始化
redisContext *redisConnect(const char *ip, int port);
*/
redisContext * pRedisContext = redisConnect("127.0.0.1",12345)

/*执行 设置/删除操作命令*/
redisReply * reply = NULL;
reply = (redisReply*)redisCommand(pRedisContext, "SET %s %d",key,value);

/*校验返回结果是否ok*/
if (reply->type == REDIS_REPLY_ERROR){
    freeReplyObject(reply);
    return -1;
}
freeReplyObject(reply);

/*执行查询操作结果命令*/
redisReply * reply = NULL;
reply = (redisReply*)redisCommand(pRedisContext, "GET %s",key);
/*判断返回的结果是否是预期的类型,比如string类型*/
if(tmpReply->type == REDIS_REPLY_STRING){
    printf("%s",tmpReply->str);
}else{
    freeReplyObject(tmpReply);
    return -1;
}
freeReplyObject(tmpReply);
return 0;

同步模式下的使用方式比较简单,具体不同数据类型的读取,可以进一步参考github上的readme

基于hiredis库的异步redis操作

如果需要使用异步的方式执行redis操作,比如需要立即返回而不等待,或者需要使用subscribe订阅功能时,必须要使用异步方式使用,可以将redis搭配任意事件响应框架使用,在example目录下提供了整合hiredis与libev和libevent的例子,注意连接得到的异步句柄redisAsyncContext不是线程安全的,需要有锁处理。

这里以libevent为例,介绍异步redis api的操作。其中redisLibeventAttach函数的定义在github仓库中hiredis/adapters/libevent.h文件中,使用的示例在 hiredis/examples/example-libevent.c中有给出
初始化流程实现如下

redisAsyncContext * pRedisAsyncCtx = NULL;
pRedisAsyncCtx = redisAsyncConnect("127.0.0.1", 12345);
event_base *pBase = event_base_new();
redisLibeventAttach(pRedisAsyncCtx,pBase);

执行异步操作命令的示例如下,需按要求格式定义回调

int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);

    redisAsyncCommand(pRedisAsyncCtx , callback, pSelfClass, "set %s %s",key,value);

    /*用于判断是否失败*/
    void callback(redisAsyncContext *c, void *r, void *privdata) {
    	redisReply *reply = (redisReply* )r;
    	if (reply == NULL) return;
        	selfclass * pSelfClass = (selfclass*) privdata;
    	}
    }

如无需关注返回结果,回调设置为NULL即可。

    iRet = redisAsyncCommand(pRedisAsyncCtx ,NULL,NULL,"PUBLISH %s %s",key,message);

以subscribe订阅流程为例,同时说明异步回调返回的数据类型

    redisAsyncCommand(pRedisAsyncCtx, callback, pPrivdata, "subscribe %s", key);
    /*针对订阅接口,返回发布的message信息*/
    void callback(redisAsyncContext *c, void *r, void *privdata) {
	    redisReply *reply = (redisReply* )r;
	    if (reply == NULL) return;
	    selfclass * pSelfClass = (selfclass*) privdata;
	    /*订阅接口回调返回数组信息*/
	    if(reply->type == REDIS_REPLY_ARRAY && reply->elements == 3 ){
	        printf(": Recieve message:\n%s:%zu:type:%d\n:%s:%zu:type:%d\n:%s:%zu:type:%d\n",
	        reply->element[0]->str, reply->element[0]->len,reply->element[0]->type,
	        reply->element[1]->str, reply->element[1]->len,reply->element[0]->type,
	        reply->element[2]->str, reply->element[2]->len,reply->element[0]->type);
	    }
	}

订阅接口返回的类型为ARRAY类型,包含3个元素的数组,每一个元素则是string字符串类型,第一个元素为"message" , 第二个元素为订阅的 key值,第三个元素为key值对应的消息。

type有如下几种类型:

#define REDIS_REPLY_STRING 1
#define REDIS_REPLY_ARRAY 2
#define REDIS_REPLY_INTEGER 3
#define REDIS_REPLY_NIL 4
#define REDIS_REPLY_STATUS 5
#define REDIS_REPLY_ERROR 6

/*redisReplay的数据结构定义如下*/
typedef struct redisReply {
    int type; /* REDIS_REPLY_* */
    long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
    size_t 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;

基于redis的进程间交互流程

在一般的情况下,进程间共享数据可以使用文件或者mmap共享内存,彼此通知可以基于信号量来实现。但想要实现存储的数据的结构化还是需要做很多工作,应用redis的订阅通知功能可以在一定程度上简化这部分的工作。

基于上述redis下同步和异步的操作流程,我们可以设定一套简单的缓存结构化的数据和进程间通信彼此告知数据变化的框架应用。

比如初始设置 status的字段到redis,同时各进程都向redis订阅keyevent通知,在对应变量产生变化时,对应进程发布通知keyevent给redis,通知内容为具体的key值,比如status。订阅了该keyevent的各个进程都会收到通知,从信息中获取到变更的key值,根据key值拿到对应新的值,执行必要的操作,如上即为基于redis的进程间交互流程的实现。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐