Twemproxy 可以说是最古老的 Redis 代理软件了,一般来说,引入代理后性能会比没有引入代理时低一些,毕竟代理会导致一些额外的性能损耗,可是 Twemproxy 却会提升性能, 这主要得益于它的 Pipelining 功能可以实现打包请求,简单点说:当代理收到多个并发请求时,它会把这些请求打包成一个请求发送给后端服务器,从而减少不必要的 RTT。关于 Pipelining 本文不做过多讨论,实际上我想说的是它的另一个功能:连接池!下面看看如何通过 Twemproxy 提升 PHP/Redis 的性能。
众所周知,PHP 的运行方式很难实现真正的连接池,不过通过本地的 Unix Domain Socket,我们可以绕开连接池,实现曲线救国,具体介绍大家可以参考我以前写的旧文:史上最LOW的PHP连接池解决方案,在那篇文章里,我借助 Nginx 的 Stream 模块,实现了一个 Redis 代理,通过它来实现连接池功能,然后 PHP 通过本地的 Unix Domain Socket 来连接 Redis 代理,从而达到连接池的效果,不过在本文里,我不再自己编写 Redis 代理,而是直接使用 Twemproxy,让我们看看效果如何。
为了让 PHP 能够通过本地的 Unix Domain Socket 来连接 Redis 代理,PHP 和 Redis 代理必须安装在同一台服务器上,后端真正的 Redis 服务器通常不会和 PHP 在同一台服务器上,本文使用 127.0.0.1 是出于方便的考虑,假设你已经安装好了,让我们看看配置文件 /etc/redis.yml:
instance:
listen: /var/run/nutcracker.sock
redis: true
redis_auth: ...
preconnect: true
servers:
- 127.0.0.1:6379:1
让我们把服务运行起来,So easy:
shell> /path/to/nutcracker -c /etc/redis.yml
下面让我们压测看看性能怎么样,测试脚本 test.php 如下:
<?php
$redis = new Redis;
if (empty($_GET['twemproxy'])) {
$redis->connect('127.0.0.1', 6379);
} else {
$redis->connect('/var/run/nutcracker.sock');
}
$redis->auth('...');
$redis->set('foo', 'bar');
echo $redis->get('foo');
?>
通过 ab 模拟一个高并发的场景,压测看看性能有没有提升:
shell> ab -k -n 10000 -c 100 "http://path/test.php?twemproxy=0"
shell> ab -k -n 10000 -c 100 "http://path/test.php?twemproxy=1"
一开始,结果让人非常沮丧,使用 Twemproxy,没有带来任何性能上的提升。这是为什么呢?原来是因为 Twemproxy 是单线程的,缺省情况下只能使用一个 CPU,这个问题好解决,我们只要按 CPU 个数启动多个进程(假设有 4 个 CPU)即可。
首先,创建多个配置文件 /etc/redis1-4.yml:
instance:
listen: /var/run/nutcracker[1-4].sock
redis: true
redis_auth: ...
preconnect: true
servers:
- 127.0.0.1:6379:1
然后,启动多个 Twemproxy 进程:
shell> /path/to/nutcracker -c /etc/redis1.yml -m 512 -s 11111 -d
shell> /path/to/nutcracker -c /etc/redis2.yml -m 512 -s 22222 -d
shell> /path/to/nutcracker -c /etc/redis3.yml -m 512 -s 33333 -d
shell> /path/to/nutcracker -c /etc/redis4.yml -m 512 -s 44444 -d
相应的,测试脚本 test.php 也要做出适当的调整:
<?php
$redis->connect('/var/run/nutcracker' . rand(1, 4) . '.sock');
?>
再执行压测,结果发现使用 Twemproxy 后,性能整整提升了 100%!如果你在压测过程中通过 top 命令观察 CPU 情况的时候,会发现所有 CPU 都被吃满了,基本上没有 idle,此外,如果你在压测过程中通过 MONITOR 观察 Redis 执行的命令,你会观察到 Pipelining 现象,还会观察到 AUTH 被省略了,这些都是性能提升的原因。
说明:为什么 mbuf-size 在高并发的时候推荐设置为 512,参考 recommendation。
此外,有时候系统可能会偏爱 CPU0,此时在运行多个 Twemproxy 进程的时候,可以考虑通过 taskset 命令绕开 CPU0,避免 CPU0 成为性能瓶颈。