英文原文:How League Of Legends Scaled Chat To 70 Million Players - It Takes Lots Of Minions.
在 2013 年初马化腾被问及“过去两年腾讯在海外投资中最成功的案例是什么”时,他毫无疑问的回答:“投资美国的 Riot Games,做出《英雄联盟》。”在那个时候,《英雄联盟》这款游戏仅上市 3 年,却以 500 万同时在线(日活跃用户 1200 万)玩家数量横扫全球,成为全世界第一大线上游戏。而值得一提的是,一年后(2014 年),该游戏的日活跃玩家数量已超过 2700 万,最高同时在线玩家也达到了 750 万。
回顾《英雄联盟》的发展无疑是一个高速成长的光辉史,然而这个光辉史赖以生存的基础设施却不得不克服一次又一次的挑战,历经一次又一次的迭代,就比如下面我们要说的聊天服务。近日,HighScalability 创始人 Todd Hoff 总结了 Riot Games 公司 Michal Ptaszek 在 Strange Loop 2014 会议上的演讲“Scaling League of Legends Chat to 70 million Players”( PPT 下载),简述了该游戏聊天服务“Make it work. Make it right. Make it fast.”理念的实现途径。
以下为译文
如上图所示,该游戏的聊天服务需要支撑 750 万并发用户,2700 万日活跃用户,每秒钟需要处理的消息上万条,每台服务器每天处理消息达十亿条。
对于对战类型游戏,团队间交流直接影响到了比赛的胜负。为了帮助完成这一目标,聊天服务初始就使用了 XMPP 特性,就如 WhatsApp 一样。在小规模下实现并没有什么难度,可以说是开箱即用,然而当用户快速增长时,挑战也随之而来。为了更快和更好的实现这一点,WhatsApp 和 LOL(英雄联盟)不得不定制化 Erlang VM,对其进行优化并添加多个监控功能,只为解决大规模下的性能瓶颈。
纵观整个服务架构,Riak CRDTs(commutative replicated data types,可交换多副本数据类型)应用无异是最大的亮点,通过零可变贡献实现大规模线性横向扩展。时至今日,CRDTs 的使用范围仍然很小,甚至大多数人都没听说过这项技术,但是它将来必将成为众多机构的宠儿,在写操作上另辟奇径。下面我们一起看 LOL 如何打造支撑超过 7 千万玩家的聊天系统:
状态
平台
Chat
1. 支持私聊和群聊
2. Chat 拥有独立的界面,同时还支持好友列表。你可以查看好友的连接状态(在线或离线)、游戏状态、游戏时间,以及获得过的奖项。
3. REST APIs 让 chat 可以作为其他 LoL 服务的后端服务。举个例子,store 会与 chat 通信来验证好友关系。Leagues 会使用 chat 的社交图谱将新玩家组织到一起。这样一来,这些新玩家就可以交到一些志同道合的朋友,从而增加在线时间。
4. Chat 必须稳定的保持在一个低延时环境,chat 的宕机会拉低整个游戏的用户体验。
5. 选择 XMPP 作为协议,提供消息、状态信息并且负责通讯列表维护。
6. 基于性能和新功能等原因,他们不得不偏离核心 XMPP 协议。
7. Chat 服务打造时就选择了 Ejabberd 作为服务器。Erlang 同样非常棒,拥有更好的错误隔离和可追溯性。同时,它还支持代码的热加载,如此一来,给 bug 打补丁时就不需要再重启服务。
8. 目标是零共享以实现线性横向扩展,同时零共享还更有益于错误隔离及追溯。在零共享实现上,系统刚还有一些提升空间。
9. 运维人员只有 3 人,这样就对 chat 服务器容错提出了非常高的要求,同时也意味着不是每个故障都需要人力介入。
10. 让它崩溃。不要试图从一个严重的故障中做缓慢的恢复。取而代之,从一个已知的状态下重启更加适合。举个例子,当大量数据库查询积压时,重启可以让新的查询实时完成,队列中的查询则另选恰当时间进行。
11. 每台服务器上都运行了 Ejabberd 和 Riak,Riak 作为服务器使用。在需要时,可添加服务器对系统进行横向扩展。Ejabberd 和 Riak 运行在不同的集群中。
12. Riak 服务器使用了多数据中心备份机制,它们还会提供数据给第二 Riak 集群。类似社交图等昂贵的 ETL 查询都运行在第二集群上,从而避免主集群受到影响。备份操作同样会在第二集群上进行。
13. 扩展性、性能和容错机制是个长期奋斗目标,大部分的 Ejabberd 代码都已经被重写。
14. 消除明显的瓶颈。
15. 避免共享可能会变化的状态,这样可以更容易的进行大规模线性扩展。
16. 增加功能以获得生产环境中代码的能见度。让代码可以在涉及到同一事务的多个服务器上同时升级。
17. 优化 Erlang VM 中的服务器调试功能。获得会话内存使用情况,以更好地进行内存使用优化。
18. 项目开始时就考虑到了数据库扩展性。开始时选择的 MySQL 造成了性能、可靠性、扩展性等多方面的问题。比如,数据模式的修改速度匹配不了代码的变更。
监视
实现 Feature Toggles(功能表示)
动态代码重载
日志
加载测试代码
未来的工作
学到的知识
1. 故障肯定会产生,你不需要做到完全控制。即使你的代码不存在 bug,也可能存在一个 ISP 路由器以及 10 万个用户同时丢失的情况。做好万全之策。确保系统可以承担同时丢失一半用户的情况,或者在丢失1/4 chat 服务器的情况下不会影响到性能。
2. 规模扩大小概率事件变常态。如果某个 bug 发生的概率是十亿分之一,但是在 LoL 的规模,这个 bug 可能每天都会发生一次。即使某些完全不可能发生的事件都有可能发生。
3. 成功的关键是清楚系统发生的所有事情。必须清楚你系统是健康的或者濒临崩溃。
4. 指定一个策略。LoL 为其 chat 服务选择了横向扩展策略。为了支撑这个策略,他们选择了一个不同的途径来支撑这个策略。他们不仅选择了 Riak 这个 NoSQL 数据库,同时还挑战了 CRDTs 这个途径,只为了横向扩展能尽可能的无缝和强大。
5. 可用。贯穿开始和衍变。他们开始于 Ejabberd,这并不一定代表着 Ejabberd 更容易开始,但是 Ejabberd 绝对可以更匹配他们的需求。
6. 让一切更可见。增加追踪、警报、监视、同样一级一切有意义的东西。
7. 让系统可运维。LoL 给软件更新添加了事务特性,还给系统添加了功能标识、热更新、自动化测试加载、高可配置日志等级等功能,这一切都只是为了更容易管理。
8. 减少无用协议。定制系统所需的功能。如果你的系统只存在双向好友关系,那么你不再需要那个通用的昂贵协议。
9. 避免可变状态共享。这条已众所周知,但是仍然有系统会共享可变状态,从而导致横向扩展时的各种问题。
10. 利用好你的社交图。Chat 服务提供了一个原生的社交图。这些信息可以被用于提升用户体验,以及开发更有意思的新功能。