redis批处理操作:基于hiredis实现

redis中读取字符串最简单的方式是get key,对于同一时间读取多个key的情况,如果是循环读取,那么代价会很大,特别是使用云服务或者远程网络连接的额定带宽下的redis。此时,最常用的是通过批batch处理的方法,两种加速的方法:(mset、mget)和pipeline。首先看下如何通过hiredis接口调用这几个指令,关于hiredis其本功能介绍,可以参考之前的文章:hiredis接口分析 hiredis读写二进制 异步接口

首先看下mset指令为:

mset key1 value1 key2 value2 ........

依次设定各个key以及对应的值,该指令比较容易实现:

int mSetCmd(redisContext* rc, vector<std::string> keys, vector<std::string> values)
{
    if(keys.size()!=values.size())
    return -1;
    std::string strCmd = "MSET";
    for(int i = 0; i < keys.size(); i++)
    {
        strCmd += " "+keys[i]+" "+values[i];
    }
    redisReply *reply=(redisReply*)redisCommand(rc, strCmd.c_str());
    if (reply == NULL) 
        return -1;
    return 0;  
}

这里实现很简单,通过vector传送多个键值以及对应的值。mget指令为:

mget key1 key2 .....

依次请求各个key的值,会按照key值依次按行返回value,实现代码如下:

int mGetCmd(redisContext* rc,vector<std::string> keys,vector<std::string>& values)
{
    std::string strCmd = "MGET";
    for(int i=0;i<keys.size();i++)
    {
        strCmd += " "+keys[i];
    }
    redisReply *reply=(redisReply*)redisCommand(rc, strCmd.c_str());
    if(reply->type != REDIS_REPLY_ARRAY)//返回必须是数列的形式
    {
    printf("mget failed!");
    return -1;
    } 
    for (int i = 0; i < reply->elements; ++i) {  
    redisReply* childReply = reply->element[i]; 
    if (childReply->type == REDIS_REPLY_STRING)
    {
        std::string strTmp(childReply->str);
        values.push_back(strTmp);       
    }
    }
    return 0;     
}

与之前的hiredis指令不同,这里返回的不是连续的一块缓存,而是数列的形式,每一个reply的数组成员element[],每一个值对应一个键值对。

下面我们来看pipeline,其原理很简单,pipeline就是把所有的命令一次发过去,避免频繁的发送、接收带来的网络开销,redis在打包接收到一堆命令后,依次执行,然后把结果再打包返回给客户端,实现代码如下:

int pipeLineCmd(redisContext* rc,std::vector<std::string> & pipeLineCmd, std::vector<std::string> &pipeLineReq, std::vector<bool> &pipeLineReqStatus)
{
    for(int i=0;i<pipeLineCmd.size();i++)
    {
        redisAppendCommand(rc, pipeLineCmd[i].c_str());
    }
    for (int i = 0; i < pipeLineCmd.size();i++)
    {
        bool status = false;
        std::string resp_str = "";
        redisReply *reply = 0;
        if(redisGetReply(rc, (void **)&reply) == REDIS_OK
                && reply != NULL
                && reply->type == REDIS_REPLY_STRING)
        {
            status = true;
            resp_str = reply->str;
        }
        //free
        freeReplyObject(reply);
        pipeLineReqStatus.push_back(status);
        pipeLineReq.push_back(resp_str);
    }
}

在之前的分析文章中可以知道,只是将待发送的命令写入到上下文对象的输出缓冲区中,即将命令拷贝到context的obuf里,直到调用后面的redisGetReply命令才会批量将缓冲区中的命令写出到Redis服务器。这样可以有效的减少客户端与服务器之间的同步等候时间,以及网络IO引起的延迟。redisGetReply依次执行如下步骤:1.从reply队列里面返回最前面的reply,如果队列为空,继续后面的操作;2.如果发现obuf中有数据没有发送,那么发送cmd;3.等待回包(server会把cmd中所有的处理做完,然后回包,appand过几次,server就会返回几个reply);收到回包后,redisGetReply里面解析回包为reply(可能为多个,具体数量跟appand次数相同)。

对于两种操作的性能可以参考:https://github.com/redis/redis-rb/tree/master/benchmarking

对10k个记录进行读,其性能测试结果如下

      user system total real
get 1.46 0.580226 2.050099 ( 12.701514)
pipeline with 10 each  0.74 0.300464 1.041347 ( 2.975966)
pipeline with 100 each  0.54 0.219881 0.766023 ( 1.368800)
pipeline with 1000 each  0.51 0.200081 0.716371 ( 1.403288)
pipeline with 10000 each  0.47 0.200489 0.672237 ( 1.201756)
mget with 10 each  0.22 0.061788 0.289068 ( 2.317538)
mget with 100 each  0.08 0.008353 0.092111 ( 0.240111)
mget with 1000 each  0.06 0.001894 0.063780 ( 0.085815)
mget with 10000 each  0.05 0.001459 0.057561 ( 0.070013)

从测试结果来看,mget的性能要更好一点,这与其处理的简化和高效有关。

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值