知乎已读服务架构如何实现高可用、高扩展、去并发?

阅读数:3083 2019 年 5 月 8 日

知乎是一个问答社区和知识分享平台,通过个性化首页推荐的方式在海量的信息中高效分发用户感兴趣的优质内容。为了避免给用户推荐重复的内容,知乎设计了已读服务功能,用于过滤用户读过的内容。随着知乎上用户和问答的增多,已读的数据规模也在高速增长。如何设计已读服务架构,保证其在大规模数据的冲击下,实现高可用、高扩展和去并发?如何设计缓存系统,降低资源消耗?InfoQ 记者本次采访到知乎搜索后端负责人孙晓光,请他来聊聊知乎的已读服务架构。另外,孙老师将在 QCon 全球软件开发大会(广州站)分享题为「知乎首页已读数据万亿规模下高吞吐低时延查询系统架构设计」,感兴趣的同学可以重点关注下。

InfoQ:介绍下知乎的已读服务?它用于哪些业务场景?

孙晓光:知乎从问答起步,在过去的 8 年中逐步成长为一个大规模的综合性知识内容平台,目前,知乎上有多达 2800 万个问题,共收获了超过 1.3 亿个回答,同时知乎还沉淀了数量众多的文章、电子书以及其他付费内容。知乎通过个性化首页推荐的方式在海量的信息中高效分发用户感兴趣的优质内容。为了避免给用户推荐重复的内容,已读服务会将所有知乎站上用户深入阅读或快速掠过的内容长期保存,并将这些数据应用于首页推荐信息流和个性化推送的已读过滤。

首页已读过滤流程示意图

虽然已读服务的业务模式较为简单,但我们并没因为业务简单就在设计上放弃了灵活性和普适性。我们设计开发了一套支持 BigTable 数据模型的 Cache Through 缓冲系统 RBase 来实现已读服务,一方面充分利用 Cache 的高吞吐低时延能力,另一方面还可以利用灵活的 BigTable 数据模型来辅助业务快速演进。

RBase 数据模型

InfoQ:之前面临哪些业务挑战,如何设计已读服务架构?是如何做到高可用、高性能、高扩展的?

孙晓光:目前知乎已读的数据规模已超万亿并以每天接近 30 亿的速度持续高速增长。与常见的“读多写少”的业务不同,已读服务不仅需要在这样的存量数据规模下提供在线查询服务,还同时承载着每秒 4 万条新纪录写入的冲击。已读内容过滤作为首页信息流推荐中对响应时间影响较大的关键任务点,它的可用性和响应时间都需要满足非常高的要求。

综合业务需求和线上数据来看,已读服务的要求和挑战主要有以下几点:

  • 历史数据长期保留,数据规模庞大,目前已超过一万亿条记录;
  • 业务写入量大,业务长期保持每秒 4 万行新纪录的写入,日新增记录约 30 亿条;
  • 查询吞吐高,峰值每秒 3 万个查询,平均每个查询包含 400 个文档;
  • 低且稳定的响应时间,目前服务 P99 分位线稳定维持在 24 ms ,P999 则维持在 45 ms;
  • 可用性要求高,服务对象首页信息流作为知乎第一大流量入口是公司最重要的业务之一。

下面我们来看下在应对业务的挑战时,已读服务的设计在「高可用」「可扩展」和「去并发」三个角度的思考。

  • 高可用

在我们谈高可用的时候,我们无时无刻不在面对各种故障。显然,让系统拥有自愈的能力和机制是面对故障时依旧保持高可用的根本。无状态的服务的恢复相对简单,只需自愈机制将故障服务重启或迁移到正常节点。而对于有状态的服务,如果状态是可以恢复的,不论是从更底层的存储系统恢复状态还是利用副本机制从其他副本恢复,那么自愈机制同样可以维持有状态服务的高可用。最后我们还希望隔离各种故障所产生的变化,让业务端尽可能感知不到故障恢复前后系统所发生的各种微妙变化。

  • 可扩展

在一个系统里无状态的部分通常是最容易扩展的,在服务发现和路由机制的帮助下无状态的服务可以非常容易地横向扩展到更多的节点上。尽量消除组件的状态可以帮助我们提升整个系统的可扩展性。但业务是多样的,系统也是复杂的,不可能理想化地只包含无状态的组件。在这种情况下我们应当收拢状态,减少需要维护的强状态组件。如果能进一步将有状态的服务调整为可从外部系统恢复的弱状态服务,对整个系统的可扩展性同样能起到非常正面的作用。

  • 去并发

通常业务系统越往核心组件走状态越重扩展的代价也越大,层层拦截快速降低需要深入到核心组件的并发请求量在大型系统设计上是非常常见的。在已读服务中采用了两个常见的分层去并发的设计。首先是高效率的缓存,通过提高缓存的命中率我们将大量的业务请求拦截在系统最薄弱的数据库层以外。其次是数据压缩机制,通过使用高效率的压缩机制来平衡计算和存储的消耗降低最终落到物理存储设备上的 I/O 压力。

RBase 架构图

基于「高可用」「可扩展」和「去并发」这三个大方向的思考,我们从设计最初就以分布式多副本无单点的目标架构来设计已读服务。整体架构设计中最贴近调用方的部分都是无状态的,而中间层的大量组件都是前面提到的弱状态组件,系统中强状态的组件只有关系数据库。我们使用 Kubernetes 编排所有无状态和弱状态组件,借助 Kubernetes 为整个系统提供故障恢复的机制,整个系统中除了关系数据库之外的所有组件都直接借助 Kubernetes 满足了高可用和可扩展。在关系数据库这里,我们初期采用了 MHA 的方式来保证它的高可用,但可扩展性仍然依赖于人工的运维操作。近期我们通过将 MySQL 替换成协议兼容的 TiDB ,在数据库层面实现了真正意义上的高可用和可扩展,补全了已读服务在高可用和高可扩展性上最后的一个短板。除此以外得益于灵活的多层缓冲架构和数据更新通知设计,已读服务的缓冲命中率可以长期维持在 99% 以上,极大地降低了传导到数据库集群的压力提升了系统的整体性能。

InfoQ:您在 QCon 全球软件开发大会(广州站)的演讲提纲中有提到「缓存系统则是万亿规模数据集高吞吐低时延的关键点」,那么是如何设计缓存系统的?期间遇到过什么样的技术挑战?

孙晓光:在由「用户」和「内容类型」和「内容」所组成的空间中,由于用户维度和内容维度的基数非常高,都在数亿级别,即使记录数在一万亿这样的数量级下,数据在整个三维空间内的分布依然非常稀疏。单纯依靠底层存储系统的能力很难在尺寸巨大且极度稀疏的数据集上提供高吞吐的在线查询,更难以满足业务对低响应时间的要求。尺寸巨大且分布稀疏的数据集对缓存系统的资源消耗和命中率的也提了巨大的挑战。

利用 BloomFilter 增加数据密度

考虑到首页推荐业务可以容忍极少量未读过的内容被判已读,我们首先采用将数据通过缓冲 BloomFilter (而非原始数据)的方式增加了缓冲数据的致密程度从而降低了资源消耗;进一步通过 Cache Through 的设计避免了不必要的 Cache Invalidation 操作,从而提升了 Cache 命中率。已读服务的缓存设计同典型业务系统的缓冲设计不同,前者对系统实现提出了更高的要求。除了 Cache Through 的设计之外,分级多副本缓存以及副本故障迁移等特性都加大了系统实现的复杂度。在我们克服了这些困难和挑战之后,最终交付的已读服务从各方面表现来看都非常理想,为首页和推送业务的快速发展解决了后顾之忧。

InfoQ:看到您提到了云原生数据库的迁移代价,聊一聊这方面的内容?现在你们采用的是哪种数据库?选择这种数据库的原因是什么?

孙晓光:已读服务对底层物理存储的功能需求并不高,但我们对它的可靠性可扩展性有着非常高的要求,除此以外考虑到已读数据量庞大且增长迅速,我们还对它的空间消耗有一定的要求。考虑到维护成本和相关生态成熟度,我们根据公司当时的技术栈特点选择了 MySQL 作为数据的物理存储系统,采用了一系列成熟的方案来提升 MySQL 的可靠性、扩展性和空间效率。

  • 使用 MHA 配合 MySQL Semi-Sync 来搭建 MySQL 集群,通过多副本机制保障数据安全性和系统服务的可用性;
  • 采用 TokuDB 作为存储引擎,TokuDB 的高压缩比极大降低了空间消耗提升了资源利用率;
  • 采用分库分表机制为未来集群扩展保留足够的空间。

随着业务的高速发展,已读业务的每日新数据写入量也持续快速增长。目前已读服务每日新增的已读记录已接近 30 亿条,如果按照产品定义,需要保存 3 年已读历史记录供首页过滤来看,在写入量不再增长的前提下数据最终规模也将超过 3 万亿条。即便有 MySQL TokuDB 引擎极高压缩比的支持,在不考虑多副本的情况下最终落地的单一副本数据尺寸也将达到 45 TB 的规模。在这样一个规模下运维 MySQL 的分库分表方案无论是从工作量还是运维的风险上考虑,都是不可忽略的。基于这些考虑我们做出了向云原生数据库迁移的尝试,我们调研了目前较为流行的开源云原生关系数据库 CockroachDB 和 TiDB,在功能特性角度都能满足需求的前提下,考虑到我们已经使用了 MySQL 作为已读存储,和 MySQL 协议兼容的 TiDB 就非常有优势了。并且 TiDB 的开发者在中国,这极大降低了我们遇到问题时寻求帮助的难度。综合考虑后我们开始测试迁移数据到 TiDB。整个迁移测试过程大约耗时一个半月,期间我们的主要工作包括:

  • 使用 TiDB-Lightning 迁移全量历史数据;
  • 使用 DM 保持 MySQL 集群同 TiDB 集群的数据同步;
  • 调优 TiDB 和 TiKV 的参数设置以适应已读的 workload;
  • 移植 MySQL Binlog 到 TiDB Binlog。

在整个迁移工作完成后,之前所面临的困难和得到了极大的缓解。整个已读服务中最重要的,也是状态最重的组件在机制上也有了高可用、高性能和高扩展性的保障。

InfoQ:目前设计的架构还有哪些缺陷或者是不够完美的地方,打算如何解决呢?

孙晓光:目前已读服务中所积累的已读数据除了可以被应用到在线过滤的场景之外,这些数据的潜在价值也是很值得挖掘的。目前已读系统的全盘设计都是以面向在线查询为主,数据分析的能力在现在的系统中是缺失的。在迁移数据库到 TiDB 后我们很期望可以在 TiDB 3.0 发布后利用 TiFlash 的离线分析能力来进一步挖掘并释放已读系统的能力和价值

嘉宾简介:
孙晓光,知乎搜索后端负责人。目前承担知乎搜索后端架构设计以及工程团队的管理工作。曾多年从事私有云相关产品开发工作关注云原生技术,TiKV 项目 Committer。

5 月 25-28 日,QCon 全球软件开发大会广州站,孙晓光老师将会现场进行【知乎首页已读数据万亿规模下高吞吐低时延查询系统架构设计】相关内容的分享,通过深度讲解知乎关于万亿级数据规模的高吞吐低时延的相关实践,以期为现场观众开拓有关高可用性能架构问题的另一种思路。

收藏

评论

微博

发表评论

注册/登录 InfoQ 发表评论

最新评论

EricZ炜韬 2019 年 05 月 10 日 14:12 0 回复
利用 BloomFilter 增加数据密度 能详细再解释一下吗?
没有更多了