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

相关文章

Redis 命令详解与实战案例

《Redis命令详解与实战案例》本文详细介绍了Redis的基础知识、核心数据结构与命令、高级功能与命令、最佳实践与性能优化,以及实战应用场景,通过实战案例,展示了如何使用Redis构建高性能应用系统... 目录Redis 命令详解与实战案例一、Redis 基础介绍二、Redis 核心数据结构与命令1. 字符

SpringBoot18 redis的配置方法

《SpringBoot18redis的配置方法》本文介绍在SpringBoot项目中集成和使用Redis的方法,包括添加依赖、配置文件、自定义序列化方式、使用方式、实际使用示例、常见操作总结以及注意... 目录一、Spring Boot 中使用 Redis1. 添加依赖2. 配置文件3. Redis 配置类

Redis中群集三种模式的实现

《Redis中群集三种模式的实现》Redis群集有三种模式,分别是主从同步/复制、哨兵模式、Cluster,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1. Redis三种模式概述2、Redis 主从复制2.1 主从复制的作用2.2 主从复制流程2

Redis的安全机制详细介绍及配置方法

《Redis的安全机制详细介绍及配置方法》本文介绍Redis安全机制的配置方法,包括绑定IP地址、设置密码、保护模式、禁用危险命令、防火墙限制、TLS加密、客户端连接限制、最大内存使用和日志审计等,通... 目录1. 绑定 IP 地址2. 设置密码3. 保护模式4. 禁用危险命令5. 通过防火墙限制访问6.

深入理解Redis线程模型的原理及使用

《深入理解Redis线程模型的原理及使用》Redis的线程模型整体还是多线程的,只是后台执行指令的核心线程是单线程的,整个线程模型可以理解为还是以单线程为主,基于这种单线程为主的线程模型,不同客户端的... 目录1 Redis是单线程www.chinasem.cn还是多线程2 Redis如何保证指令原子性2.

Docker + Redis 部署集群的实现步骤

《Docker+Redis部署集群的实现步骤》本文详细介绍了在三台服务器上部署高可用Redis集群的完整流程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋... 目录一、环境准备1. 服务器规划(3 台服务器)2. 防火墙配置(三台服务器均执行)3. 安装 docke

Redis中删除策略的几种实现方式

《Redis中删除策略的几种实现方式》本文详细介绍了Redis的过期键删除策略和内存淘汰策略,过期键删除策略包括定时删除、惰性删除和定期删除,具有一定的参考价值,感兴趣的可以了解一下... 目录前言一、设计背景:为什么需要删除策略?二、第一类:过期键的 3 种核心删除策略1. 定时删除(Timed Dele

Spring Boot整合Redis注解实现增删改查功能(Redis注解使用)

《SpringBoot整合Redis注解实现增删改查功能(Redis注解使用)》文章介绍了如何使用SpringBoot整合Redis注解实现增删改查功能,包括配置、实体类、Repository、Se... 目录配置Redis连接定义实体类创建Repository接口增删改查操作示例插入数据查询数据删除数据更

Java Lettuce 客户端入门到生产的实现步骤

《JavaLettuce客户端入门到生产的实现步骤》本文主要介绍了JavaLettuce客户端入门到生产的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 目录1 安装依赖MavenGradle2 最小化连接示例3 核心特性速览4 生产环境配置建议5 常见问题

Redis 基本数据类型和使用详解

《Redis基本数据类型和使用详解》String是Redis最基本的数据类型,一个键对应一个值,它的功能十分强大,可以存储字符串、整数、浮点数等多种数据格式,本文给大家介绍Redis基本数据类型和... 目录一、Redis 入门介绍二、Redis 的五大基本数据类型2.1 String 类型2.2 Hash