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


  • 客户端启用6000个并发链接 , 超时时间500ms
  • 通过上面的操作 , 可以保证所有请求超时 , 超时后客户端又会立马开始重新建链 , 再次建链后访问MongoDB还会超时 , 这样就模拟了反复建链断链的过程 。 此外 , 为了保证和雪崩故障环境一致 , 把2个mongos代理部署在同一台物理机 。
    2) 故障模拟测试结果
    为了保证和故障的mongos代理硬件环境一致 , 因此选择故障同样类型的服务器 , 并且操作系统版本一样(2.6.32-642.el6.x86_64) , 程序都跑起来后 , 问题立马浮现:
    连环触发!MongoDB核心集群雪崩故障背后竟是……文章插图
    由于出故障的服务器操作系统版本linux-2.6过低 , 因此怀疑可能和操作系统版本有问题 , 因此升级同一类型的一台物理机到linux-3.10版本 , 测试结果如下:
    连环触发!MongoDB核心集群雪崩故障背后竟是……文章插图
    从上图可以看出 , 客户端6000并发反复重连 , 服务端压力正常 , 所有CPU消耗在us% , sy%消耗很低 。 用户态CPU消耗3个CPU , 内核态CPU消耗几乎为0 , 这是我们期待的正常结果 , 因此觉得该问题可能和操作系统版本有问题 。
    为了验证更高并反复建链断链在Linux-3.10内核版本是否有2.6版本同样的sy%内核态CPU消耗高的问题 , 因此把并发从6000提升到30000 , 验证结果如下:
    测试结果:通过修改MongoDB内核版本故意让客户端超时反复建链断链 , 在linux-2.6版本中 , 1500以上的并发反复建链断链系统CPU sy% 100%的问题即可浮现 。 但是 , 在Linux-3.10版本中 , 并发到10000后 , sy%负载逐步增加 , 并发越高sy%负载越高 。
    总结:linux-2.6系统中 , MongoDB只要每秒有几千的反复建链断链 , 系统sy%负载就会接近100% 。 Linux-3.10 , 并发20000反复建链断链的时候 , sy%负载可以达到30% , 随着客户端并发增加 , sy%负载也相应的增加 。 Linux-3.10版本相比2.6版本针对反复建链断链的场景有很大的性能改善 , 但是不能解决根本问题 。
    4、客户端反复建链断链引起sy% 100%根因
    为了分析%sy系统负载高的原因 , 安装perf获取系统top信息 , 发现所有CPU消耗在如下接口:
    连环触发!MongoDB核心集群雪崩故障背后竟是……文章插图
    从perf分析可以看出 , cpu 消耗在_spin_lock_irqsave函数 , 继续分析内核态调用栈 , 得到如下堆栈信息:
    - 89.81% 89.81% [kernel] [k] _spin_lock_irqsave - _spin_lock_irqsave
    - mix_pool_bytes_extract
    - extract_buf
    extract_entropy_user
    urandom_read
    vfs_read
    sys_read
    system_call_fastpath
    0xe82d
    上面的堆栈信息说明 , MongoDB在读取 /dev/urandom, 并且由于多个线程同时读取该文件 , 导致消耗在一把spinlock上 。
    到这里问题进一步明朗了 , 故障root case 不是每秒几万的连接数导致sys 过高引起 。 根本原因是每个mongo客户端的新链接会导致MongoDB后端新建一个线程 , 该线程在某种情况下会调用urandom_read 去读取随机数/dev/urandom, 并且由于多个线程同时读取 , 导致内核态消耗在一把spinlock锁上 , 出现cpu 高的现象 。
    5、MongoDB内核随机数优化
    1) MongoDB内核源码定位分析
    上面的分析已经确定 , 问题根源是MongoDB内核多个线程读取/dev/urandom随机数引起 , 走读MongoDB内核代码 , 发现读取该文件的地方如下:
    连环触发!MongoDB核心集群雪崩故障背后竟是……文章插图
    上面是生成随机数的核心代码 , 每次获取随机数都会读取”/dev/urandom”系统文件 , 所以只要找到使用该接口的地方即可即可分析出问题 。
    继续走读代码 , 发现主要在如下地方:
    //服务端收到客户端sasl认证的第一个报文后的处理 , 这里会生成随机数
    //如果是mongos , 这里就是接收客户端sasl认证的第一个报文的处理流程
    Sasl_scramsha1_server_conversation::_firstStep(...) {... ...
    unique_ptr sr(SecureRandom::create);binaryNonce[0] = sr->nextInt64;
    binaryNonce[1] = sr->nextInt64;
    binaryNonce[2] = sr->nextInt64;
    ... ...
    }
    //mongos相比mongod存储节点就是客户端 , mongos作为客户端也需要生成随机数
    SaslSCRAMSHA1ClientConversation::_firstStep(...) {... ...
    unique_ptr sr(SecureRandom::create);binaryNonce[0] = sr->nextInt64;
    binaryNonce[1] = sr->nextInt64;
    binaryNonce[2] = sr->nextInt64;
    ... ...
    }
    2) MongoDB内核源码随机数优化
    从前面的分析可以看出 , mongos处理客户端新连接sasl认证过程都会通过"/dev/urandom"生成随机数 , 从而引起系统sy% CPU过高 , 我们如何优化随机数算法就是解决本问题的关键 。