/ Redis

解决 Redis 110 Connection timed out 问题记录

背景

之前在某公司的时候,用 php-redis 扩展时,服务器会报错 110,"Connection timed out" after 0 ms,不可思议吧,0ms超时?当时一直以为是扩展层面的 bug。

现在直接用的 predis,同样的,总是能遇到 Connection timed out 这个报错。这次总不能又是 php 代码有问题了吧 :)

调试

调试代码的时候确认错误不是扩展返回的,110 代表了不是连接前的错误,而是 redis 服务器直接返回的错误。

这样问题就很明确了,直接从 redis 服务器入手。

1,首先 redis 是同步IO的,实例是单线程的,因此任何慢操作都可能会阻塞其它请求,而导致超时,可以参考的方向有 特大key导致的超时,rdb时同步写导致的超时(设置 no-appendfsync-on-rewrite yes 来解决)等等。但是这些都不会返回0ms的超时,而是在我们客户端设定的连接超时时间后超时。因此,这些尝试是有意义的,但是对解决瞬间超时毫无意义。

2,redis 服务器的 timeout 默认是 0,也不应该有问题才对(吧?) :(

3,然后检查最大连接数和当前连接数,没有一点问题,当前连接数远远小于 maxclients配置。

4,redis-cli CLIENT LIST,发现一堆 idle 时间超级久的连接,难道是有 idle 时再请求偶尔就会报超时?(idle连接是怎么造成的呢?长连接却没有复用?php-redis 扩展bug ?predis bug?还是其它异常崩溃导致客户端连接已经释放,但未通知到服务器?)虽然有 idle 连接,但是还是未达到 maxclients 数,不应该连接失败才对,而且超过连接数的错误日志也不是这个。

5,阅读了 redis 官方文档的一篇关于超时的文章,https://redis.io/topics/clients#client-timeouts,发现大坑来了,他的 timeout 指的是连接空闲多久被关闭,而不是熟知的连接超时或者执行超时。这样,结合4,猜测,如果超过了一定空闲连接数,客户端如果没有复用连接而还在建立新连接,redis 服务器再次接受新请求时会一定程度上拒绝。

所以,解决起来超级简单。更改配置文件,以便下次重启 redis 服务器会用到;同时用命令行动态更新配置,实时生效。具体超时时间根据业务需要做调整,其实一般不需要很大,只要不idle,就不会超时的。

vim /path/to/redis.conf
timeout 600
tcp-keepalive 60

$redis-cli
CONFIG SET timeout 600
CONFIG SET tcp-keepalive 60

后记

不得不说,虽然 redis 真的很好用,但 redis 的这个默认配置真的太坑了,不是每个人都有时间和精力把所有 redis 资料阅读一遍的。

看看人家 mysql,各种超时很详细,https://stackoverflow.com/a/4284212/5049871