携程一次Redis迁移容器后Slowlog“异常”分析

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介:

容器化对于Redis自动化运维效率、资源利用率方面都有巨大提升,携程在对Redis在容器上性能和稳定性进行充分验证后,启动了生产Redis迁移容器化的项目。其中第一批次两台宿主机,第二批次五台宿主机。

本次“异常”是第二批次迁移过程中发现的,排查过程一波三折,最终得出让人吃惊的结论。

希望本次结论能给遇到同样问题的小伙伴以启发,另外本次分析问题的思路对于分析其他疑难杂症也有一定借鉴作用。

一、问题描述

在某次Redis迁移容器后,DBA发来告警邮件,slowlog>500ms,同时在DBA的慢日志查询里可以看到有1800ms左右的日志,如下图1所示:
image

二、分析过程

2.1 什么是Slowlog

在分析问题之前,先简单解释下Redis的slowlog。阅读Redis源码(图2)不难发现,当某次Redis的操作大于配置中slowlog-log-slower-than设置的值时,Redis就会将该值记录到内存中,通过slowlog get可以获取该次slowlog发生的时间和耗时,图1的监控数据也是从此获得。
image
也就是说,slowlog只是单纯的计算Redis执行的耗时时间,与其他因素如网络之类的都没关系。

2.2 矛盾的日志

每次slowlog都是1800+ms并且都随机出现,在第一批次Redis容器化的宿主机上完全没有这种现象,而QPS远小于第一批次迁移的某些集群,按常理很难解释,这时候翻看CAT记录,更加加重了我们的疑惑,见图3:
image

CAT是携程根据开源软件(https://github.com/dianping/cat) 的定制版本,用于客户端记录打点的耗时,从图中可以很清晰的看到,Redis打点的最大值367ms也远小于1800ms,它等于是说下面这张自相矛盾图,见图4:
image
2.3 求助社区

所以说,slowlog问题要么是CAT误报,要么是Redis误报,但Redis使用如此广泛,并且经过询问CAT的维护者说CAT有一定的消息丢弃率,而Redis在官方github issue中并没有发现类似的slowlog情形,因此我们第一感觉是CAT误报,并在官方Redis issue中提问,试图获取社区的帮助。

很快社区有人回复,可能是NUMA架构导致的问题,但也同时表示NUMA导致slowlog高达1800ms很不可思议。关于NUMA的资料网上有很多,这里不再赘述,我们在查阅相关NUMA资料后也发现,NUMA架构导致如此大的slowlog不太可能,因此放弃了这条路径的尝试。

2.4 豁然开朗

看上去每个方面好像都没有问题,而且找不到突破口,排障至此陷入了僵局。

重新阅读Redis源代码,直觉发现gettimeofday()可能有问题,模仿Redis获取slowlog的代码,写了一个简答的死循环,每次Sleep一秒,看看打印出来的差值是否正好1秒多点,如图5所示:
image
图5的程序大概运行了20分钟后,奇迹出现了,gettimeofday果然有问题,下面是上面程序测试时间打印出来的LOG,如图6:
image
图6中标红的时间减去1秒等于1813ms,与slowlog时间如此相近!在容器所在的物理机上也测试一遍,发现有同样的现象,排除因容器导致slowlog,希望的曙光似乎就在眼前了,那么问题又来了:

到底为什么会相差1800ms+呢?

为什么第一批机器没有这种现象呢?

为什么之前跑在物理机上的Redis没有这种现象呢?

带着这三个问题,重新审视系统调用gettimeofday获取当前时间背后的原理,发现一番新天地。

三、系统时钟

系统时钟的实现非常复杂,并且参考资料非常多。

简单来说 我们可以通过命令:
image

来获取当前系统的时钟源,携程的宿主机上都是统一Time Stamp Counter(TSC):80x86微处理器包括一个时钟输入插口,用来接收来自外部振荡器的时钟信号,从奔腾80x86微处理器开始,增加了一个计数器。

随着每增加一个时钟信号而加一,通过rdtsc汇编指令也可以去读TSC寄存器,这样如果CPU的频率是1GHz,TSC寄存器就能提供纳秒级别的计时精度,并且现代CPU通过FLAG constant_tsc来保证即使CPU休眠也不影响TSC的频率。

当选定TSC为时钟源后,gettimeofday获取墙上时钟(wall-clock)正是从TSC寄存器读出来的值转换而来,所谓墙上时钟主要是参照现实世界人们通过墙上时钟获取当前时间,但是用来计时并不准确,可能会被NTP或者管理员修改。

那么问题又来了,宿主机的时间没有被管理员修改,难道是被NTP修改?即使是NTP来同步,每次相差也不该有1800ms这么久,它的意思是说难道宿主机的时钟每次都在变慢然后被NTP拉回到正常时间?我们手工执行了下NTP同步,发现的确是有很大偏差,如图7所示:
image
按常识时钟正常的物理机与NTP服务器时钟差异都在1ms以内,相差1s+绝对有问题,而且还是那个老问题,为什么第一批次的机器上没有问题?

四、内核BUG

两个批次宿主机一样的内核版本,第一批没问题而第二批有问题,差异只可能在硬件上,非常有可能在计时上,翻看内核的commit log终于让我们发现了这样的commit,如图8所示:
image
该commit非常清楚指出,在4.9以后添加了一个宏定义INTEL_FAM6_SKYLAKE_X,但因为搞错了该类型CPU的crystal frequency会导致该类型的CPU每10分钟慢1秒钟。

这时再看看我们的出问题的第二批宿主机xeon bronze 3104正好是skylake-x的服务器,影响4.9-4.13的内核版本,宿主机内核4.10正好中招。

并且NTP每次同步间隔1024秒约慢1700ms,与slowlog异常完全吻合,而第一批次的机器CPU都不是SKYLAKE-X平台的,避开了这个BUG,迁移之前Redis所在的物理机内核是3.10版本,自然也不存在这个问题。至此,终于解开上面三个疑惑。

五、总结

5.1 问题根因

通过上面的分析可以看出,问题根因在于内核4.9-4.13之间skylake-x平台TSC晶振频率的代码BUG,也就是说同时触发这两个因素都会导致系统时钟变慢,叠加上Redis计时使用的gettimeofday会容易被NTP修改导致了本文开头诡异的slowlog“异常”。有问题的宿主机内核升级到4.14版本后,时钟变慢的BUG得到了修复。

5.2 怎么获取时钟

对于应用需要打点记录当前时间的场景,也就是说获取Wall-Clock,可以使用clock_gettime传入CLOCK_REALTIME参数,虽然gettimeofday也可以实现同样的功能,但不建议继续使用,因为在新的POSIX标准中该函数已经被废弃。

对于应用需要记录某个方法耗时的场景,必须使用clock_gettime传入CLOCK_MONOTONIC参数,该参数获得的是自系统开机起单调递增的纳秒级别精度时钟,相比gettimeofday精度提高不少,并且不受NTP等外部服务影响,能准确更准确来统计耗时(Java中对应的是System.nanoTime),也就是说所有使用gettimeofday来统计耗时(Java中是System.currenttimemillis)的做法本质上都是错误的。

原文发布时间为:2018-12-17
本文作者:李剑
本文来自云栖社区合作伙伴“ 数据和云”,了解相关信息可以关注“
OraNews”微信公众号

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
2月前
|
存储 NoSQL Redis
Redis 新版本引入多线程的利弊分析
【10月更文挑战第16天】Redis 新版本引入多线程是一个具有挑战性和机遇的改变。虽然多线程带来了一些潜在的问题和挑战,但也为 Redis 提供了进一步提升性能和扩展能力的可能性。在实际应用中,我们需要根据具体的需求和场景,综合评估多线程的利弊,谨慎地选择和使用 Redis 的新版本。同时,Redis 开发者也需要不断努力,优化和完善多线程机制,以提供更加稳定、高效和可靠的 Redis 服务。
60 1
|
1月前
|
存储 运维 Kubernetes
云端迁移:备份中心助力企业跨云迁移K8s容器服务平台
本文将简要介绍阿里云容器服务ACK的备份中心,并以某科技公司在其实际的迁移过程中遇到具体挑战为例,阐述如何有效地利用备份中心来助力企业的容器服务平台迁移项目。
|
1月前
|
缓存 监控 NoSQL
Redis 缓存穿透的检测方法与分析
【10月更文挑战第23天】通过以上对 Redis 缓存穿透检测方法的深入探讨,我们对如何及时发现和处理这一问题有了更全面的认识。在实际应用中,我们需要综合运用多种检测手段,并结合业务场景和实际情况进行分析,以确保能够准确、及时地检测到缓存穿透现象,并采取有效的措施加以解决。同时,要不断优化和改进检测方法,提高检测的准确性和效率,为系统的稳定运行提供有力保障。
55 5
|
2月前
|
消息中间件 NoSQL Kafka
Flink-10 Flink Java 3分钟上手 Docker容器化部署 JobManager TaskManager Kafka Redis Dockerfile docker-compose
Flink-10 Flink Java 3分钟上手 Docker容器化部署 JobManager TaskManager Kafka Redis Dockerfile docker-compose
70 4
|
3月前
|
Oracle NoSQL 关系型数据库
主流数据库对比:MySQL、PostgreSQL、Oracle和Redis的优缺点分析
主流数据库对比:MySQL、PostgreSQL、Oracle和Redis的优缺点分析
623 2
|
3月前
|
NoSQL 关系型数据库 Redis
mall在linux环境下的部署(基于Docker容器),Docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongo
mall在linux环境下的部署(基于Docker容器),docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongodb、minio详细教程,拉取镜像、运行容器
mall在linux环境下的部署(基于Docker容器),Docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongo
|
2月前
|
NoSQL 安全 Redis
AWS迁移教程,Redis迁移到Elasticache
AWS迁移教程,Redis迁移到Elasticache
|
3月前
|
存储 Prometheus NoSQL
Redis 内存突增时,如何定量分析其内存使用情况
【9月更文挑战第21天】当Redis内存突增时,可采用多种方法分析内存使用情况:1)使用`INFO memory`命令查看详细内存信息;2)借助`redis-cli --bigkeys`和RMA工具定位大键;3)利用Prometheus和Grafana监控内存变化;4)优化数据类型和存储结构;5)检查并调整内存碎片率。通过这些方法,可有效定位并解决内存问题,保障Redis稳定运行。
167 3
|
3月前
|
NoSQL 安全 容灾
阿里云DTS踩坑经验分享系列|Redis迁移、同步
阿里云数据传输服务DTS在帮助用户迁移Redis数据、同步数据时,在某些复杂场景下会出现报错,或者源库与目标库数据不一致的问题,给用户带来困扰。本文介绍了DTS Redis到Redis迁移、同步过程中的典型问题,以帮助用户更好地使用DTS。
249 2
|
4月前
|
缓存 NoSQL 网络协议
【Azure Redis 缓存】Redisson 连接 Azure Redis出现间歇性 java.net.UnknownHostException 异常
【Azure Redis 缓存】Redisson 连接 Azure Redis出现间歇性 java.net.UnknownHostException 异常
115 1