背景
由于对redis缓存中数据有批量操作,例如预热缓存数据,或者在列表页批量去获取缓存数据,在使用了multi批量提交事务后,发现redis压力高居不下,而使用了pipeline之后压力回落了平常,也因为这个案例,特在此写个分析与笔记。
multi
简介
标记一个事务块的开始。
事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 EXEC 命令原子性(atomic)地执行。
实现原理
我用php扩展调起redis服务,执行,代码如下:
$redis = new redis();
$redis->connect('127.0.0.1',6379);
$handle = $redis->multi();
$handle->incr('a');
$handle->incr('b');
$handle->exec();
为了查看这期间具体的连接过程,用wireshark监听回环地址端口6379,抓包看到如下图:
redis客户端与服务端建立连接后,multi标记事务开始,之后每次执行,服务端返回queued队列标志。查看redis源码src/multi.c文件:
void queueMultiCommand(client *c) {
multiCmd *mc;
int j;
c->mstate.commands = zrealloc(c->mstate.commands,
sizeof(multiCmd)*(c->mstate.count+1));
mc = c->mstate.commands+c->mstate.count;
mc->cmd = c->cmd;
mc->argc = c->argc;
mc->argv = zmalloc(sizeof(robj*)*c->argc);
memcpy(mc->argv,c->argv,sizeof(robj*)*c->argc);
for (j = 0; j < c->argc; j++)
incrRefCount(mc->argv[j]);
c->mstate.count++;
}
上述源码中可以看出redis服务端每次会把事务块中的命令保存到内存中,上述简介已经解释过最后通过exec命令执行,由下图可知,最后redis服务端一次性返回所有命令返回结果。
pipeline
简介
客户端将执行的命令写入到缓冲中,最后由exec命令一次性发送给redis执行返回。
实现原理
同样,用相关代码调用redis抓包;
$redis = new redis();
$redis->connect('127.0.0.1',6379);
$handle = $redis->pipeline();
$handle->incr('a');
$handle->incr('b');
$handle->exec();
继续用wireshark抓包,如下图所示:
- pipeline客户端发送请求包
- pipeline服务端返回请求包
这上面的图片简要分析一下,pipeline管道操作是需要客户端与服务端的支持,客户端将命令写入缓冲,最后再通过exec命令发送给服务端,服务端通过命令拆分,逐个执行返回结果。
两者的区别
由上面的请求也可以看出了两者最明显的区别是客户端发送请求的方式不一样,具体相关区别如下:
-
pipeline选择客户端缓冲,multi选择服务端缓冲;
-
请求次数的不一致,multi需要每个命令都发送一次给服务端,pipeline最后一次性发送给服务端,请求次数相对于multi减少
-
multi/exec可以保证原子性,而pipeline不保证原子性