在2010年10月真正开始动手作知乎这个产品时,包含李申申在内,最初只有两位工程师;到2010年12月份上线时,工程师是四个。node
知乎的主力开发语言是Python。由于Python简单且强大,可以快速上手,开发效率高,并且社区活跃,团队成员也比较喜欢。git
知乎使用的是Tornado框架。由于它支持异步,很适合作实时Comet应用,并且简单轻量,学习成本低,再就是有FriendFeed 的成熟案例,Facebook 的社区支持。知乎的产品有个特性,就是但愿跟浏览器端创建一个长链接,便于实时推送Feed和通知,因此Tornado比较合适。github
最初整个团队的精力所有放在产品功能的开发上,而其余方面,基本上能节约时间、能省的都用最简单的方法来解决,固然这在后期也带来了一些问题。web
最初的想法是用云主机,节省成本。知乎的第一台服务器是512MB内存的Linode主机。可是网站上线后,内测受欢迎程度超出预期,不少用户反馈网站很慢。跨国网络延迟比想象的要大,特别是国内的网络不均衡,全国各地用户访问的状况都不太同样。这个问题,再加上当时要作域名备案,知乎又回到了本身买机器找机房的老路上。数据库
买了机器、找了机房以后又遇到了新的问题,服务常常宕掉。当时服务商的机器内存老是出问题,动不动就重启。终于有一次机器宕掉起不来了,这时知乎就作了Web和数据库的高可用。创业就是这样一个状况,永远不知道明早醒来的时候会面临什么样的问题。浏览器
这是当时那个阶段的架构图,Web和数据库都作了主从。当时的图片服务托管在又拍云上。除了主从,为了性能更好还作了读写分离。为解决同步问题,又添加了一个服务器来跑离线脚本,避免对线上服务形成响应延迟。另外,为改进内网的吞吐量延迟,还更换了设备,使整个内网的吞吐量翻了20倍。服务器
在2011年上半年时,知乎对Redis已经很依赖。除了最开始的队列、搜索在用,后来像Cache也开始使用,单机存储成为瓶颈,因此引入了分片,同时作了一致性。网络
知乎团队是一个很相信工具的团队,相信工具能够提高效率。工具实际上是一个过程,工具并无所谓的最好的工具,只有最适合的工具。并且它是在整个过程当中,随着整个状态的变化、环境的变化在不断发生变化的。知乎本身开发或使用过的工具包括Profiling(函数级追踪请求,分析调优)、Werkzeug(方便调试的工具)、Puppet(配置管理)和Shipit(一键上线或回滚)等。数据结构
知乎最初是邀请制的,2011年下半年,知乎上线了申请注册,没有邀请码的用户也能够经过填写一些资料申请注册知乎。用户量又上了一个台阶,这时就有了一些发广告的帐户,须要扫除广告。日志系统的需求提上日程。架构
这个日志系统必须支持分布式收集、集中存储、实时、可订阅和简单等特性。当时调研了一些开源系统,好比Scribe整体不错,可是不支持订阅。Kafka是Scala开发的,可是团队在Scala方面积累较少,Flume也是相似,并且比较重。因此开发团队选择了本身开发一个日志系统——Kids(Kids Is Data Stream)。顾名思义,Kids是用来聚集各类数据流的。
Kids参考了Scribe的思路。Kdis在每台服务器上能够配置成Agent或Server。Agent直接接受来自应用的消息,把消息聚集以后,能够打给下一个Agent或者直接打给中心Server。订阅日志时,能够从Server上获取,也能够从中心节点的一些Agent上获取。
具体细节以下图所示:
知乎还基于Kids作了一个Web小工具(Kids Explorer),支持实时看线上日志,如今已经成为调试线上问题最主要的工具。
Kids已经开源,放到了Github上。
知乎这个产品有一个特色,最先在添加一个答案后,后续的操做其实只有更新通知、更新动态。可是随着整个功能的增长,又多出了一些更新索引、更新计数、内容审查等操做,后续操做五花八门。若是按照传统方式,维护逻辑会愈来愈庞大,维护性也会很是差。这种场景很适合事件驱动方式,因此开发团队对整个架构作了调整,作了事件驱动的架构。
这时首先须要的是一个消息队列,它应该能够获取到各类各样的事件,并且对一致性有很高的要求。针对这个需求,知乎开发了一个叫Sink的小工具。它拿到消息后,先作本地的保存、持久化,而后再把消息分发出去。若是那台机器挂掉了,重启时能够完整恢复,确保消息不会丢失。而后它经过Miller开发框架,把消息放到任务队列。Sink更像是串行消息订阅服务,但任务须要并行化处理, Beanstalkd就派上了用场,由其对任务进行全周期管理。架构以下图所示:
举例而言,若是如今有用户回答了问题,首先系统会把问题写到MySQL里面,把消息塞到Sink,而后把问题返回给用户。Sink经过Miller把任务发给 Beanstalkd,Worker本身能够找到任务并处理。
最开始上线时,每秒钟有10个消息,而后有70个任务产生。如今每秒钟有100个事件,有1500个任务产生,就是经过如今的事件驱动架构支撑的。
知乎在2013年时天天有上百万的PV,页面渲染实际上是计算密集型的,另外由于要获取数据,因此也有IO密集型的特色。这时开发团队就对页面进行了组件化,还升级了数据获取机制。知乎按照整个页面组件树的结构,自上而下分层地获取数据,当上层的数据已经获取了,下层的数据就不须要再下去了,有几层基本上就有几回数据获取。
结合这个思路,知乎本身作了一套模板渲染开发框架——ZhihuNode。
经历了一系列改进以后,页面的性能大幅度提高。问题页面从500ms 减小到150ms,Feed页面从1s减小到600ms。
随着知乎的功能愈来愈庞杂,整个系统也愈来愈大。知乎是怎么作的服务化呢?
首先须要一个最基本的RPC框架,RPC框架也经历了好几版演进。
初版是Wish,它是一个严格定义序列化的模型。传输层用到了STP,这是本身写的很简单的传输协议,跑在TCP上。一开始用的还不错,由于一开始只写了一两个服务。可是随着服务增多,一些问题开始出现,首先是ProtocolBuffer会 生成一些描述代码,很冗长,放到整个库里显得很丑陋。另外严格的定义使其不便使用。这时有位工程师开发了新的RPC框架——Snow。它使用简单的JSON作数据序列化。可是松散的数据定义面对的问题是,好比说服务要去升级,要改写数据结构,很难知道有哪几个服务在使用,也很难通知它们,每每错误就发生了。因而又出了第三个RPC框架,写RPC框架的工程师,但愿结合前面两个框架的特色,首先保持Snow简单,其次须要相对严格的序列化协议。这一版本引入了 Apache Avro。同时加入了特别的机制,在传输层和序列化协议这一层都作成了可插拔的方式,既能够用JSON,也能够用Avro,传输层能够用STP,也能够用二进制协议。
再就是搭了一个服务注册发现,只须要简单的定义服务的名字就能够找到服务在哪台机器上。同时,知乎也有相应的调优的工具,基于Zipkin开发了本身的 Tracing系统。
按照调用关系,知乎的服务分红了3层:聚合层、内容层和基础层。按属性又能够分红3类:数据服务、逻辑服务和通道服务。数据服务主要是一些要作特殊数据类型的存储,好比图片服务。逻辑服务更多的是CPU密集、计算密集的操做,好比答案格式的定义、解析等。通道服务的特色是没有存储,更可能是作一个转发,好比说Sink。
这是引入服务化以后总体的架构。