Redis 学习 - hiredis(官网 2021-01-06)



官网:https://github.com/redis/hiredis
https://redislabs.com/lp/hiredis/
https://github.com/redis/hiredis/releases



Hiredis

1. Hiredis简介

Hiredis是Redis数据库的一个极简C客户端库。

它是极简主义的,因为它只增加了对协议的最小支持,但同时它使用了一个高级的类似printf的API。

除了支持发送命令和接收应答之外,它还附带了一个与I/O层分离的应答解析器。它是一个流解析器,设计为易于重用,例如,它可以用于更高级别的语言绑定,以实现高效的应答解析。

Hiredis只支持二进制安全的Redis协议,所以你可以使用它与任何版本的Redis >= 1.2.0。

这个库附带了多个api。有同步API、异步API和应答解析API。

IMPORTANT: Breaking changes from 0.14.1 -> 1.0.0 (一些更改)

  • redisContext has two additional members (free_privdata, and privctx).
  • redisOptions.timeout has been renamed to redisOptions.connect_timeout, and we’ve added redisOptions.command_timeout.
  • redisReplyObjectFunctions.createArray now takes size_t instead of int for its length parameter.

2. Synchronous API(同步API)

要使用同步API,只需要引入几个函数调用:

redisContext *redisConnect(const char *ip, int port);
void *redisCommand(redisContext *c, const char *format, ...);
void freeReplyObject(void *reply);

2.1 Connecting(连接)

redisConnect函数用于创建所谓的redisContext。
Context(上下文)是Hiredis保存连接状态的地方。

redisContext结构
1 有一个整数err字段,当连接处于错误状态时,这个字段是非零的。
2 errstr字段将包含一个包含错误描述的字符串。
3 关于错误的更多信息可以在Errors一节(见下文)中找到。
4 在尝试使用redisConnect连接Redis,你应该检查err字段,看看是否建立连接成功:

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

注意:redisContext不是线程安全的。

2.2 Sending commands(发送命令)

有几种方法可以向Redis发出命令。首先要介绍的是redisCommand。该函数采用类似于printf的格式。最简单的形式是这样使用的:

reply = redisCommand(context, "SET foo bar");

说明符%s在命令中插入一个字符串,并使用strlen来确定字符串的长度:

reply = redisCommand(context, "SET foo %s", value);

当你需要在命令中传递二进制安全字符串时,可以使用%b说明符。与指向字符串的指针一起,它需要string对象的size_t长度参数:

reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen);

在内部,Hiredis以不同的参数拆分命令,并将其转换为与Redis通信的协议。一个或多个空格分隔参数,所以你可以在参数的任何位置使用说明符:

reply = redisCommand(context, "SET key:%s %s", myid, value);

2.3 Using replies(使用回复)

当命令成功执行时,redisCommand的返回值保存一个应答。
当错误发生时,返回值为NULL,并且上下文中的err字段将被设置(参见Errors部分)。
一旦返回错误,就不能重用上下文,您应该建立一个新的连接。

redisCommand的标准应答类型为redisReply。redisReply中的type字段应该用来测试收到了什么类型的回复:

2.4 RESP2

  • REDIS_REPLY_STATUS:
    The command replied with a status reply. (返回应答状态)
    状态字符串可以使用reply->str访问。
    该字符串的长度可以通过reply->len访问。

  • REDIS_REPLY_ERROR:
    The command replied with an error.(返回一个错误)。
    可以访问与REDIS_REPLY_STATUS相同的错误字符串(reply->str) 查看错误描述。

  • REDIS_REPLY_INTEGER:
    The command replied with an integer. (返回一个整数)
    该整数值可以通过long long类型的reply->integer字段访问。

  • REDIS_REPLY_NIL:
    The command replied with a nil object. (返回一个nil对象)。
    没有数据可访问。

  • REDIS_REPLY_STRING:
    A bulk (string) reply.(批量(字符串)) 。
    返回的值可以通过reply->str访问。该字符串的长度可以通过reply->len访问。

  • REDIS_REPLY_ARRAY:
    A multi bulk reply. (一个多批量回复(其实就是数组))。
    数组的元素个数存储在reply->elements中。
    每个元素也是一个redisReply对象,可以通过reply->element[…index…]访问数组中的某个元素。
    Redis可能会用嵌套数组来回复,这是完全支持的。

2.5 RESP3

Hiredis还支持如下所示的每一种新的RESP3数据类型。有关协议的更多信息,请参见RESP3规范。

  • REDIS_REPLY_DOUBLE:
    The command replied with a double-precision floating point number. (返回一个双精度浮点数)。
    该值存储在str成员中 存储为字符串,可以使用strtod或类似的方法进行转换。

  • REDIS_REPLY_BOOL:
    A boolean true/false reply. (返回一个bool值)。
    该值存储在integer成员中,值为0或1。

  • REDIS_REPLY_MAP:
    An array with the added invariant that there will always be an even number of elements. (添加元素个数始终为偶数的不变式的数组)。
    除了前面提到的不变式外,这个映射在函数上等价于REDIS_REPLY_ARRAY。

  • REDIS_REPLY_SET:
    An array response where each entry is unique. 一个数组响应,其中每个条目都是唯一的。
    与映射类型一样,除了没有重复的值之外,数据与数组响应是相同的。

  • REDIS_REPLY_PUSH:
    An array that can be generated spontaneously by Redis. 一个可以由Redis自动生成的数组。
    此数组响应将始终包含至少两个子元素。第一个包含推送消息的类型(例如message或invalidate),第二个是包含推送有效负载本身的子数组。

  • REDIS_REPLY_ATTR:
    从Redis 6.0.6开始,这个回复类型在Redis中不被使用

  • REDIS_REPLY_BIGNUM:
    A string representing an arbitrarily large signed or unsigned integer value.表示任意大的有符号或无符号整数值的字符串。
    该数字将在redisReply的str成员中被编码为字符串。

  • REDIS_REPLY_VERB:
    A verbatim string, intended to be presented to the user without modification. 一种一字不差的字符串,不经修改就呈现给用户。
    字符串有效载荷存储在str成员中,类型数据存储在vtype成员中(例如txt是原始文本,md是markdown)。

回复应该使用freeReplyObject()函数释放。
注意,这个函数将负责释放包含在数组和嵌套数组中的子应答对象,因此用户不需要释放子应答(这实际上是有害的,将破坏内存)。

重要提示:
当使用异步API时,hiredis(1.0.0)的当前版本会释放应答。
这意味着当你使用这个API时,你不应该调用freeReplyObject。
回调返回后,应答被hiredis清理。
我们可能会引入一个标志,以便在库的未来版本中对此进行配置。

2.6 Cleaning up(清理)

要断开和释放上下文,可以使用以下函数:

void redisFree(redisContext *c);

这个函数立即关闭套接字,然后释放在创建上下文时完成的分配。

2.7 Sending commands (cont’d)(发送命令)(二进制安全)

与redisCommand一样,可以使用redisCommandArgv函数发出命令。它的原型如下:

void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);

接受的参数个数为:argc;
各个参数(每个参数是一个字符串):字符串数组argv(这是一个:元素类型为char*的数组);
各个参数的长度:argvlen(这是一个:元素类型为size_t的数组)。

为方便起见,argvlen可以设置为NULL,并且函数将在每个参数上使用strlen(3)来确定其长度。
显然,当任何参数都需要二进制安全时,应该在数组argvlen中提供每个参数的长度。

返回值具有与redisCommand相同的语义。

2.8 Pipelining 流水线

为了解释Hiredis如何在阻塞连接中支持管道,需要了解内部执行流。

当redisCommand家族中的任何一个函数被调用时,Hiredis首先根据Redis协议格式化命令。格式化的命令然后放入上下文的输出缓冲区。这个输出缓冲区是动态的,因此它可以容纳任意数量的命令。在将命令放入输出缓冲区之后,将调用redisGetReply。该函数有以下两条执行路径:

  1. 输入缓冲区非空:
    尝试解析来自输入缓冲区的单个响应并返回它
    如果没有可以解析的答复,继续2
  2. 输入缓冲区为空:
    将整个输出缓冲区写入套接字
    从套接字读取,直到可以解析单个应答

函数redisGetReply作为Hiredis API的一部分,可以在套接字上期待应答时使用。
对于管道命令,唯一需要做的事情是填充输出缓冲区。
由于这个原因,除了不返回应答外,可以使用两个与redisCommand家族相同的命令:

void redisAppendCommand(redisContext *c, const char *format, ...);
void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);

在调用任意一个函数一次或多次后,可以使用redisGetReply来接收后续的响应。
该函数的返回值是REDIS_OK或REDIS_ERR,后者表示在读取应答时发生了错误。
与其他命令一样,上下文中的err字段可以用来找出导致此错误的原因。

下面的例子展示了一个简单的管道(导致只有一个write(2)调用和一个read(2)调用):

redisReply *reply;
redisAppendCommand(context,"SET foo bar");
redisAppendCommand(context,"GET foo");
redisGetReply(context,(void *)&reply); // reply for SET
freeReplyObject(reply);
redisGetReply(context,(void *)&reply); // reply for GET
freeReplyObject(reply);

这个API也可以用来实现阻塞订阅器:

reply = redisCommand(context,"SUBSCRIBE foo");
freeReplyObject(reply);
while(redisGetReply(context,(void *)&reply) == REDIS_OK) {
   
   
    // consume message
    freeReplyObject(reply);
}

2.9 Errors错误

当函数调用不成功时,根据函数返回NULL或REDIS_ERR。上下文中的err字段将是非零的,并设置为以下常量之一:

REDIS_ERR_IO:在创建连接时出现了一个I/O错误,试图写到套接字或从套接字读取。如果在应用程序中包含了errno.h,则可以使用全局errno变量来找出问题所在。

REDIS_ERR_EOF:服务器关闭连接,导致一个空读。

REDIS_ERR_PROTOCOL:解析协议时出现错误。

REDIS_ERR_OTHER:任何其他错误。目前,它只在指定的主机名无法解析时使用。

在每种情况下,上下文中的errstr字段都将被设置为保存错误的字符串表示形式。

3. Asynchronous API(异步API)

Hiredis提供了一个异步API,可以轻松地与任何事件库一起工作。例如:Hiredis与libevlibevent捆绑使用。

3.1 Connecting(连接)

函数redisAsyncConnect可以用来建立一个非阻塞连接到Redis。
它返回一个指向新创建的redisAsyncContext结构体的指针。
创建后应检查err字段,以查看是否存在创建连接的错误。
因为将要创建的连接是非阻塞的,所以如果指定的主机和端口能够接受连接,内核就不能立即返回。

注意:redisAsyncContext不是线程安全的。

redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {
   
   
    printf("Error: %s\n", c->errstr);
    // handle error
}

异步上下文可以保存一个断开回调函数,当连接断开时(由于一个错误或每个用户请求)调用该函数。这个函数应该有以下的原型:

void(const redisAsyncContext *c, int status);

在断开连接时,当用户发起断开连接时,status参数被设置为REDIS_OK,或者当由错误导致断开连接时设置为REDIS_ERR。当它是REDIS_ERR时,可以访问上下文中的err字段来找出错误的原因。

在断开回调触发后,context对象总是被释放。当需要重新连接时,断开回调是一个很好的选择。

每个上下文只能设置一次断开回调。对于后续调用,它将返回REDIS_ERR。设置断开回调函数的原型如下:

int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);

ac->data可以用于将用户数据传递给这个回调函数,对于redisConnectCallback也可以这样做。

3.2 Sending commands and their callbacks(发送命令和它们的回调)

在异步上下文中,由于事件循环的性质,命令是自动流水线的。
因此,与同步API不同,发送命令的方式只有一种。
因为命令是异步发送到Redis的,发出命令需要一个回调函数,当收到回复时调用。回复回调应该有以下原型:

void(redisAsyncContext *c, void *reply, void *privdata);

privdata参数可用于将任意数据从命令最初排队等待执行的点发送到回调函数。

在异步上下文中,可以使用以下函数发出命令:

int redisAsyncCommand(
		redisAsyncContext *ac, 
		redisCallbackFn *fn, 
		void *privdata,
  		const char *format, ...);
int redisAsyncCommandArgv(
  		redisAsyncContext *ac, 
  		redisCallbackFn *fn, 
  		void *privdata,
  		int argc, 
  		const char **argv, 
  		const size_t *argvlen);

这两个函数的工作方式都类似于它们的阻塞对应函数。
当命令成功添加到输出缓冲区时,返回值为REDIS_OK,否则返回值为REDIS_ERR。
示例:当根据用户请求断开连接时,不可能向输出缓冲区添加新命令,并且在调用redisAsyncCommand家族时返回REDIS_ERR。

如果读取带有空回调函数的命令的应答,则会立即释放它。
当命令的回调非空时,内存在回调之后立即被释放:应答仅在回调的持续时间内有效。

当上下文遇到错误时,所有挂起的回调都将以空应答调用。

3.3 Disconnecting(断开)

可以使用以下方式终止异步连接:

void redisAsyncDisconnect(redisAsyncContext *ac);

当调用此函数时,连接不会立即终止。相反,新的命令将不再被接受,并且只有当所有挂起的命令都已写入套接字,它们各自的响应已经被读取,它们各自的回调已经被执行时,连接才会终止。在此之后,断开连接回调函数将以REDIS_OK状态执行,上下文对象将被释放。

3.4 Hooking it up to event library X(将其连接到事件库X)

在创建上下文对象之后,需要在其上设置一些钩子。有关绑定到libev和libevent的信息,请参阅适adapters/目录。

4 Reply parsing API(回复解析API)

Hiredis附带了一个应答解析API,这使得编写更高级别的语言绑定变得很容易。

reply解析API由以下函数组成:

redisReader *redisReaderCreate(void);
void redisReaderFree(redisReader *reader);
int redisReaderFeed(redisReader *reader, const char *buf, size_t len);
int redisReaderGetReply(redisReader *reader, void **reply);

当hiredis创建一个普通的Redis上下文时,同样的一组函数被hiredis内部使用,上面的API只是将它暴露给用户直接使用。

4.1 Usage(使用)

redisReaderCreate()函数创建一个redisReader结构体,该结构体为协议解析器保存一个带有未解析数据和状态的缓冲区。

传入的数据——很可能来自套接字——可以使用redisReaderFeed()放置在redisReader的内部缓冲区中。
redisReaderFeed()这个函数将复制buf所指向的len字节的缓冲区。
当调用redisReaderGetReply()时,将解析该数据。
这个函数通过void **reply返回一个整数状态和一个reply对象(如上所述)。返回的状态可以是REDIS_OK或REDIS_ERR,后者表示出了问题(协议错误或内存不足错误)。

解析器将多批量有效负载的嵌套级别限制为7。如果多块嵌套级别高于此级别,解析器将返回一个错误。

4.2 Customizing replies(定制答道)

函数’ redisReaderGetReply() ‘创建’ redisReply ‘,并使函数参数’ reply ‘指向创建的变量’ redisReply '。

例如,如果响应类型为’ REDIS_REPLY_STATUS ‘,那么’ redisReply ‘的’ str '字段将保持状态为普通的C字符串。

但是,可以通过在’ redisReader ‘结构上设置’ fn ‘字段来定制负责创建’ redisReply ‘实例的函数。这应该在创建’ redisReader '后立即完成。

例如, hiredis-rb
使用自定义应答对象函数创建Ruby对象。

4.3 Reader max buffer(Reader的最大缓冲区)

无论是直接使用Reader API还是通过一个普通的Redis上下文间接使用它,redisReader结构都使用一个缓冲区来积累来自服务器的数据。为了避免在未使用的缓冲区中浪费内存,通常这个缓冲区在它为空且大于16 KiB时被销毁

然而,当使用非常大的有效负载时,破坏缓冲区可能会大大降低性能,因此可以修改空闲缓冲区的最大大小,将reader结构体的maxbuf字段的值更改为所需的值。0这个特殊值意味着空闲缓冲区没有最大值,因此该缓冲区永远不会被释放。

例如,如果你有一个普通的Redis上下文,你可以设置最大空闲缓冲区为零(无限制),只需:

context->reader->maxbuf = 0;

这样做只是为了在使用大负载时最大化性能。
为了防止分配无用的内存,上下文应该尽快重新设置为REDIS_READER_MAX_BUF。

4.4 Reader max array elements(reader数组元素的最大个数)

默认情况下,hiredis应答解析器将多块元素的最大数目设置为2^32 - 1或4,294,967,295个条目。如果你需要处理多块的回复,你可以把值设置得更高或为0,这意味着无限:

context->reader->maxelements = 0;

5 SSL/TLS Support

5.1 Building

默认情况下不支持SSL/TLS,需要一个明确的标志:
安装hiredis时,make时要加参数,如下:

make USE_SSL=1

这需要OpenSSL开发包(例如,包括头文件)可用。

When enabled, SSL/TLS支持libhiredis_ssl.a和libhiredis_ssl.so静态/动态库。这使原始库不受影响,因此不会引入额外的依赖项。

5.2 Using it

首先,你需要确保包含SSL头文件:

#include "hiredis.h"
#include "hiredis_ssl.h"

除了libhiredis之外,还需要链接到libhiredis_ssl,并添加-lssl -lcrypto以满足其依赖关系。
Hiredis在其正常的redisContext或redisAsyncContext之上实现了SSL/TLS,所以你需要首先建立一个连接,然后发起SSL/TLS握手。

5.2.1 Hiredis OpenSSL Wrappers

在Hiredis可以协商SSL/TLS连接之前,有必要初始化OpenSSL并创建上下文。你可以通过两种方式做到这一点:

  1. 直接使用OpenSSL API初始化库的全局上下文,并创建SSL_CTX *和SSL *上下文。使用SSL *对象,您可以调用redisInitiateSSL()。
  2. 使用一组hiredis提供的围绕OpenSSL的包装器,创建一个redisSSLContext对象来保存配置,并使用redisInitiateSSLWithContext()来初始化SSL/TLS握手。
/* An Hiredis SSL context. It holds SSL configuration and can be reused across
 * many contexts.
 */
redisSSLContext *ssl_context;

/* An error variable to indicate what went wrong, if the context fails to
 * initialize.
 */
redisSSLContextError ssl_error;

/* Initialize global OpenSSL state.
 *
 * You should call this only once when your app initializes, and only if
 * you don't explicitly or implicitly initialize OpenSSL it elsewhere.
 */
redisInitOpenSSL();

/* Create SSL context */
ssl_context = redisCreateSSLContext(
    "cacertbundle.crt",     /* File name of trusted CA/ca bundle file, optional */
    "/path/to/certs",       /* Path of trusted certificates, optional */
    "client_cert.pem",      /* File name of client certificate file, optional */
    "client_key.pem",       /* File name of client private key, optional */
    "redis.mydomain.com",   /* Server name to request (SNI), optional */
    &ssl_error);

if(ssl_context == NULL || ssl_error != 0) {
   
   
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值