在生产环境中艰难学习Redis

trivago是一家使用Redis作为旅馆搜索应用的网站,酒店搜索的前端(Web)是使用PHP的Symfony框架构建的,后端部分使用Java。

在2014年开始流量翻了一番以后,发现40%传入请求发生HTTP 500:内部服务器错误,主要是PHP和Redis连接时的读取错误,Redis服务器已经死了,他们天真以为PHP设置问题,做了以下改进:
1.将PHP连接超时从500ms提高到2.5秒
2.禁用PHP设置 default_socket_timeout
3.禁用主机系统上的SYN Cookie
4.检查Redis和Webservers上文件描述符的数量
5.提升主机系统的mbuffer
6.控制和调整TCP积压大小

结果没有用,然后着重解决了PHP自动关闭连接问题,旧版本中会有连接失败和内存泄露问题。

他们决定做A/B测试,问题依旧,这个过程他们了解到:

Redis是单线程的,每个命令可能会堵塞其他命令执行,不能在大量读取同时经常进行修改,写操作会堵塞读操作。

他们激活了 Watchdog,发现Redis服务器崩溃了,检查了Redis日志,发现Redis每隔几分钟将数据保存到磁盘中:


[20398] 22 May 09:20:55.351 * 10000 changes in 60 seconds. Saving...
[20398] 22 May 09:20:55.759 * Background saving started by pid 41941
[41941] 22 May 09:22:48.197 * DB saved on disk
[20398] 22 May 09:22:49.321 * Background saving terminated with success
[20398] 22 May 09:25:23.299 * 10000 changes in 60 seconds. Saving...
[20398] 22 May 09:25:23.644 * Background saving started by pid 42027
[20398] 22 May 09:26:50.646 # Accepting client connection: accept: Software caused connection abort
[20398] 22 May 09:26:50.900 # Accepting client connection: accept: Software caused connection abort
...

原来Redis正在分发后台进程,需要复制页面表。所以,如果你有一个大的Redis实例并有很多键key,这就花费时间持久化。

他们停用了Redis这种快照持久化,用于不需要持久性。这减少了read error on connection到30%以上。

如果您的Redis实例拥有大量流量,并且您的应用程序正在根据请求执行写入操作,则您将进行更多key的修改。这导致更多Redis的BGSAVE触发,并可能导致大量连接被拒绝。

他们还发现,因为一个团队每15分钟运行一次cronjob,是将MySQL数据转存到共享的Redis实例中,由于Redis的单线程,共享的Redis实例每15分钟被阻塞几秒钟。

请注意在团队和数据上下文之间共享的Redis实例。由于不同的用例和长时间运行的命令,它们可能会相互阻塞。记住:Redis是单线程的。

他们发现了以上几个原因,将连接和超时错误的数量减少了一个数量级。一切都很顺利,Redis的设置很健康。时间过去很快,当团队在应用中实施了新功能后,流量持续增长。流量增长速度很快,连接和命令超时错误又出现了。

幸运的是看到了一种模式,它每5分钟定期发生一次。根据我们上次调查方法,测量了基本延迟,启用了看门狗并阅读了SLOWLOG文档。

他们确定了一个cronjob,它触发了一个大Redis实例的KEYS *命令,在大Redis数据库中,KEYS *命令会导致长时间堵塞Redis实例,KEYS命令只能用于调试目的。

KEYS命令的时间复杂度定义为:
O(N),N是数据库中的密钥数量.

他们检查了所有Redis命令,优化了超过40%的执行命令,从而减少了与Redis进行通信的时间。大大提升了响应时间。

他们在接下来的几个月中接受了越来越多的流量的挑战,并以多种方式进一步优化了应用和栈。一些目标是解决每个请求的内存消耗,优化数据库查询(慢查询日志),调整缓存层并向数据中心添加更多硬件(Web服务器)。特别是最后一个变化,添加更多的Web服务器到技术栈,却制造了另一个挑战。

每个请求应用程序会创建第三方连接,再次执行一个或两个命令后并断开连接。50%最多75%的命令用于连接处理。记住Redis是单线程的。如果您有很多客户端尝试连续连接到您的Redis实例,这将让您的实例忙于连接处理,而不是执行运行业务逻辑的命令。这可能导致您的Redis实例减速/阻塞。

提示
考虑在应用程序和Redis之间增加第三方组件代理。如果您具有很高的连接/命令比率,或者Redis被用作许多不同团队的平台,这个代理可能是非常有益的。它可以减少连接开销,或者用作昂贵和不需要的命令的防火墙。

解决方案是使用twitter的tweetproxy,twemproxy是为这个用例专门创建的。在每个Web服务器上安装此代理,twemproxy持有与Redis实例的持久连接。您的应用程序只能连接到本地这个代理即可,这应该会更快,因为它通过网络连接到unix域套接字而不是外部的服务。它也支持memcached。

twemproxy的另一个优点是能够阻止昂贵的命令。所以它可以阻止像KEYS这样的命令,还有像FLUSHALL和FLUSHDB这样的危险操作。

缺点:每个新的Redis版本都需要相应的twecproxy支持。

twemproxy的部署取得了巨大的成功。消除了剩下的所有超时和连接错误(不购买新硬件)。

解决了连接和命令超时的所有已知原因。他们平台的增长和流量仍然持续,因此继续优化Redis的使用。

使用一致性Hash分割数据到几台Redis上,twemproxy支持,,降低了每台机器流量负载,提高了可靠性。

Learn redis the hard way (in production) · trivago

Redis是单线程的,但却可以开启多个Redis实例,然后基于用户ID(或者session ID)做一致性哈希Sharding分布式化使用,连接池也有必要加上