使用hiredis接口(Synchronous API)编写redis流水线客户端

本文主要介绍使用hiredis接口(Synchronous API)编写redis流水线(pipelining)客户端的方法。

1. 流水线(pipelining)介绍

流水线(pipelining)允许redis客户端一次性向redis服务器发送多个命令,Redis服务器在接收到这些命令后,按顺序处理这些命令,然后将(这些命令的)处理结果一次性返回给redis客户端。

通过使用流水线,可以减少redis客户端与redis服务器之间的网络通信次数,以此提升redis客户端在发送多个命令时的性能。 

为了解释hiredis如何在阻塞连接中支持流水线,我们通过分析redisCommand函数的执行步骤,了解hiredis流水线的内部原理。

当redisCommand函数(或其同族函数)被调用时,hiredis首先根据redis协议,将需要执行的命令进行格式化,然后,将格式化后的命令放入到redis连接的output buffer中(这个output buffer是动态的,所以它可以容纳任何数量的命令)。当命令被放入output buffer后,此时redisGetReply被调用了,这个函数会进行下面两种操作:

1. 如果input buffer不为空:则从input buffer中解析一条来自redis服务器的相应消息,并返回该消息;

2. 如果input buffer为空:则将output buffer中的全部内容写入socket中,然后等待socket中redis服务器返回的响应消息,读取并解析该消息。

函数redisGetReply作为hiredis API,可以在socket中有(redis服务器的)响应消息时使用。而对于流水线命令来说,只需要把想要执行的命令放入到output buffer中即可,通常我们使用如下函数(或其同族函数)来实现此目的:

void redisAppendCommand(redisContext *c, const char *format, ...);

上面的redisAppendCommand函数与redisCommand函数的区别在于,redisAppendCommand函数不返回redis服务器的响应消息(实际上它只将命令放入到output buffer中),而redisCommand函数实际上包括了“redisAppendCommand函数”和“redisGetReply函数”两个步骤,所以redisCommand函数是阻塞的(使用了阻塞的redisContext对象),每次调用redisCommand函数时,都要等待redis服务端的返回结果,然后才能继续执行程序后面的逻辑。

redisCommand函数的使用示例如下:

redisReply *reply;

reply = redisCommand(conn, "SET %s %s", "foo", "bar");
freeReplyObject(reply);

reply = redisCommand(conn, "GET %s", "foo");
printf("%s\n", reply->str);
freeReplyObject(reply);

如果我们需要向redis服务端发送多条命令,如果使用redisCommand函数来发送,那么每次发送后都需要等待返回结果后才能继续下一次发送,这很显然会影响redis客户端的处理性能。

因此,hiredis提供了redisAppendCommand函数,来实现流水线命令发送方案:当我们需要向redis服务端发送多条命令时,可以先调用若干次redisAppendCommand函数,之后,再调用redisGetReply函数来接收(并解析)redis服务器返回的响应消息。

redisAppendCommand函数实现流水线命令方案的示例如下:

redisReply *reply;

redisAppendCommand(context,"SET foo bar");
redisAppendCommand(context,"GET foo");

redisGetReply(context,&reply); // SET命令的返回
freeReplyObject(reply);
redisGetReply(context,&reply); // GET命令的返回
freeReplyObject(reply);

注意:redisAppendCommand函数的调用次数必须与redisGetReply函数的调用次数一致,否则会出现获取到的redis服务端返回的处理结果跟预期不一致的情况。示例如下:

// 测试redisGetReply与redisAppendCommand 调用次数不一致的情况
redisAppendCommand(conn, "get foo");

reply = redisCommand(conn, "set fooo barr");
// 此处本想获取set fooo barr的返回信息,却获取了get foo的返回信息
printf("set info: %s\n", reply->str);

上述代码的printf函数打印出来的返回值是“get foo”命令的返回值,因为调用redisAppendCommand函数后,没有与之对应的redisGetReply函数函数调用,后面调用“redisCommand(conn, "set fooo barr");”时,该函数的子步骤redisGetReply函数会获取input buffer中第一个返回值,即“redisAppendCommand(conn, "get foo");”的返回值。

2. 流水线客户端示例

2.1 示例代码

redis流水线客户端的示例代码如下:

#include <iostream>
#include "hiredis/hiredis.h"

using namespace std;

int main()
{
    // 建立redis连接
    redisContext *c = redisConnect("192.168.213.128", 6379);
    if ((c == NULL) || (c->err))
    {
        if (c)
        {
            cout << "Error: " << c->errstr << endl;
            // 释放redis连接
            redisFree(c);
            return -1;
        }
        else
        {
            cout << "Can't allocate redis context." << endl;
            return -1;
        }
    }
    else
    {
        cout << "Connected to Redis." << endl;
    }

    redisReply *reply;

    // 发送添加数据命令、查询数据命令
    redisAppendCommand(c, "SET foo bar");
    redisAppendCommand(c, "GET foo");

    // 获取添加数据命令的返回结果
    redisGetReply(c, (void**)&reply);
    cout << "SET reply is: " << reply->str << endl;
    freeReplyObject(reply);
    // 获取查询数据命令的返回结果
    redisGetReply(c, (void**)&reply);
    cout << "GET reply is: " << reply->str << endl;
    freeReplyObject(reply);

    // 释放redis连接
    redisFree(c);
    
    return 0;
}

2.2 编译redis流水线客户端

执行下面的命令编译上述代码,生成redis客户端:

g++ -o hiredis_syncAPI_pipelining hiredis_syncAPI_pipelining.cpp -lhiredis

2.3 测试redis流水线客户端

2.3.1 启动redis服务器

我们在主机(IP地址为192.168.213.133)上打开redis服务器,该redis服务器监听对于192.168.213.133的连接,如下:

[root@node1 /opt/liitdar/hiredis]# redis-server /etc/redis.conf

查看redis-server是否在监听192.168.213.133:

[root@node1 /opt/liitdar/hiredis_for_demo]# netstat -anpot |grep 192.168.213.128
tcp        0      0 192.168.213.128:6379    0.0.0.0:*               LISTEN      11606/redis-server   off (0.00/0/0)

上面的结果显示redis-server已经在监听192.168.213.133地址了。

2.3.2 启动redis流水线客户端

在另外一台主机(IP地址为192.168.213.131)上运行前面编译生成的redis流水线客户端“hiredis_syncAPI_pipelining”,如下:

./hiredis_syncAPI_pipelining

2.3.3 观察测试结果

正常情况下,我们编写的redis流水线客户端能够连接到redis服务器,并执行指定的redis流水线命令,如下:


如果运行redis流水线客户端的终端中出现上述信息,说明我们的编写的redis流水线客户端正常运行了。


猜你喜欢

转载自blog.csdn.net/liitdar/article/details/80314623