现场填坑系列:mongodb 复制集跨机房同步网络问题探查
接到现场报告,客户MongoDB间数据延迟越来越大,有的已经超过2-3个小时,造成有些打到延迟mongodb上面的数据库请求无法反应数据库的最新更改。这个问题反复出现在高峰期尤其明显,持续近一月。
架构
客户为异地双机房架构,两地机房相隔上千公里,带宽250M,光纤,具体链路情况不明
F5 | F5
N中心 | S中心
业务服务 ... 业务服务 | 业务服务 .... 业务服务
mongodb4 ... mongodb6 <----|----- mongodb1(主) ... mongdb2
所有数据库在一个复制集共6台(其中一台不参与选举),使用的是mongodb replicaSet 进行同步。
现象
S中心数据库反复产生延迟,其中一次rs.printSlaveReplicationInfo()查询集群情况显示如下,数据库4,5,6三台延迟(此处不是每次都是三台同时延迟,有时只是这三台中的两台或者一台出现延迟,但是1,2从不延迟),延迟时间最多达到2-3个小时,非高峰期可以恢复。
?
- 客户在两个机房间通过ping命令未发现南北网络有延迟现象。
- 在发生延迟时,有时可以通过切换同步源的方式,暂时解决问题。
插入图
分析
首先查看有问题的机器,发现4,5,6三台均在N中心,1,2均在S中心,而用rs.status查看,可以发现当延迟时,这三台都以1为同步源,也就是发生跨网络同步。
由于三台出现同步的机器都在N中心,可猜测与链路有关。客户ping命令未显示延迟,说明ping的数据量可能不够或者问题是由某种条件触发的。
是否有可能同步带宽超过了链路限制,我们和网络方面的人进行了沟通,得到的消息是我们的应用使用的带宽没有达到上限。最高也就极限带宽的2/3左右。为确认带宽情况,我们自己也对带宽进行了分析。
带宽使用分析
南北中心业务高峰期正常情况下带宽占用70Mb/s,也没有达到理论高度,而且正常情况下某些瞬时,带宽可以超过150Mb/s,也证明服务远远没有达到带宽上线。我们进一步对产生延迟的情况下的带宽进行分析,发现了一个奇怪的情况
:在产生延迟的情况下,数据库用来同步的带宽远远不足60Mb/s ,只有不到15Mb/s。也就是说,数据库有大量堆积,产生延迟的时候,数据库同步反而变慢了。
同时我们监控了S中心内未延迟节点与通中心主节点同步时的带宽约为17Mb/s ,由于mongodb每台slave都是复制同样的数据,可以推知其他机器都应该按照这个速率进行同步。而延迟节点只有4.5Mb/s左右。
我们又查看了延迟节点的监控,查看了延时节点从正常状态转入延迟状态的带宽情况,发现其使用带宽会在某种情况下发生骤降
到底是什么导致这种状况呢?是mongodb本身的问题吗?
这种情况我们还是决定围绕核心现象来进行分析:这几台服务器和正常的服务器有什么不同。我们一一排查了机器配置,软件配置的问题,确保出问题的服务器和正常服务器没有原则不同,同时也查了这几台服务器在延迟时的表现,发现延迟时,这几台mongo的指标远远没有到达瓶颈,所以唯一可疑的不同还是在网络上,即使ping没有发现问题,我们还是决定要进一步抓包。
抓包
因为抓包对性能有显著影响,为了抓出特征包,我们选在节点开始延迟,并且延迟时间还在增大时进行抓包,抓包时间在1分钟以内。
抓包语句 tcpdump -i eth0 port xxx -w /home/tcpdump.cap
下面是我们看到的情况(使用Wireshark)
?
网络中出现了大量的Dup ACK,这个说明了TCP出现了数据丢失导致了重传。
Dup ACK
TCP的流程是面向连接,会尽力保证数据的完整性,所以有失败重传机制,Dup ACK 就是这个机制的一部分。
每个包都有一个Seq号和一个包长度,正常情况下,某一端所有发送的包的Seq号是连续的,比如
第一个包的Seq是1,长度是2,
则第二个包Seq=1+2=3,长度4,
第三个包的Seq=3+4,长度若干,
以此类推,当接收端收到这个包时会将这个包放到缓冲区,缓冲区里可能有很多从另一端发来的包,他们必须能连续起来,接收端会向发送端发包时告知会带上我接收到的最后一个连续的包的Seq是多少,这个数据会放在ACK里传送。
上面注意:
- 发送端和接收端是相对的,两边都遵循这个规则。
- 不是每一个包都ACK,这样开销太大。
- 也不一定有专门用于ACK的包,事实上ACK包往往也可能是对上一个或者几个包的回复,顺便就进行了ACK。
插入图
假如丢失了一个包会怎么样?接收端发现缓存里的包丢失了,而后面的包已经到了,就会触发上面的Dup ACK,告知我接收到的连续的数据的序号到哪里为止,也就是DupACK包的ACK。这个请求会反复发送,直到服务端发送缺失包(retransmit)。在收到缺失包前,后续的包即使收到也不会ack。这个是retransmit的流程。
上面的流程在dump中典型的例子用wireshark打开会是下面的样子:注意这里包括【TCP Previous segment not captured】【Dup ACK】 等内容都是wireshark的分析结果。TCP包中并不包含这些标签。
首先从端发现丢包【TCP Previous segment not captured】然后引发从端大量发送dup ack,直到主端重传通过TCP Fast Retransmition 或Retransmition 重传missing package,见下图。重传期间所有有缺失的包会暂存入buffer。
?
在wireshark中可以对重传的包进行过滤 tcp.analysis.retransmission
过滤结果为32条,占所抓包的0.3%,这个统计可以看做是就是丢包的统计。然而由于这样的丢包,触发tcp进行反复沟通引起的混乱远远大于此,如果统计所有由此引起的dup Ack, Out of Order,Fast Retransmit,Transmit,window_update 等,可以看到这些处理丢包的请求占到了5.5%之多。
同时刚才提到发送端可能发送很多包,但是只是其中一个丢了。这些没有丢掉的包会放在一个缓冲区中,这个缓冲区叫做window,由于后续包不断到来,而失去的包没有补上,所以tcp不能向上层转发,于是就会更改window的大小,这个也是非常消耗资源的行为。
总结来说:
从库的dump中,共抓包16.81秒10176个包,错误重传造成的dup ack和重传包达到562 占 5.5%;
主库(同步源)的dump中,共抓包12秒 3501个包,错误重传以及dup ack相关的包占到325,占比9.3%;
而同机房内部节点完全未见丢包与重传,且在跨机房节点处于无延迟正常状态也未见丢包重传。
重传与流量下降间的关系
为了确保这个现象只在跨机房调用中出现,我们也抓取了同机房同步,以及跨机房无延迟时的情况,这两种情况下均无上述问题。重传错误与流量的关系可见下图,折线为吞吐,柱形为error包数量,可见流量的重大转折均伴随有相关错误,下图也展现了window scaling的情况,展示出由于错误发生,windows size 的修改情况,可见6分钟左右发生了一次大的scaling,正对应了dump中的错误。
?
结论与解决方法
在丢包率0.3%的情况下,mongodb的replicaSet发生了比较严重的问题。表现为同步速率大幅下降,然后产生延迟。
客户网络情况比较复杂,链路是运营商链路,还有许多第三方厂商的设备,难以在短时间内排查原因,所以再发生同步时,我们采用rs.syncFrom脚本切换同步源临时解决这个问题:即当脚本监测延迟>10分钟时,立刻切换到另外一个数据源。
此方法基于在产生延迟后,立即切换另外一个数据源可以重新打开同步链路快速同步,瞬间同步时速可以到达160Mb/s,延时数据能很快消失。
但是在执行过程中,出现过切换同时,mongodb由于大量写入而影响业务的情况,所以后来改为在非高峰先讲需要修改的节点改为hide节点再进行处理。
未来最终解决方案
解决网络延迟:这个是最直接的解决办法,毕竟同机房同步未见问题。
减少replicaSet的量:检查业务中的写入更新删除逻辑,减少生成量。具体分析和处理方法将另文描述。
使用mongoshake等第三方同步工具:由于尚未明晰重传为何导致复制集处理速度下降,所以不使用复制集也可能可以解决这个问题。且复制集对源的选择有自己的算法,虽然可以短时间的切换,但是更多时候是根据mongodb自身的算法进行选择,比如在本案中,三台N节点Mongo都到S节点同步,造成带宽乘以3倍,虽然可以手动制定其中两台从同节点一台复制,但mongodb还是有可能自动根据自己的算法切换源。而使用第三方同步工具可以解决上面的两个问题。但是要检查第三方工具在0.3%网络丢包下的工作情况。