请求/响应协议和RTT
Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。
这意味着通常情况下一个请求会遵循以下步骤:
- 客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应。
- 服务端处理命令,并将结果返回给客户端。
因此,例如下面是4个命令序列执行情况:
Client: INCR X
Server: 1
Client: INCR X
Server: 2
Client: INCR X
Server: 3
Client: INCR X
Server: 4
客户端和服务器通过网络进行连接。这个连接可以很快(loopback接口)或很慢(建立了一个多次跳转的网络连接)。无论网络延如何延时,数据包总是能从客户端到达服务器,并从服务器返回数据回复客户端。这个时间被称之为 RTT (Round Trip Time - 往返时间).
redis客户端执行一条命令分4个过程:
当客户端需要在一个批处理中执行多次请求时很容易看到这是如何影响性能的(例如添加许多元素到同一个list,或者用很多Keys填充数据库)。例如,如果RTT时间是250毫秒(在一个很慢的连接下),即使服务器每秒能处理100k的请求数,我们每秒最多也只能处理4个请求,严重制约Redis的性能。
幸运的是有一种方法可以改善这种情况。它就是 Pipeline。
Redis Pipeline介绍
Redis管道技术可以在服务器未响应时,客户端可以继续向服务器发送请求,并最终一次性读取所有服务器的响应。
简单来说就是管道中的命令是没有关系的,它们只是像管道一样流水发给服务器,而不是串行执行。
这样可以最大限度的利用Redis的高性能并节省不必要的网络IO开销。
管道(pipelining),是一种几十年来广泛使用的技术。例如许多POP3协议已经实现支持这个功能,大大加快了从服务器下载新邮件的过程。
Redis很早就支持管道(pipelining)技术,因此无论你运行的是什么版本,你都可以使用管道(pipelining)操作Redis。
注意 :使用管道发送命令时,服务器将被迫回复一个队列答复,占用很多内存。所以, 管道命令数目不要过大,如果你需要发送大量的命令,最好是把他们按照合理数量分批次的处理,例如10K的命令,读回复,然后再发送另一个10k的命令,等等。这样速度几乎是相同的,但是在回复这10k命令队列需要非常大量的内存用来组织返回数据内容。
注意 :另外要说的就是,在一个管道中执行的N个命令,是不具有原子性的,不会像 MySQL 事务一样要么同时成功,要么同时失败。
Redis Pipeline管道使用
在很多场景下,我们要完成一个业务,可能会对redis做连续的多个操作,譬如库存减一、订单加一、余额扣减等等,这有很多个步骤是需要依次连续执行的。
测试通过Pipeline命令保存、获取10000条数据
<?php
$redis = new Redis();
$redis->connect('localhost', 6379);
$t1 = microtime(true);
$pipe = $redis->multi(Redis::PIPELINE);
for ($i = 0; $i < 10000; $i++) {
// $redis->set("key:$i", $i);
// $pipe->get("key::$i");
$pipe->set("key:$i", $i);
// $redis->hset('test1', "key:$i", $i);
// $pipe->hset('test1', "key:$i", $i);
// $pipe->hget('test1', "key:$i");
}
$replies = $pipe->exec();
$t2 = microtime(true);
echo '耗时' . round($t2 - $t1, 3) . '秒<br>';
echo 'Now memory_get_usage: ' . memory_get_usage() . '<br />';
# 测试发现
# 管道对写入的性能提升比较巨大,但是对于读取,几乎没什么影响
# 删除当前数据库中的所有Key
flushdb
# 删除所有数据库中的key
flushall
# Redis 中有删除单个 Key 的指令 DEL,但好像没有批量删除 Key 的指令,不过我们可以借助 Linux 的 xargs 指令来完成这个动作
redis-cli -h ip -p port keys "*"| xargs redis-cli -h ip -p port del
Redis连接池问题
虽然管道能够解决并发时写入性能的问题,当有多个业务逻辑都在对Redis进行大批量操作时,会造成Redis连接池的连接长时间得不到释放,因此可能会使连接池中的连接很快就被前几个线程所占有,迟迟得不到释放,并发量较高时,大量线程得不到连接池中的连接而造成大量线程等待超时。
总结
使用管道不仅仅是为了降低RTT以减少延迟成本,实际上使用管道也能大大提高Redis服务器中每秒可执行的总操作量。这是因为,在不使用管道的情况下, 尽管操作单个命令开起来十分简单,但实际上这种频繁的I/O操作造成的消耗是巨大的,这涉及到系统读写的调用, 这意味着从用户域到内核域。上下文切换会对速度产生极大的损耗。正确使用pipeline对性能的提升十分明显。