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的性能要更好一点,这与其处理的简化和高效有关。