当前位置: 首页 > 科技观察

从0到100——知乎结构变迁史

时间:2023-03-12 21:41:54 科技观察

或许很多人不知道,知乎是继百度贴吧、豆瓣之后中国互联网最大的UGC(UserGeneratedContent)社区。知乎创业三年,从零开始,到现在已经有100多台服务器。目前,知乎注册用户超过1100万,每月使用人数超过8000万;网站每月PV超过2.2亿,每秒动态请求近2500次。在ArchSummitBeijing2014大会上,知乎联合创始人兼CTO李慎深带来了知乎三年多以来的首次全面技术分享(幻灯片下载)。本文根据演讲内容整理而成。初始架构选择2010年10月知乎真正开始做这个产品的时候,一开始只有李慎深等两个工程师;2010年12月上线时,工程师4人。知乎的主要开发语言是Python。因为Python简单而强大,上手快,开发效率高,社区活跃,团队成员也比较喜欢。知乎使用的是Tornado框架。因为支持异步,所以非常适合实时的Comet应用,而且简单轻量,学习成本低,有FriendFeed和Facebook社区支持的成熟案例。知乎的产品有一个特点,就是希望和浏览器建立长连接,方便实时推送消息和通知,所以Tornado比较合适。最初,整个团队专注于产品功能的开发,而其他方面则基本节省了时间,用最简单的方式解决了。当然,这也带来了后期的一些问题。最初的想法是使用云托管来节省成本。知乎的第一台服务器是512MB内存的Linode主机。但网站上线后,内测的火爆程度超出预期,不少用户反映网站速度很慢。跨国网络延迟大于预期,尤其是国内网络不平衡,全国用户访问情况不尽相同。这个问题,再加上当时的域名备案,让知乎又回到了买机找机房的老路子。买机找机房后又遇到新问题,服务经常宕机。当时服务商的机器内存老是出问题,每次都重启。最后,一旦机器宕机起不来,知乎就把web和数据库做了高可用。创业就是这样的情况,你永远不知道明天早上醒来会面临什么样的问题。这是那个阶段的架构图,Web和数据库都是master-slave。当时图片服务托管在有拍云上。除了主从,还做了读写分离,性能更好。为了解决同步问题,增加了另一台服务器来运行离线脚本,以避免对在线服务的响应延迟。此外,为了改善内网的吞吐延迟,更换了设备,使整个内网的吞吐量翻了20倍。2011年上半年,知乎就已经严重依赖Redis。除了最开始使用队列和搜索,后来也使用了Cache,单机存储成了瓶颈,所以引入了分片,同时实现了一致性。知乎团队是一个相信工具的团队,相信工具可以提高效率。工具实际上是一个过程。没有最好的工具,只有最合适的工具。而且它是在整个过程中,随着整个状态的变化和环境的变化,在不断地变化着。知乎开发或使用的工具有Profiling(功能级跟踪请求、分析调优)、Werkzeug(便捷调试工具)、Puppet(配置管理)、Shipit(一键上线或回滚)等。日志系统知乎本来就是一个邀请制。2011年下半年,知乎上线申请注册。没有邀请码的用户也可以通过填写一些信息来申请注册。用户数量达到了一个新的水平。此时有一些账号发广告,需要去除广告。对日志系统的需求已提上日程。这个日志系统必须支持分布式收集、集中存储、实时、可订阅、简单等特性。当时调研了一些开源系统,比如Scribe,总体来说还不错,但是不支持订阅。Kafka是用Scala开发的,但是团队在Scala上的积累比较少,Flume类似,比较重。于是开发团队选择了自己开发一个日志系统——Kids(KidsIsDataStream)。顾名思义,Kids用于汇集各种数据流。孩子们参考抄写员的想法。Kdis可以配置为每台服务器上的Agent或Server。Agent直接接收来自应用程序的消息,收集消息后可以直接调用下一个Agent或中央Server。订阅日志时,可以从Server获取,也可以从中心节点的一些Agent获取。具体如下图所示:知乎还做了一个基于Kids的Web小工具(KidsExplorer),支持实时查看上网日志,现在已经成为调试上网问题最重要的工具。Kids已经开源并放在Github上。事件驱动架构知道这个产品有一个特性。最早添加一个答案后,后面的操作其实只是更新通知和更新动态。但是随着整个功能的增加,更新索引、更新计数、内容审核等操作也多了一些,后续的操作也是五花八门。如果按照传统的方式,维护逻辑会越来越大,可维护性很差。这个场景很适合事件驱动的方式,所以开发组对整个架构进行了调整,做了一个事件驱动的架构。这时候首先需要的是一个消息队列,它应该能够获取各种事件,对一致性要求很高。针对这种需求,知乎开发了一款名为Sink的小工具。它获取到消息后,先保存并持久化到本地,然后再分发消息。如果那台机器挂了,重启时可以完全恢复,保证消息不会丢失。然后通过Miller开发框架将消息放到任务队列中。Sink更像是一个串行的消息订阅服务,但是任务需要并行处理,所以Beanstalkd就派上了用场,可以对任务进行全周期的管理。架构如下图所示:比如用户现在回答一个问题,系统会先把问题写入MySQL,把消息插入到Sink,然后再把问题返回给用户。Sink通过Miller将任务发送给Beanstalkd,Worker可以自行查找并处理任务。刚上线的时候每秒10条消息,后来生成了70个任务。现在每秒100个事件,产生1500个任务,目前的事件驱动架构是支持的。页面渲染优化2013年知乎每天都有几百万的PV,页面渲染其实是一个计算密集型的问题。另外,因为需要获取数据,所以也是IO密集型的。这时,开发组将页面组件化,升级了数据获取机制。知乎根据整个页面组件树的结构,从上到下分层获取数据。当上层数据已经获取到后,下层数据就不需要再往下走了。几个层基本上有几个数据采集。结合这个思路,知乎做了一套模板渲染开发框架——知乎Node。经过一系列的改进,页面的性能有了很大的提升。问题页面从500ms减少到150ms,feed页面从1s减少到600ms。面向服务的架构(SOA)随着知乎的功能越来越复杂,整个系统也越来越庞大。知乎是如何服务的?首先需要一个基本的RPC框架,RPC框架经历了数次演进。第一个版本是Wish,这是一个严格定义的序列化模型。传输层使用的是STP,是我自己写的一个非常简单的传输协议,运行在TCP之上。一开始还不错,因为一开始只写了一两个服务。但是,随着服务的增多,一些问题也开始出现。首先,ProtocolBuffer会生成一些描述代码,放在整个库中非常冗长难看。此外,严格的定义使其使用起来不方便。这时,一位工程师开发了一个新的RPC框架——Snow。它使用简单的JSON进行数据序列化。但是,数据定义松散的问题是,比如一个服务需要升级或者数据结构需要重写,很难知道有哪些服务在使用,也很难通知他们,并且经常发生错误。于是第三个RPC框架就出来了。编写RPC框架的工程师希望结合前两个框架??的特点,一是保持Snow简单,二是要求有相对严格的序列化协议。此版本引入了ApacheAvro。同时,增加了一个特殊的机制。传输层和序列化协议层都是可插入的。JSON和Avro都可以使用。传输层可以使用STP或二进制协议。然后就是服务注册发现,只需要简单的定义服务的名称就可以找到服务在哪台机器上了。同时,知乎也有相应的调优工具,并基于Zipkin开发了自己的Tracing系统。根据调用关系,知乎的服务分为三层:聚合层、内容层和基础层。按属性可分为数据服务、逻辑服务和通道服务三类。数据服务主要是针对特殊数据类型的存储,比如图片服务。逻辑服务是比较CPU密集型和计算密集型的操作,比如应答格式的定义和解析。通道服务的特点是没有存储,更多的是转发,比如sink。这是引入服务后的整体架构。演讲还介绍了基于AngularJS开发知乎栏目的新实践。后续我们会在网站发布演讲视频,敬请期待。