前几篇介绍了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例子:
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的结构:
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”段:
乱译: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~