技术决策需要在不同限制条件下做出权衡,本文介绍了Twitter早期应对用户大规模增长所做出的技术架构决策。原文:Twitter’s Tough Architectural Decision[1]
研究大规模互联网应用可以学到很多东西,像Netflix这样的公司,其复杂的架构不仅能够向数百万用户提供内容,而且还改善了用户体验,增加了用户粘性。
在一家小公司中,像推送通知这样的机制可以是主代码库的一部分,并且可以在同一台机器上与应用程序的其他部分一起运行。
但在Netflix,负责推送通知的服务器部署在全球多个数据中心,在本文的最后有一个关于这一架构演进的详细讨论的链接。
虽然学习这些案例研究很有趣,但大多数人仍然没有在如此大的规模下工作的经验。在某些情况下,我们更容易了解公司如何从传统的主流应用服务方式转变为迎合用户体验的现代解决方案。
下面我们以Twitter为例加以说明。
10年前,大约在2012 - 2013年,Twitter呈指数级增长,服务1.5亿用户。
当时Twitter每秒钟处理6000条来自用户的推文。我想说的是,这仍然在可控范围之内,我们拥有高吞吐量的数据库可以处理这一写入速率。
但对于阅读推文的用户来说,就有问题了。当每个用户查询Twitter主页时,必须获取到所关注的每个人的推文组合,这就是每秒钟30万请求!!
如果直接从数据库读取,这个读取速率会让人发疯!
每个请求是什么样子?我举个例子。想象一个人关注了5000个用户,当他打开Twitter网站时,后端使用如下查询访问数据库:
Get all the tweets ordered according to time, if it belongs to any of these 5000 users
如果某个用户在这5000个用户内,获取该用户所有的推文,并按时间排序
即使将查询限制为几条推文,仍然会给数据库带来沉重的负担。
Twitter如何解决容量问题🚀
Twitter有两条时间线:
- 用户时间线: 用户自己所有推文的集合,从磁盘中获取。
- 主页时间线: 用户关注的人的推文的集合,组成了用户的主页。
在设计数据库时,最简单的数据库只是将所有写操作附加到文件的末尾,并在需要时读取。没有什么比简单的附加到文件中更快的了,但是,随着数据库文件的增长,从这种类型的数据库中查询某些内容将花费很长时间。
为了减少查询时间,我们在其中添加索引。但是添加索引将意味着写入将花费更长的时间,因为必须在将其写入数据库之前编辑索引。但由于读取的数量将远远多于写入的数量,这是一种公平的权衡。
同样,Twitter的阅读量也远远超过了写入量。所以他们构建了一个系统,可以更好的为用户的主页时间线服务。他们预先计算出所有用户的主页时间线,并将其存储在Redis集群中。就这么简单!
现在,无论用户何时发布消息,该消息都会被插入每个关注者的时间线队列中。所以如果你有5000个粉丝,你的推文就会有5000个写操作!!外加上1个数据库本身的写操作😅
这听起来很疯狂,但你仔细想想,这是有道理的。现在可以同时为数百万用户提供服务而不需要访问磁盘,这可以从根本上减少延迟。这个过程叫做扇出(fan-out)!
那么到目前为止,Twitter的架构是怎样的呢?
- 用户访问Twitter的主页。
- Twitter在Redis集群中查找用户的主页时间线。
- 一旦找到,就向用户直接展示。
- 当用户发送一条推文时,该推文会被复制到用户的所有关注者的时间线上。
还有一些其他的细节:
- Twitter维护了一个图数据库[2],其保存了用户关注关系的所有数据。当发生扇出时,扇出服务查询这个图数据库以确定将推文推送到何处。
- 扇出将在数据中心的3台不同的机器上进行复制,即每个用户的时间线存储在3台不同的机器上。这是必需的,因为如果其中一台机器出现故障,其他机器可以作为备份提供服务。
- 推文本身并不存储在集群中,而只存储推文ID。推文将在传递给用户的同时被检索出来。
- 如果用户超过30天没有登录Twitter,该用户的时间线将不会保存在Redis集群中。对于这类用户,只有当他们返回并向主页时间线发出请求时,才会从磁盘重构他们的时间线。
每个用户的主页时间线上存储的推文数量是有限制的,每次只向用户显示800条推文,这是为了控制内存使用而做出的设计决策!
名人用户的特殊处理💃🏻🕺🏻
上述优化适用于普通用户,但名人有特殊的处理,下面我们以Lady Gaga为例,她当时有大约3100万粉丝。
当Lady Gaga发布推文时,会发生3100万次扇出操作!!而且要复制3次!!
结果就是,有些用户可能会在Lady Gaga的推文发布5分钟后才能看到,因为他们的时间线没有被及时更新。
这就产生了一个不太好的效果,一个可以看到Lady Gaga推文的用户会回复这条推文,这条回复将会在其他用户的时间线中出现,而这些用户还没有收到lady Gaga的原始推文,也许可以称之为无头推文(Headless tweets)🤔
为了避免这种情况,Twitter想出了一种混合方法:
- 如果一个人有太多的关注者,那么就不对他们的推文做扇出操作。
- 这些用户的推文只有在有人请求主页时才会被合并到时间线中。
又一个简单的解决方案!
总结
研究Twitter的整个架构是非常困难的,尤其是现在已经成为了大公司以后。
但从他们过去解决大规模问题的方式中学习,也可以帮助我们在工作的时候做出决策。即使在成千上万的地方存储一条推文听起来很荒谬,如果能够有效解决问题并且没有任何副作用,这就一个很好的解决方案!
参考
- Timelines at Scale[3]
- The Infrastructure Behind Twitter: Scale[4]
- Scaling Push Messaging for Millions of Devices @Netflix[5]
References:
[1] Twitter’s Tough Architectural Decision: https://dennysam.medium.com/twitters-tough-architectural-decision-c61e4d0d41a5
[2] SQL, NoSQL, Graph: A Commentary on Databases: https://softwareengineeringwk.substack.com/p/nosql-sql-graph-database-types?r=7esi1&utm_campaign=post&utm_medium=web
[3] Timelines at Scale: https://www.infoq.com/presentations/Twitter-Timeline-Scalability/
[4] The Infrastructure Behind Twitter: Scale: https://blog.twitter.com/engineering/en_us/topics/infrastructure/2017/the-infrastructure-behind-twitter-scale
[5] Scaling Push Messaging for Millions of Devices @Netflix: https://www.youtube.com/watch?v=6w6E_B55p0E
你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。
微信公众号:DeepNoMind