对六月六日 LeanCloud 多项服务发生中断的说明

各位 LeanCloud 的用户,大家好。

LeanCloud 的多项服务在六月六日周六下午发生了大约四个小时的中断或不稳定。其中 16:10 到 19:09 为故障阶段;19:09 到 20:17 为限流恢复阶段。

在故障阶段受到重大影响的服务包括:数据存储、网站及控制台、云代码、推送、工单系统、用户反馈、第三方登录、应用内社交;受到轻度影响的服务包括:短信、实时通信服务中获取聊天记录的 API;未受影响的服务包括:统计分析、离线数据分析、应用内搜索、文档。

在限流恢复阶段受到重大影响的服务包括数据存储、网站及控制台、云代码、推送、短信、工单系统、用户反馈、第三方登录、应用内社交、统计分析、离线数据分析、应用内搜索、文档、实时通信中获取聊天记录的 API。实时通信在这个阶段未受影响。

在这次事故中没有发生服务器端数据的丢失或损坏。

我们知道这次服务中断给很多用户造成了实质性的影响。我写这封信的目的是为了向用户说明发生的事情,以及我们将如何改进产品和服务以降低类似事件发生的可能性。

架构总览

为了更好地解释这次事故,我想先简单介绍一下 LeanCloud 的后端架构。以下是一个简化版的架构图。

LeanCloud 简化架构图

虽然实际上系统间的关联比这个图要复杂,并且有一些做异步处理的系统没有画出来,但用以说明问题是足够的。

在 LeanCloud 的最前端,除了负载均衡之外就是 API 服务集群,也就是实现 RESTful API 的部分。客户端应用通过 SDK 或直接调用 API 和它通讯。根据请求的类型,API 服务再调用后端的各个系统完成所需的功能。比如如果是统计服务的请求,事件信息会被发送到统计服务并最终被保存到 HBase 集群;如果是数据存储服务的请求,那么 API 服务就会调用 MySQL 和 MongoDB 集群完成数据访问。因为 MongoDB 存储了每个应用的大部分数据,所以不少服务都需要从这里获取一些信息,是整个系统中很重要的组件。

MongoDB 集群存储了所有应用除了统计和备份数据以外的其他数据。由于数据量巨大,所以我们对 MongoDB 进行了分片(sharding),让数据分布在不同 shard。每个 shard 又由 5 台服务器(节点)组成,其中每台都保存着这个 shard 的完整数据,这样不但可以承担更高的负载,也保证了即使其中一些服务器坏掉,数据也是安全的。每个 shard 有一台称为 primary 的服务器,所有对数据的修改都会首先在这台服务器进行,然后再复制到其他服务器,而读取可以从任何一台服务器进行。当 primary 出现问题时,会有一个自动选举过程从其他成员里选出新的 primary,让服务可以继续,这是实现容错的方式。

故障说明

在故障发生前的一周,我们做了与本次故障相关的两个改动。

为了更好地管理 LeanCloud 的大量服务器,我们在近期引入了 Apache Mesos,这是一个为服务器集群的资源管理提供高层抽象接口的系统。Mesos 会在每台受管理的服务器上运行一个 mesos-slave 进程。

我们把 MongoDB 从 2.4 升级到了 2.6。新版本对地理位置的查询,以及 count 查询的准确性有所改进,所以我们认为这次升级对用户来说是值得的。但新版本的一个负面影响是,因为每次启动时要检查索引,所以启动时间大大延长了。而如果 MongoDB 是在出错的情况下重新启动,会导致大量的索引被重新建立,进一步延长启动的时间。

大约在 16:10 我们的运维工程师收到 MongoDB 集群内存不足的报警,在初步诊断后,确定原因为 mesos-slave 启动的一个子进程占用内存太多,有可能是存在内存泄漏,所以我们开始终止各台服务器的 mesos-slave 进程。在这时我们发现 MongoDB 的其中一个 shard 中有三个节点因为可用内存不足进入异常状态,其中一个是 primary,所以我们开始重启其中两个节点。由于 MongoDB 2.6 在启动过程中需要校验数据并修复索引,所以这个过程很慢,而当时正是流量高峰期,这个 shard 剩下的节点不足以承担当时的负载,所以很快被压垮。由于 primary 的连接数被占满,这导致出问题的两个节点无法加入集群。在这种情况下,我们决定屏蔽所有 API 服务请求,并重启处于错误状态的三个节点。

在此之后,这三个节点分别经历了多次重启,原因是出问题的 shard 大约有 10,000 个数据库,而新版 MongoDB 要验证每个数据库并修复索引。这不但使得重启很慢,而且因为数据库数量太多,这个过程会耗尽系统对子进程数的限制,所以在重启之后集群无法恢复正常可用状态。直到我们找到原因并调整了系统设置,各个节点才完成正常启动,集群恢复可用。

在这个过程中,除了事故本身,我们在沟通上也犯了一些错误。当用户询问服务恢复时间时,我们给出了过于乐观的估计,但因为以上所说的原因,多个 MongoDB 节点经过了多次重启,实际恢复服务的时间晚了很多,这给用户造成了进一步的困扰。

改进措施

  • 更新 MongoDB 节点内存报警值,预留更多响应时间。(已完成)
  • 在 MongoDB 节点暂停使用 mesos-slave。(已完成)
  • 设置各服务 OOM 优先级,意外情况发生导致剩余内存不够时重启低优先级服务以保护高优先级服务。(已完成)
  • MongoDB 集群增加硬件资源。
  • 拆分 MongoDB 集群为多个小集群,降低单个集群里的数据库数量。这也让我们可以对 MongoDB 进行滚动升级,降低风险。
  • 在重要系统升级前进行更多事故恢复的测试,并以滚动方式在较长时间内逐步完成升级。
  • 降低 MongoDB 启动时间。
  • 进行阶段性的 MongoDB 压测及故障恢复模拟。
  • status.leancloud.cn 从主站拆分,让用户得到更准确的故障信息。
  • 改进 API 服务线程池分配机制,避免因为一个 MongoDB shard 异常而堵塞线程池。
  • 改进 API 服务流量控制,紧急情况时做到可以仅切断对应特定 MongoDB shard 的请求。
  • 在发生线上事故时,我们将在 Blog 上开通直播贴,实时向用户通报进展。

结语

我们肩上承载着为数万开发者提供稳定服务的责任。周六傍晚事故持续的四个小时也是所有 LeanCloud 的同事最难熬的一段时间。我们了解到有的创业团队为了当天进行的一个活动,在过去的时间里非常辛苦地工作,而活动却受到了 LeanCloud 事故的影响。这样的事情让同为创业公司的我们非常地难受和惭愧。

我们希望通过这个详细的报告让用户全面地了解整个过程,并将尽一切努力降低未来发生类似事件的可能性。虽然任何人都没有办法完全保证服务中断不会发生,但我们会采取一系列措施避免可预见的问题,并确保在发生意外的时候能更快地恢复。为了保持改进过程的透明,我们开放了一个 追踪各项具体措施的 Trello board,用户可以通过它随时了解我们的执行过程。

江宏
LeanCloud Cofounder/CEO

对六月六日 LeanCloud 多项服务发生中断的说明》上有6条评论

  1. harvey

    希望能更加稳定起来,目前云服务虽然都是起步阶段,但也是拉开差距的阶段,有时候几次致命的 bug 就可能导致客户信心的动摇,从而选择别的产品。

    有问题我们理解,但希望 lc 能真正的成熟强大起来,尽量降低人为性质的 bug,做一个真正的云平台。

    回复
  2. 唐建法

    在出现问题后能够把细节都披露出来,非常赞!高透明度可以增加用户的信任度,同时也可以让业界同行得到一次借鉴机会,有则改之无则加勉。

    一些 comments 供参考:

    1)“由于 MongoDB 2.6 在启动过程中需要校验数据并修复索引。。。” 这个过程只会在你非正常停掉 mongod 进程的情况下才会执行, 并不是每次正常启动都会实行的。 MongoDB 的官方文档对于修复索引动作有很清楚的说明:

    Automatic rebuild of interrupted index builds after a restart.

    If a standalone or a primary instance terminates during an index build without a clean shutdown, mongod now restarts the index build when the instance restarts. If the instance shuts down cleanly or if a user kills the index build, the interrupted index builds do not automatically restart upon the restart of the server.

    If a secondary instance terminates during an index build, the mongod instance will now restart the interrupted index build when the instance restarts.

    To disable this behavior, use the –noIndexBuildRetry command-line option.

    2)“。。。 这个 shard 剩下的节点不足以承担当时的负载,所以很快被压垮。。。” 这个说明在容量规划上可能需要改正。理论上来说,一个分片只应该由 primary 节点来负责所有读写,这样的话任何 1 到 2 台机器宕机,都不会造成性能下降。用读写分离来减轻主节点负担的方式,必须要考虑到某些从节点宕机后剩下节点是否可以继续处理所有的系统压力。你们这次有可能就中了读写分离部署的枪。可以考虑在复制集里减少 1 个或 2 个数据节点,但是增加分片数量的方式来实现对性能的扩展。

    3) “… 直到我们找到原因并调整了系统设置…” 如果能够提供具体的设置方法,可能对读者会有很好帮助

    4) 拆分小集群是一个不错的做法,可以减少运维的压力。Facebook 的 Parse 应用平台就采用类似的做法。

    5)primary 连接爆满从另一个方面说明了对系统现有能力的评估测试尚有欠缺。如果很清楚你们的系统的性能参数的话,可以对 mongos 或者 API 端的客户端连接池进行最大值限制,在源头对 mongod 的连接数爆满问题的进行规避

    回复
  3. 唐建法

    博主能在出现问题后能够把细节都披露出来,非常赞!高透明度可以增加用户的信任度,同时也可以让业界同行得到一次借鉴机会,有则改之无则加勉。

    一些 comments 供参考:

    1)“由于 MongoDB 2.6 在启动过程中需要校验数据并修复索引。。。” 这个过程只会在你非正常停掉 mongod 进程的情况下才会执行, 并不是每次正常启动都会实行的。 MongoDB 的官方文档对于修复索引动作有很清楚的说明:

    Automatic rebuild of interrupted index builds after a restart.

    If a standalone or a primary instance terminates during an index build without a clean shutdown, mongod now restarts the index build when the instance restarts. If the instance shuts down cleanly or if a user kills the index build, the interrupted index builds do not automatically restart upon the restart of the server.

    If a secondary instance terminates during an index build, the mongod instance will now restart the interrupted index build when the instance restarts.

    To disable this behavior, use the –noIndexBuildRetry command-line option.

    2)“。。。 这个 shard 剩下的节点不足以承担当时的负载,所以很快被压垮。。。” 这个说明在容量规划上可能需要改正。理论上来说,一个分片只应该由 primary 节点来负责所有读写,这样的话任何 1 到 2 台机器宕机,都不会造成性能下降。用读写分离来减轻主节点负担的方式,必须要考虑到某些从节点宕机后剩下节点是否可以继续处理所有的系统压力。你们这次有可能就中了读写分离部署的枪。可以考虑在复制集里减少 1 个或 2 个数据节点,但是增加分片数量的方式来实现对性能的扩展。

    3) “… 直到我们找到原因并调整了系统设置…” 如果能够提供具体的设置方法,可能对读者会有很好帮助

    4) 拆分小集群是一个不错的做法,可以减少运维的压力。Facebook 的 Parse 应用平台就采用类似的做法。

    5)primary 连接爆满从另一个方面说明了对系统现有能力的评估测试尚有欠缺。如果很清楚你们的系统的性能参数的话,可以对 mongos 或者 API 端的客户端连接池进行最大值限制,在源头对 mongod 的连接数爆满问题的进行规避

    回复
  4. 运维派

    最近互联网厂家真是问题多多啊,阿里、携程、又拍、青云,现在又是 LeanCloud,不过敢于披露细节和改进措施,还是值得点赞的。

    回复

发表评论

电子邮件地址不会被公开。 必填项已用*标注