8 月 7 日上午 8 点 30 分左右,聊天服务出现不稳定状态,8 点 59 分我们内部监控系统显示服务不可用并报警,工程师随之上线处理。之后陆续收到用户反馈,说终端用户无法连接上 LeanCloud 聊天服务器。经我们检查确认是由于某台服务器出现了网络故障而导致连锁反应,我们工程师在定位故障后即刻进行了服务重启和扩容,最终在 11 点 50 分让服务彻底恢复正常。
这是一次非常重大的事故。为了将问题说清楚,我们先来介绍一下 LeanCloud 聊天服务的架构。
这里主要包括如下几部分:
-
Router
这是处理客户端连接请求的大门,由它来分配具体的连接服务器,客户端转而与连接服务器进行通信。
-
消息推送集群
负责处理 Push Notification(消息推送)的请求。它会从消息队列里面获取请求,然后检索目标设备,最后将推送消息发送到实时通信服务器(消息推送和实时通信共享同一个长连接,如上图中 ① 所示)。
-
实时通信集群
负责处理客户端的消息请求,具体而言,其内部又分为连接管理和消息发送多个模块。因为同一个应用、同一个聊天室的用户可能分散到不同物理机器上,所以在用户之间进行消息转发的时候,实时通信服务器之间会产生 RPC 调用(如上图中 ② 所示)。
-
Zookeeper cluster
所有集群的状态监控和自动扩展都是通过 Zookeeper 来完成。
接下来给大家还原一下事故的过程和我们的处理措施:
|时间|过程描述|
|---|---|
|07:57|有一台服务器出现网络问题,所有进出的 RPC 请求都无法完成,与 Zookeeper 连接也多次失败。|
|08:25|由于其他机器发送到故障机的请求堆积,整个集群内存紧张,服务开始出现不稳定。|
|08:59|系统监控开始报警,服务大范围不可用。这时候我们工程师紧急上线开始处理。|
|09:10|剔除故障机,第一次尝试重启集群。但由于客户端重连机制的存在,重连压力太大造成服务启动失败。|
|09:50|用 iptables 来挡住部分连接请求,第二次尝试逐步开放服务,再次失败。|
|10:15|扩容集群,再次逐步开放服务。虽然重连压力依然非常大,但服务器运行正常。|
|10:50|限制对旧节点和端口的访问,开放 15% 的访问流量,这时候应用内有少量用户已经恢复使用。|
|11:20|所有旧节点使用新端口重新上线,并逐步开放 30%、50%、70% 的访问流量。|
|11:50|全流量开放,实时通信和推送服务完全恢复正常。|
下面这张图表是我们在故障期间所监控到的连接数变化情况:
虽然我们较早就收到了系统报警,但在事故处置阶段,因为没有很好地应对大量客户端重连的情况,所以导致几次重新开放服务都宣告失败,影响了服务恢复的速度,这值得我们反思:当开放服务时,我们首先尝试了同时开放全部服务器,但因请求量过大,应用层无法处理而失败。然后我们将 router 规则调整为随机分配,小范围地开放了两台服务器,但又发现进程最大可用文件句柄数很快被占满,单台机器上瞬时涌入了数倍于正常状态的连接。经过分析,确认是客户端重连、发来的 SSL 握手请求无法被应用层处理,所以最后我们通过同时更改 iptables 和 router 规则,逐步放行访问流量,才让集群最终恢复过来。
本次故障持续时间超过 3 个半小时,对用户业务产生了重大影响,我们对此深感愧疚!
我们一直致力于打造最稳定的云服务,接下来整个团队会以最高优先级来解决平台稳定性问题,信誓旦旦的保证是没有实际意义的,所以后续改进措施里也加入了截止时间,希望大家一起来监督:
-
应用层容错:改进 RPC 容错性,限制积压请求量,避免单点故障影响全局。(预计 8 月 13 日周四完成)
-
问题发现速度:目前的连接波动报警机制还不及时,我们会通过短信、邮件、内部机器人等多种手段来及早报警。(预计 8 月 11 日周二完成)
-
事故处理速度:吸取大规模长连接集群运维经验,建立细粒度的流量控制机制,可以在系统层和应用层比较方便地进行控制。(预计到 8 月 31 日完成第一个版本)
-
客户端 SDK:客户端要确认在重试新的服务器前关闭旧的连接,并进一步优化重连机制。(预计 8 月 14 日周五完成)