连环触发!MongoDB核心集群雪崩故障背后竟是……( 六 )


binaryNonce[2] = sr->nextInt64;
... ...
}
2) MongoDB内核源码随机数优化
从前面的分析可以看出 , mongos处理客户端新连接sasl认证过程都会通过"/dev/urandom"生成随机数 , 从而引起系统sy% CPU过高 , 我们如何优化随机数算法就是解决本问题的关键 。
继续分析MongoDB内核源码 , 发现使用随机数的地方很多 , 其中有部分随机数通过用户态算法生成 , 因此我们可以采用同样方法 , 在用户态生成随机数 , 用户态随机数生成核心算法如下:
class PseudoRandom {
... ...
uint32_t _x;
uint32_t _y;
uint32_t _z;
uint32_t _w;
}
该算法可以保证产生的数据随机分布 , 该算法原理详见:
也可以查看如下git地址获取算法实现:MongoDB随机数生成算法注释
总结:通过优化sasl认证的随机数生成算法为用户态算法后 , CPU sy% 100%的问题得以解决 , 同时代理性能在短链接场景下有了数倍/数十倍的性能提升 。
三、问题总结及疑问解答
从上面的分析可以看出 , 该故障由多种因素连环触发引起 , 包括客户端配置使用不当、MongoDB服务端内核极端情况异常缺陷、监控不全等 。 总结如下:

  1. 客户端配置不统一 , 同一个集群多个业务接口配置千奇百怪 , 超时配置、链接配置各不相同 , 增加了抓包排查故障的难度 , 超时时间设置太小容易引起反复重连 。
  2. 客户端需要配全所有mongos代理 , 这样当一个代理故障的时候 , 客户端SDK默认会剔除该故障代理节点 , 从而可以保证业务影响最小 , 就不会存在单点问题 。
  3. 同一集群多个业务接口应该使用同一配置中心统一配置 , 避免配置不统一 。
  4. MongoDB内核的新连接随机算法存在严重缺陷 , 在极端情况下引起严重性能抖动 , 甚至业务“雪崩” 。
分析到这里 , 我们可以回答第1章节的6个疑问点了 , 如下:
Q1:为什么突发流量业务会抖动?
答:由于业务是java业务 , 采用链接池方式链接mongos代理 , 当有突发流量的时候 , 链接池会增加链接数来提升访问MongoDB的性能 , 这时候客户端就会新增链接 , 由于客户端众多 , 造成可能瞬间会有大量新连接和mongos建链 。 链接建立成功后开始做sasl认证 , 由于认证的第一步需要生成随机数 , 就需要访问操作系统"/dev/urandom"文件 。 又因为mongos代理模型是默认一个链接一个线程 , 所以会造成瞬间多个线程访问该文件 , 进而引起内核态sy%负载过高 。
Q2:为何mongos代理引起“雪崩” , 流量为何跌零不可用?
答:原因客户端某一时刻可能因为流量突然有增加 , 链接池中链接数不够用 , 于是增加和mongos代理的链接 , 由于是老集群 , 代理还是默认的一个链接一个线程模型 , 这样瞬间就会有大量链接 , 每个链接建立成功后 , 就开始sasl认证 , 认证的第一步服务端需要产生随机数 , mongos服务端通过读取"/dev/urandom"获取随机数 , 由于多个线程同时读取该文件触发内核态spinlock锁CPU sy% 100%问题 。 由于sy%系统负载过高 , 由于客户端超时时间设置过小 , 进一步引起客户端访问超时 , 超时后重连 , 重连后又进入sasl认证 , 又加剧了读取"/dev/urandom"文件 , 如此反复循环持续 。
此外 , 第一次业务抖动后 , 服务端扩容了8个mongos代理 , 但是客户端没有修改 , 造成B机房业务配置的2个代理在同一台服务器 , 无法利用mongo java sdk的自动剔除负载高节点这一策略 , 所以最终造成”雪崩” 。
Q3:为什么数据节点没有任何慢日志 , 但是代理负载却CPU sy% 100%?
答:由于客户端java程序直接访问的是mongos代理 , 所以大量链接只发生在客户端和mongos之间 , 同时由于客户端超时时间设置太短(有接口设置位几十ms , 有的接口设置位一百多ms , 有的接口设置位500ms) , 就造成在流量峰值的时候引起连锁反应(突发流量系统负载高引起客户端快速超时 , 超时后快速重连 , 进一步引起超时 , 无限死循环) 。 Mongos和mongod之间也是链接池模型 , 但是mongos作为客户端访问mongod存储节点的超时很长 , 默认都是秒级别 , 所以不会引起反复超时建链断链 。


推荐阅读