Redis-C客户端-HiRedis-(二)

2023-11-04 09:18
文章标签 redis 客户端 hiredis

本文主要是介绍Redis-C客户端-HiRedis-(二),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

客户端示例:https://gist.github.com/dspezia/1893378

hiredis入门http://www.codingcool.com/2013/02/21/hiredis%E5%85%A5%E9%97%A8/

前几篇介绍了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例子:

redisReply  *reply ;
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的结构:

typedef  struct 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”段:

This type of reply is just a CRLF terminated string representing an integer, prefixed by a ":" byte. For example ":0\r\n", or ":1000\r\n" are integer replies.

乱译: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~


redis持久化和主从的学习

redis是个好东西,我在之前的几篇博客里也啰嗦过了,呵呵~
但是如果用在实际项目中,如果不是只拿它代替memcache的话,还是非常有必要了解它的持久化内容的,不然很可能出现数据丢失的尴尬情况。

我果断的搜索了一下相关内容,发现很多大牛都做了比较好的总结,贴一下我找到的还不错的帖子:
链接一
链接又一
链接还一

我就说说我碰到的问题吧~
我的测试环境:
os: win7 64bit
redis: 2.4.5 64bit

当以AOF方式持久化时,客户端运行BGREWRITEAOF命令会导致服务器端报错退出

  • Exiting on error writing to the append-only file: Bad file descriptor

    具体细节可以围观这里。

    我搜索了一下,这个问题貌似不是大众问题,只有个别人碰见,看到2种解决方法,一个是说把配置文件里的dir参数改成绝对路径,我试了试没用,还有就是更换版本,所以我又下了一个redis 2.2.2 for window,依然用64bit版的来测试,这次就ok了!

    然后引申出一个章节,redis官方是没有提供window版本的,我们下的应该都是民间的,那么我就不深究为什么2.4.5会报错了!
    由于这个原因,我也把测试环境从win7换成了centOS6.3 x86_64,redis版本也用的是比较新的2.6.9。毕竟官方正版的才是王道啊!

    然后是redis的主从,其实redis的主从配置真的不是一般的简单,看这里。
    简单的都有点让人不好意思了!

    但是查看了一下大牛们的分析,redis在集群和单点故障上还是存在缺陷的,要想搞清楚原因,就得看一些比较底层的资料,我列出一些:
    链接1,2,3

    除此之外,还有redis的运维监控方面的文章不错,可以看看

    好了,先到这里,回头补充


hiredis发布/订阅示例

发布订阅(pub/sub)是一种消息通信模式,主要的目的是解耦消息发布者和消息订阅者之间的耦合,这点和设计模式中的观察者模式比较相似。pub /sub不仅仅解决发布者和订阅者直接代码级别耦合也解决两者在物理部署上的耦合。redis作为一个pub/sub server,在订阅者和发布者之间起到了消息路由的功能。订阅者可以通过subscribe和psubscribe命令向redis server订阅自己感兴趣的消息类型,redis将消息类型称为通道(channel)。当发布者通过publish命令向redisserver发送特定类型的消息时。订阅该消息类型的全部client都会收到此消息。这里消息的传递是多对多的。一个client可以订阅多个 channel,也可以向多个channel发送消息。

   
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include "hiredis.h" #include "async.h" #include "adapters/libevent.h" void subCallback(redisAsyncContext *c, void *r, void *priv) { redisReply *reply = r; if (reply == NULL) return; if ( reply->type == REDIS_REPLY_ARRAY && reply->elements == 3 ) { if ( strcmp( reply->element[0]->str, "subscribe" ) != 0 ) { printf( "Received[%s] channel %s: %s\n", (char*)priv, reply->element[1]->str, reply->element[2]->str ); } } } 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(); 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); redisAsyncSetConnectCallback(c,connectCallback); redisAsyncSetDisconnectCallback(c,disconnectCallback); redisAsyncCommand(c, subCallback, (char*) "sub", "SUBSCRIBE foo"); event_base_dispatch(base); return 0; }

这篇关于Redis-C客户端-HiRedis-(二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/344757

相关文章

Knife4j+Axios+Redis前后端分离架构下的 API 管理与会话方案(最新推荐)

《Knife4j+Axios+Redis前后端分离架构下的API管理与会话方案(最新推荐)》本文主要介绍了Swagger与Knife4j的配置要点、前后端对接方法以及分布式Session实现原理,... 目录一、Swagger 与 Knife4j 的深度理解及配置要点Knife4j 配置关键要点1.Spri

Redis出现中文乱码的问题及解决

《Redis出现中文乱码的问题及解决》:本文主要介绍Redis出现中文乱码的问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1. 问题的产生2China编程. 问题的解决redihttp://www.chinasem.cns数据进制问题的解决中文乱码问题解决总结

Redis的持久化之RDB和AOF机制详解

《Redis的持久化之RDB和AOF机制详解》:本文主要介绍Redis的持久化之RDB和AOF机制,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录概述RDB(Redis Database)核心原理触发方式手动触发自动触发AOF(Append-Only File)核

Redis分片集群、数据读写规则问题小结

《Redis分片集群、数据读写规则问题小结》本文介绍了Redis分片集群的原理,通过数据分片和哈希槽机制解决单机内存限制与写瓶颈问题,实现分布式存储和高并发处理,但存在通信开销大、维护复杂及对事务支持... 目录一、分片集群解android决的问题二、分片集群图解 分片集群特征如何解决的上述问题?(与哨兵模

SpringBoot连接Redis集群教程

《SpringBoot连接Redis集群教程》:本文主要介绍SpringBoot连接Redis集群教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1. 依赖2. 修改配置文件3. 创建RedisClusterConfig4. 测试总结1. 依赖 <de

SpringBoot+Redis防止接口重复提交问题

《SpringBoot+Redis防止接口重复提交问题》:本文主要介绍SpringBoot+Redis防止接口重复提交问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录前言实现思路代码示例测试总结前言在项目的使用使用过程中,经常会出现某些操作在短时间内频繁提交。例

Redis 配置文件使用建议redis.conf 从入门到实战

《Redis配置文件使用建议redis.conf从入门到实战》Redis配置方式包括配置文件、命令行参数、运行时CONFIG命令,支持动态修改参数及持久化,常用项涉及端口、绑定、内存策略等,版本8... 目录一、Redis.conf 是什么?二、命令行方式传参(适用于测试)三、运行时动态修改配置(不重启服务

浅析如何保证MySQL与Redis数据一致性

《浅析如何保证MySQL与Redis数据一致性》在互联网应用中,MySQL作为持久化存储引擎,Redis作为高性能缓存层,两者的组合能有效提升系统性能,下面我们来看看如何保证两者的数据一致性吧... 目录一、数据不一致性的根源1.1 典型不一致场景1.2 关键矛盾点二、一致性保障策略2.1 基础策略:更新数

Redis Cluster模式配置

《RedisCluster模式配置》:本文主要介绍RedisCluster模式配置,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录分片 一、分片的本质与核心价值二、分片实现方案对比 ‌三、分片算法详解1. ‌范围分片(顺序分片)‌2. ‌哈希分片3. ‌虚

Springboot整合Redis主从实践

《Springboot整合Redis主从实践》:本文主要介绍Springboot整合Redis主从的实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言原配置现配置测试LettuceConnectionFactory.setShareNativeConnect