REDIS+PHP的并发连接数过低

目前碰到一个棘手的问题,用REDIS来做缓存的时候,发现在并发情况下,REDIS 对于短连接的处理能力很差。
测试环境:CENTOS 6.2 PHP 5.4 PHPREDIS2.0插件 nginx/1.1.16
DELL 2950 八核八G REDIS 的并发数的配置文件部分已经注释掉了。硬盘保存功能也关闭了。
内网同样配置的机器用webbench 发起攻击,并发调用同样的PHP

$sUserID = 'abcasdasda';
$sKey = 'ProvinceName';
$redis = new Redis();
$nError = $redis->connect('127.0.0.1', 6379);
if ($nError != 1)
    echo -9998;
$b = $redis->hget($sUserID, $sKey);
if(empty($b))
	echo -9999;
else	
$redis->incr('newCount');
$redis->close();

在浏览器中测试成功后,运行一次PHP 。newcount 加一。

运行webbench 经过N轮各种参数的测试,每秒newcount 只能增加470个左右。
同样环境下运行插入MYSQL 的PHP

<?php
$con = mysql_connect("localhost","root","abc123");
if (!$con) {
die('Could not connect: ' . mysql_error());
echo -9999;//......
return -9999;
}
else {
mysql_select_db("test", $con);

if(isset($_SERVER["HTTP_X_FORWARDED_FOR"]))
{
        $realip = $_SERVER["HTTP_X_FORWARDED_FOR"];  
    }
    elseif (isset($_SERVER["HTTP_CLIENT_IP"]))
{
        $realip = $_SERVER["HTTP_CLIENT_IP"];  
    }
    else
{
        $realip = $_SERVER["REMOTE_ADDR"];
    }
$URL=$_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
$query="Insert into url_log (time,url,u_return,client_ip) values ( now(), '".$URL."', '".$strReturn."','".$realip."')";
if(mysql_query($query))
{
        echo "DB Insert OK";//......
    }
    else
{
        echo "DB Insert Error";//......
    }
}
mysql_close($con);
?>

每秒插入的记录数,稳定在2000以上,远远比REDIS 要完成的操作多。

想请问大家,如何能提高REDIS 的并发能力,我们的应用场景是短链接为主,每个链接处理的问题都比较少。
希望能利用REDIS 作为内存数据库 实时高速读写 。

希望高人给出建议 谢谢!

阅读 28.6k
7 个回答

redis号称并发可以达到10W级别,但那是针对C API而言,如果用PHP API效果就差了很多,一般推荐的解决方案是采用nginx lua的扩展来实现nginx对redis的操作,并不穿透到PHP层,PHP对redis的并发也只能到这个层次了

我在自己的双核cpu的测试机上,测试的结果也跟你大同小异,但是我要说的是大家都忽略了一个事实,那就是redis是单进程单线程的,而mysql是多进程并发的。也就是说redis只能利用到计算机的一个核心,而mysql可以用到所有核心。在redis的官方benchmark页面(http://redis.io/topics/benchmarks)有这样一句话

Redis is a single-threaded server. It is not designed to benefit from multiple CPU cores. People are supposed to launch several Redis instances to scale out on several cores if needed. It is not really fair to compare one single Redis instance to a multi-threaded data store.

你的测试服务器有8个核心,那么mysql测试出来的数据是2000左右,平均到单核就是250左右,redis显然比它高一些,虽然说不能这么简单的换算,但是还是能说明一部分问题。

还有就是缓存发挥最大作用的地方应该是随机读写,在这一部分它跟传统数据库拉开了性能差距。

PHP真是躺着也中枪,如果用对了,phpredis的效能实际并不比所谓行业标准差太多,请放心。

首先,MySQL并不慢,可以把MySQL想象成非常快的文件读写服务。insert语句只不过是顺序地在文件尾部附加内容,更何况还有执行优化(并不是每条insert都flush到磁盘)。

其次,你的测试代码不公平,由于没有用Redis的pipeline(意味着每个操作都要取得返回值),你先调了一个hGet,这就已经是网络IO的一个来回了;然后才incr操作(又一个来回)。相当于你在MySQL里先select一下,再insert。

最后,虽然影响因子不大,但Redis的incr应该和MySQL的update比较吧……

题外话:你单个脚本需要做的redis读写比较少,并不意味着你的redis就得短连接,相反更应该用长连接。

EDIT:phpredis本身就支持长连接:https://github.com/nicolasff/phpredis...

这个问题很多使用PHP + Redis的人都会遇到。为了减少Redis的连接开销,最简单直接的方法就是通过proxy收敛连接数据。

目前比较靠谱的是应该算Twitter开源的twemproxy

twemproxy中文介绍:http://blog.nosqlfan.com/html/4147.html

对于nginx + lua操作Redis的方案,逻辑上可以指定连接池大小,连接池中的连接是持久连接,建立后可以重用,当并发数超过连接池中的连接数,lua会创建短连接来支持。但我们线上出现过问题,在长时间运行后,连接数会超过连接池设置的大小且不释放,直接导致Redis连接数超标拒绝连接。

新手上路,请多包涵

如果短连接不释放的话,可以通过系统配置来回收socket连接。
sysctl net.ipv4.tcp_timestamps=1
sysctl net.ipv4.tcp_tw_recycle=1

或者
vim /etc/sysctl.conf
net.ipv4.tcp_timestamps=1
net.ipv4.tcp_tw_recycle=1
/sbin/sysctl -p //使之生效
第一种方法重启后失效

主要问题是在tcp连接消耗上,可以采用长连接来解决这个问题,pconnect()

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏