这周遇到了三个值得说一说的问题,每一个问题都带来了不小的麻烦,作为教训再来回顾一下。
第一个问题,在某次上线之后,突然出现了很多 AttrubuteError
和 TypeError
,这些错误看起来都是不应该出现的,究其原因,是从 memcached 中取回的数据和预期的不同。
在代码中对容易出问题的 key 做跟踪,却找不到有任何代码对这些 key 做过 set 操作,但 memcached 中的数据的确是错的,由此基本可以推定并不是有代码 set 了错误的 key。在定位问题的过程中发现,重启应用时会有很大的机率触发 memcached 数据错乱,以此为线索, hongqn 最终定位到了导致此问题的原因: Quixote 在 fork 子进程之前 import 了部分应用代码,某个 changset 中的改动又使得部分代码在被 import 时就创建了到 memcached 的连接,多个子进程使用同一个 memcached 连接,数据包交织导致了 memcached 通讯混乱。
这里主要有两个问题: Quixote 在 fork 之前调用了应用代码、memcached 客户端库非线程安全;解决这两个问题中的任意一个都可以避免数据错乱的问题发生,第二个问题也可以通过在 fork 之后重新建立连接来作为 workaround 手段而绕过其线程安全的问题。
第二个问题危害就更大了,几乎所有的 MySQL 实例都在轮番 crash,基本上都是同一个原因:
Some pointers may be invalid and cause the dump to abort.
Query (0x7f7fb07f52f0): is an invalid pointer
Connection ID (thread ID): 170604
Status: NOT_KILLED
这个原因看起来和存储没有直接的联系,但由于最初 crash 的实例都部署在 SSD 上,还是不自觉地把 SSD 作为了首要的怀疑对象,然而还没找到 SSD 本身的任何问题,部署在硬盘上的实例也开始 crash 了,接下来就是一系列的猜测和快速推翻猜测:
- SSD 可靠性有问题?和 SSD 没有任何关系的数据库也 crash 了
- 服务器内存错误?多台物理服务器上的很多个不同的实例多次 crash,这么多服务器的内存在短时间内同时出问题的概率还是挺小的
- MySQL bug?这个版本的 MySQL 已经在线上稳定运行了 1 年以上
- 特殊的查询导致 crash? 1. 使用一组抓取的读查询对数据库热身,导致其 crash,重启之后用同一组查询再次热身,不能重现,但同一物理机上的另外一个实例 crash 了 1. 如果说是特殊的写操作导致的 crash,但是 master 不 crash slave crash 的案例存在,master crash slave 不 crash 的案例也存在
- 和压力有关?压力非常小,并且是没有查询的 slave 也会 crash
- 和 MySQL 的运行时间有关?运行了一年多的节点和每周都会重启一次的节点都会 crash
- …
之后 Redis 和 BeansDB 也开始有实例 crash,才最终停止了对 MySQL 本身的追查,把目光转向了系统,在最近的变更中找到了可疑的目标:对这些服务器新增加的 RAID 健康状态监控。
在 这里 确认了 megaraid_sas 驱动的 bug,并且特定版本的 MegaCli (version 8.01.06) 工具会触发它。一句话概括这个bug:它可能向任意内存地址写数据! 可以升级 MegaCli 到比 8.01.06 更新的版本,这样可以不触发这个 bug,或者给它打 patch 来修正 bug。
第三个问题也相当严重,某应用上线之后内存使用突涨,其内存占用增长的特殊性导致目前的内存监控失效,最终一组服务器全部宕机,使用 strace 跟踪到了奇怪的问题,在查明根本原因之前先回滚了相关的代码。
这个事故暴露了产品开发和服务监控的问题:新上线的功能务必要能在工作时间生效,比如这次上线的功能生效的条件是偶数日期,也刚好今天的日期是偶数,这个问题及时地暴露了出来,想像一下如果是在奇数日生效而明天又是周六,过了凌晨一组服务器全部宕机,那又会是多么严重的问题。而对于监控来说,依赖应用的协作进行的监控是不靠谱的,而外部监控又很难设定普适的异常条件,这也是一个需要花更多精力来做好的事情。
更正:对于第一个问题,虽然在多线程环境下共享连接也会有同样的问题,但就这次的事故而言,和线程安全无关,之前的表述有误。