宁夏新闻网

首页 > 正文

知乎已读服务的前生今世与未来

www.mahalosurfing.com2019-08-01

  02:29:45宽哥玩数码

  指南:对于许多大型网站来说,很难实现一些小而微不足道的功能。为了便于了解,随着用户数量和内容数量的增加,读取服务的大小将增加,并且响应时间要求非常短,因此它是一个实现困难的系统。作者介绍了已知服务的体系结构设计和演化过程,并对许多技术选择进行了深入分析,值得一读。

关于作者:知道搜索后端负责人的孙晓光目前负责搜索后端架构的设计和工程团队的管理。多年来,他一直致力于私有云相关产品的开发,专注于云原生技术,TikV项目提交者。

从问答的开始就知道,在过去的8年里,它逐渐成长为一个大型的综合知识内容平台。目前,有多达3000万个问题,已收获超过1.3亿个答案。还存放了大量文章,电子书和其他付费内容。了解如何通过个性化主页推荐有效地在大量信息中分发用户感兴趣的高质量内容。为了避免向用户推荐重复内容,读取服务将存储用户已知的所有内容以进行深度阅读或快速移动,并将该数据应用于主页推荐流程和个性化推送读过滤器。

业务场景,技术挑战

主页读取过滤流程图

从主页上使用读取服务的过程中,我们可以看到该服务的业务模型相对简单。我们只需要查询用户的第一个维度来查询指定的用户是否已读取某个内容。但由于业务的简单性,我们并没有放弃设计的灵活性和普遍性。为此,我们设计并开发了一个CacheThrough缓冲系统RBase,它支持BigTable数据模型来实现读取服务。一方面,它充分利用了Cache的高吞吐量和低延迟能力,另一方面,它还可以利用灵活的BigTable数据模型来协助服务。快速进化。

BigTable数据模型

尽管从业务模型中读取服务非常简单,但其技术挑战并不低。目前,已读取的数据量已超过一万亿,并且以每天近30亿的速度持续增长。与普通的“少读更少写入”业务不同,读取服务不仅需要在这种库存数据规模下提供在线查询服务,还要承受每秒4次新记录写入的影响。读取内容过滤是主页流建议中的关键任务点,对响应时间有很大影响。其可用性和响应时间需要满足非常高的要求。

基于综合业务需求和在线数据,读取服务的要求和挑战主要如下:

●可用性要求“高”:提供个性化主页和个性化推送,最重要的流量分配渠道

●写入量“大”:峰值写入每秒40K +行,每日记录接近30

●记录

●查询吞吐量“高”:在线查询峰值30KQPS/12M +读取检查

●响应时间“敏感”:90ms超时

早期解决方案,架构演变

BloomFilteronRedisCluster

最初,我们在Redis集群上使用BITSET结构来直接存储读取数据的BloomFilter。首先,由于缺少多位操作,操作放大非常严重并且消耗大量计算资源。其次,使用完整内存来存储全部数据也会增加总体成本。最后,很难预测用户的阅读增长率,并且无法根据用户的粒度合理控制BloomFilter和FalsePositiveRate的大小。

HBase的

考虑到BloomFilteronRedis解决方案的问题,我们开始使用HBase存储用户的阅读历史记录并提供在线查询服务。读取的业务需求可以非常直观地映射到BigTable的数据模型。我们使用用户id作为rowkey,将访问的文档id用作限定符,时间戳恰好用于记录文档的读取时间。整个系统的可扩展性和成本明显优于直接使用RedisCluster存储BloomFilter。

随着读取数据量和业务查询量的快速增长,读取数据访问的极其稀疏性已经开始影响HBase的缓存命中率。如果cachemiss需要访问存储,HBase的存储模型有一个很长的IO路径。根据缓冲区渗透的级别,整个请求路径上可能有多个Java进程。任何过程的GC和IO都会对此访问的延迟产生重大影响,从而导致响应时间的大幅波动。响应时间波动在主页上是不可接受的。

HBase缓冲IO路径

在学习了前两代嵌入式架构解决方案的经验之后,我们开始设计新一代的读取就绪服务。在此设计中,我们在可用性,性能和可伸缩性方面设定了更高的目标。我们希望在过去的性能和可扩展性方面取得更多进展。

●高可用性

●?HBase的

●?BloomFilteronRedisCluster

●高性能

●?HBase的

●?BloomFilteronRedisCluster

●易于扩展

●?HBase的

●?BloomFilteronRedisCluster

让我们考虑如何从高可用性,高性能和易扩展的角度构建更好的读取服务,以满足优质企业的需求和挑战。

高可用性

当我们讨论高可用性时,这也意味着我们已经意识到故障一直在发生。依靠传统的手动操作和维护来确保复杂系统的高可用性是不现实的。我们需要系统地检测每个组件的状态以检测它们的故障。我们需要为系统中的组件设计一种自我修复机制,可以在发生故障时自动恢复而无需人工干预。最后,我们还需要隔离由各种故障引起的变化,以便业务方尽可能免于故障的发生和恢复。

故障监控,自动恢复和隔离更改

高性能

对于常见系统,核心组件越多,成本越高,成本越高。层级拦截的快速减少需要对核心组件提出更深层次的请求以提高性能。首先,我们通过缓冲插槽来扩展集群可以缓冲的数据大小。然后,在Slot中,单个Slot缓冲区数据集的读取和吞吐量通过多个副本得到改善,并且在系统的缓冲层中拦截大量请求以进行消化。如果请求不可避免地进入最终数据库组件,我们还可以使用更高效的压缩来继续降低物理设备上的IO压力。

分层同时

易于扩展

提高系统可扩展性的关键是减少有状态组件的范围。借助路由和服务发现组件,可以轻松扩展系统中的无状态组件。因此,通过扩大无状态服务的范围,缩小重型状态服务的比例可以显着帮助我们提高整个系统的可扩展性。除此之外,如果我们可以设计一些可以从外部系统恢复状态的弱状态服务,那么我们通常可以使用弱状态组件来部分替换重状态组件。随着弱状态组件的扩展和重型组件的缩小,整个系统的可扩展性可以进一步提高。

弱状态部分取代重态

RBASE

在高可用性,高性能和易扩展的设计理念下,我们设计并实现了RBase作为读取服务的基础。现在让我们从RBase的全局设计开始,了解高性能,高性能和易于扩展的设计概念是如何落实的。

RBase架构

客户端API和代理是完全无状态和可伸缩的组件。底层是由MHA管理的MySQL集群。可以从数据库或副本中恢复大量弱状态组件。这些弱状态组件的最核心部分是分层缓冲模块。可以从副本或数据库还原这些缓冲区模块的状态,因此它们的可伸缩性仍然非常好。缓冲区外的组件负责管理缓冲区的一致性。在他们的帮助下,缓冲模块可以完全避免无意义的CacheInvalidate来提高缓存命中率,从而大大降低了底层数据库系统的压力。除MySQL集群之外的所有组件(体系结构中唯一的重型组件)都具有自我修复功能。通过自我修复功能和全局故障监控,我们使用Kubernetes管理所有RBase服务组件,以确保整个服务的高可用性。

缓存是RBase设计中非常关键的组成部分。但是,它们的组织管理与常见的缓存系统不同。这些设计差异反映了我们的高绩效思维。现在让我们通过典型的计算机系统内存层设计介绍缓存系统设计的想法。

计算机系统内存分层图

在过去的几十年中,为了提高计算机系统的性能,业界已经为计算机系统增加了更多的CPU和更多的内核。随着CPU的内部处理能力不断提高,我们还为CPU添加了多级缓存,以弥补主现有带宽与CPU延迟之间的巨大差距。除此之外,我们进一步将主存储器与CPU分成组,以便在它们之间实现更快的本地连接,并且仅在需要交叉访问远程存储器时才通过互连总线进行通信。在这样的系统中,大多数读写操作发生在最靠近核心的L1或L2cache中。修改主存储器中的数据时,需要更复杂的机制和更长的时间来实现缓冲区的最终一致性。为了能够将计算机系统的性能带入不断改进的行业架构,设计权衡和智慧在这个架构中无处不在。

我们利用了内存分层设计的灵感,并采用了类似于RBase缓存一致性的设计。数据库层类似于计算机系统内存,类似的分层缓冲区设计,类似的cachethrough和cachecoherence组件设计。通往简单道路的道路是相同的,通过将架构的经典和成熟理念应用于软件系统设计,它可以非常有效。

核心组件。关键设计

让我们仔细研究一下读取系统中某些核心组件的关键设计。

代理

代理负责负载平衡和隔离失败

代理是读取服务的访问层组件。它以传统方式根据用户维度将缓冲区组织到多个槽中。每个槽负责数据集的子集。 Slot中可以有多个副本来共享同一批数据的读取压力。代理将相同的副本绑定到插槽中的同一会话,以确保会话内的一致性。当复制失败时,代理优先选择相同的槽。其他副本继续提出请求。在插槽中的所有副本同时失败的极端情况下,代理还可以选择其他插槽的活动节点来处理用户的请求。这时,我们支付不使用缓冲区来提高性能的成本。在极端情况下交换系统的可用性。

高速缓存

在由“用户”和“内容类型”和“内容”组成的空间中,由于“用户”维度和“内容”维度的基础非常高,因此它们处于数亿个级别,即使记录数量达数万亿。整个三维空间的数据分布仍然非常稀少。单独依赖底层存储系统的能力使得难以在大型和极稀疏的数据集上提供高吞吐量的在线查询,使得难以满足业务的低响应时间要求。此外,大而稀疏分布的数据集对高速缓存系统的资源消耗和命中率提出了巨大挑战。

读取数据的空间分布非常稀疏

件下的缓存命中率。

Cache BloomFilter

有许多方法可以提高缓存命中率。除了可以通过增加上述缓存数据密度来缓冲的增加的数据大小之外,我们还可以通过避免不必要的缓存失效来进一步提高缓存效率。因此,我们将缓存设计为writethroughcache以使用就地更新缓存来避免invalidatecache操作。通过数据更改订阅,我们可以确保相同数据的多个缓冲副本可以在短时间内达到最终协议而不会失败。另一方面,由于通读设计,我们可以将同一数据的多个并发查询请求转换为一个cachemiss加上多个缓冲区读取,从而进一步提高缓存命中率并降低渗透底层数据库系统的压力。

CacheThrough

缓冲系统的核心工作是拦截对大量热数据的访问,因此保持缓冲数据的热量是整个系统稳定性的关键因素。但是,作为一个不断发展和演变的业务系统,当系统滚动升级或复制扩展时,新启动的缓冲节点如何快速预热并进入状态?虽然我们可以选择逐步打开流量到新节点以避免冷缓冲的影响,但是我们也面临失败后自动恢复的情况,当我们没有时间等待新缓冲服务逐渐进入州。考虑到这一点,在新节点启动后,我们将从当前插槽中选择一个活动副本来迁移所有或足够的状态,以允许新节点快速进入工作状态,避免新节点引起的响应时间抖动缓冲不热。

缓冲区状态迁移

之前我们提到过计算机系统中的内存分层设计。接下来,我们将从使用类似分层缓冲区设计的好处的角度讨论将系统引入读取服务。实际上,缓冲系统无论如何提高命中率,我们总是需要面对cachemiss的场景。多层缓冲区的存在允许我们在不同级别的缓存上应用不同的配置策略,尝试进一步减少在每个新引入的缓存级别渗透到下一层的请求数。借助多层缓冲,我们可以让不同的缓冲级别关注来自空间和时间维度的数据热量。我们甚至可以使用多层缓冲机制,通过在多个数据中心部署的情况下进一步增加缓冲层的数量,进一步减少带宽消耗并延迟跨数据中心的数据访问的增加。

跨数据中心部署

作为内容社区,用户的读取数据是非常核心的行为数据。我们不仅对主页的个性化推荐有过滤要求,而且在个性化推送中也有类似的过滤要求。个性化推送是典型的离线任务。查询吞吐量较高,但响应时间可以放宽。虽然它们访问的数据源是相同的,但推送和主页访问的数据在热量分布上有很大不同。为了使服务不相互交互并为不同服务的数据访问特征选择不同的缓冲策略,我们进一步提供了一种用于隔离缓存标签的机制,以隔离来自多个不同商户的离线写入和查询。

业务独立缓冲策略和物理隔离

MySQL的

记录中单个副本的总数据大小。在大约13T时,平均单行记录仅使用10个字节的空间。在这个阶段,我们使用12个节点来传输这些数万亿的读取数据,并且在这样的集群规模下的手动操作和维护几乎是不可接受的。

绩效指标

到2019年初,当前一代的阅读服务已经在互联网上存在了一年多,并且在各种商业指标方面非常令人满意。目前,读取流量已达到每秒40,000行,30,000个独立查询和1200万个文档解释。在这样的压力下,读取服务响应时间的P99和P999仍然稳定在25ms和50ms。

阅读服务核心业务指标

完全阴云密布,面向未来

从阅读的业务指标来看,我们提出了一个满意的答案,但作为阅读服务的开发和运作,我们知道当前架构中仍然存在一些核心难点。第一个是MySQL的操作和维护。我们不仅需要考虑在数据卷继续扩展后重新排序表的可伸缩性问题,还需要考虑整个集群的高可用性以及节点物理故障后的恢复。随着每月数据扩展近1000亿的压力,我们的不安感日益增加。我们迫切需要一个完美的解决方案来解决MySQL集群的运行和维护问题。其次,读取系统的整体架构是针对在线业务而设计的,这使得难以将数据分析直接应用于这种架构。有了这些读取服务的问题,我们在不久的将来开始了新一轮的迭代。这一轮迭代的核心目标是完全覆盖读取服务,以实现全系统高可用性按需扩展的目标。

读取服务最痛苦的MySQL操作和维护问题是由于缺乏单机数据库的可伸缩性和可用性而导致的问题。解决此问题的最直接的解决方案是解决这些问题的本机分布式数据库。幸运的是,近年来,该行业在本地分布式数据库领域取得了很大进展,而CockroachDB和TiDB是该领域的两个优秀的开源项目。虽然它们的具体实施细节和技术路径不同,但它们在总体方向上有许多相似之处。

计算分布式分层数据库模式

考虑到读取服务过去是基于MySQL技术构建的,与MySQL兼容的TiDB比ReadroachDB具有更低的读取服务迁移阈值。此外,作为中国公司,TiDB背后的PingCAP可以在遇到困难时更容易找到帮助。基于这些考虑,我们最终选择使用TiDB作为读取服务的MySQL集群迁移的目标。由于TiDB与MySQL的良好兼容性以及生态工具的改进,整个迁移工作并不复杂。除了大多数工作负载密集型数据迁移工作之外,开发还需要调整CDC组件以匹配TiDBBinlog。

数据迁移

目前,TiDB正式建议使用一站式数据迁移工具DM完成从MySQL到TiDB的全数据迁移和增量数据同步。但是,考虑到准备迁移时读取服务数据已达到1万亿行的规模,使用DM进行逻辑初始完整数据迁移可能是不可接受的。尝试使用DM进行导入测试的结果也从数据中验证,即使不考虑后续数据量变大后可能的速度下降,逻辑上引入初始全部数据至少需要一个月。基于使用DM导入完整数据耗时的估算,我们决定使用TiDBLightning独立迁移全部数据。

MySQL到TiDB数据迁移

目前,TiDBLightning尚未与DM集成,因此整个迁移过程中有许多手动流程。迁移过程需要首先启动DM以在MySQL上收集增量Binlog,然后使用TiDBLightning将历史上的全部数据快速导入TiDB。导入完整数据后,DM负责将完整数据迁移过程中MySQL端生成的增量数据同步到TiDB,并保持双方数据的一致性。整个迁移过程看起来很简单,但实际上我们遇到了一些导致多次导入失败的不一致。

TiDBLightning导入数据的工作实际上需要两个独立的程序,tidb-lightning和tikv-importer,它们都是资源密集型应用程序。在转储开始时,我们可以使用很多服务器进行数据迁移。再加上TiDBLightning的巨大资源需求,我们尝试了各种部署模式,尝试使用最少数量的服务器来完成数据导入。但这些尝试毫无例外地失败了。以下是我们尝试过的部署方法,最终失败了。我希望能帮助你避免同样的问题。

最初,我们尝试在同一台机器上运行多组tidb-lightning和tikv-importer进程,同时将数据转储到多个MySQL上。这种方法最终导致OOMKiller无法终止该进程。接下来,我们尝试在多个tidb-lightning进程之间重用相同的tikv-importer,并使用多个tidb-lightning进程编码数据而不使用tikv-importer功能来提高tikv-importer的利用率。通过错开tidb-lightning进程的启动时间,我们成功地在数据导入的早期阶段提高了tikv-importer服务器的利用率。由于不同tidb-lightning任务的实际执行时间不同,很难防止多个tidb-lightning任务在导入执行后同时向同一个tikv-importer节点提交pour-request节点,导致失败目标服务资源或被OOMKiller杀死的现象。在经历了这些失败之后,我们意识到TiDBLightning惊人的导入速度也对应了非常高的资源需求。为了提高导入数据的速度,我们必须提供足够的硬件资源。认识到这一点,我们调整了策略,并尝试将最初计划部署TiKV的一些节点更改为TiDBLightning数据导入节点。节点减少导致存储空间不足的问题是我们通过暂时将群集的副本数量调整为1来减少TiKV的存储空间需求,并期望在导入完成后将副本数量恢复为3。这次我们成功导入了全部数据并开始使用dm增量来同步MySQL中的新写入。为了避免对实时增量同步的过度影响以及无法跟上MySQL的数据生成速度,我们为复制恢复设置了速度限制。在这种情况下,将45T数据从1个拷贝增加到3个拷贝的过程花费的时间太长,甚至远远超出了我们最初在数据导入中获得的好处。除了完成复制完全恢复之前的时间之外,如果在一个节点中发生任何硬件故障,则可能导致某些区域数据丢失,并且整个迁移过程将被取消。

记录迁移上需要4天才能完成。在dm的帮助下,导入全部数据的4天内生成的所有增量数据都会在不到一天的时间内导入。 16个MySQL实例上新写入的数据将近乎实时地通过dm同步到TiDB。集群即将到来。

业务迁移

在MySQL和TiDB之间的同步稳定后,我们开始了业务流量迁移的工作。与迁移过程类似的切换流量的过程并不总是很顺利。当我们自信地将所有在线100%流量同时切换到TiDB时,我们发现查询的响应时间显着增加,并且业务方的呼叫时间也迅速增加并触发了业务警报。因此,我们将立即将流量滚动到MySQL群集,并开始对陡峭的响应时间问题进行故障排除。

读取服务在cachemiss中仅具有不同的内容,并且在业务需求的数十毫秒内难以完成读取这样的全部数据。一旦我们完成对cachemiss的阻塞查询,我们将立即响应用户的查询请求。读取服务的这种操作模式确定我们的核心优化目标是被阻止的小查询的响应时间。因此,我们将不同查询的响应时间要求划分为具有不同优先级的低/正常/高SQL语句。使用提示告诉TiDB和TiKV为不同的查询使用不同的任务队列,以便在资源级别进行区分。隔离可以避免任务的每个级别受到其他级别任务的影响。此外,我们进一步调整业务端逻辑,通过使用低精度TSO和多路复用PreparedStatement进一步减少同步查询的延迟,进一步减少网络上的往返。

除了之前的在线查询侧逻辑调整之外,我们还需要将MySQLBinlog调整为TiDBBinlog,以确保将更改事件正确推送到订阅者。 TiDBBinlog的开发改编非常简单,只需在kafka上使用消息,并根据protobuf的定义将其转换为我们的内部更改消息格式。在这部分适应工作的验证过程中,我们发现TiDBBinlog只使用Kafka的0号分区来维护事务的全局有序约束。在我们的业务写入吞吐量中,只使用一个分区很容易。很容易超过kafka相应的经纪人。处理能力导致写入和消耗binlog事件的延迟。为此,我们对TiDBBinlog的排水组件进行了一些临时调整。根据配置,我们根据数据库或表选择目标分区来实现平衡负载。在验证过程中,PingCAP学生还根据我们的反馈进行了及时调整。最终启动TiDBBinlog时,TiDBBinlog事件延迟和吞吐量显着增强。

迁移效应

迁移到TiDB后的核心业务指标

经过两个月的迁移和灰度扩展后,读取服务已成功从MySQL迁移到TiDB,核心指标与MySQL保持相似的水平。迁移完成后,读取服务的所有组件都能够以高可用性进行扩展,并为服务主页的未来流量增长提供坚实的基础。

关于TiDB3.0

我们还支持一系列反作弊的风控业务,因为我们知道我们使用的结构和我们读过的结构相同。与读取服务极端的历史数据不同,反作弊业务具有更极端的写入吞吐量,但只需要查询最近48小时的在线存储数据。 TiDB 3.0的一些新功能比反作弊业务有了质的改进,因此我们在TiDB3.0rc1的反作弊业务中将TiDB 3.0引入生产环境,并在rc2之后不久启动了Titan存储。发动机。 TiDB3.0的gRPCBatchMessage和多线程RaftStore增加了集群吞吐量,因此我们可以用更少的资源解决相同的业务问题。 Titan引擎极大地提高了业务读写响应时间的稳定性。除此之外,3.0分区表功能还可以更好地利用业务48小时查询老化的业务特性来提高查询效率。

打开泰坦引擎的效果

在反作弊行业中从TiDB 3.0中获得的红利也非常适用于阅读服务。我们计划在3.0GA之后升级读取服务集群,并期望实现更高的资源效率。

结论

在阅读服务开发和一年多的发展过程中,我们获得了一些经验和教训,以便更好地了解如何支持我们作为支持部门的下游业务。首先,我们必须了解业务需求是为了根据业务的特点来支持系统,但在设计中我们仍然需要抽象出更一般的结构。作为一项支持性业务,我们必须从一开始就关注高可用性,并为业务提供稳定的后方。我们还需要关注高性能可扩展性,以便为未来的业务发展扫清障碍。最后,在这个云原生的时代,即使是商业研发也应该以开放的心态接受新技术,CloudNativefromGroundUp。

高可用性架构

改变互联网的构建方式

指南:对于许多大型网站来说,很难实现一些小而微不足道的功能。为了便于了解,随着用户数量和内容数量的增加,读取服务的大小将增加,并且响应时间要求非常短,因此它是一个实现困难的系统。作者介绍了已知服务的体系结构设计和演化过程,并对许多技术选择进行了深入分析,值得一读。

关于作者:知道搜索后端负责人的孙晓光目前负责搜索后端架构的设计和工程团队的管理。多年来,他一直致力于私有云相关产品的开发,专注于云原生技术,TikV项目提交者。

从问答的开始就知道,在过去的8年里,它逐渐成长为一个大型的综合知识内容平台。目前,有多达3000万个问题,已收获超过1.3亿个答案。还存放了大量文章,电子书和其他付费内容。了解如何通过个性化主页推荐有效地在大量信息中分发用户感兴趣的高质量内容。为了避免向用户推荐重复内容,读取服务将存储用户已知的所有内容以进行深度阅读或快速移动,并将该数据应用于主页推荐流程和个性化推送读过滤器。

业务场景,技术挑战

主页读取过滤流程图

从主页上使用读取服务的过程中,我们可以看到该服务的业务模型相对简单。我们只需要查询用户的第一个维度来查询指定的用户是否已读取某个内容。但由于业务的简单性,我们并没有放弃设计的灵活性和普遍性。为此,我们设计并开发了一个CacheThrough缓冲系统RBase,它支持BigTable数据模型来实现读取服务。一方面,它充分利用了Cache的高吞吐量和低延迟能力,另一方面,它还可以利用灵活的BigTable数据模型来协助服务。快速进化。

BigTable数据模型

尽管从业务模型中读取服务非常简单,但其技术挑战并不低。目前,已读取的数据量已超过一万亿,并且以每天近30亿的速度持续增长。与普通的“少读更少写入”业务不同,读取服务不仅需要在这种库存数据规模下提供在线查询服务,还要承受每秒4次新记录写入的影响。读取内容过滤是主页流建议中的关键任务点,对响应时间有很大影响。其可用性和响应时间需要满足非常高的要求。

基于综合业务需求和在线数据,读取服务的要求和挑战主要如下:

●可用性要求“高”:提供个性化主页和个性化推送,最重要的流量分配渠道

●写入量“大”:峰值写入每秒40K +行,每日记录接近30

●记录

●查询吞吐量“高”:在线查询峰值30KQPS/12M +读取检查

●响应时间“敏感”:90ms超时

早期解决方案,架构演变

BloomFilteronRedisCluster

最初,我们在Redis集群上使用BITSET结构来直接存储读取数据的BloomFilter。首先,由于缺少多位操作,操作放大非常严重并且消耗大量计算资源。其次,使用完整内存来存储全部数据也会增加总体成本。最后,很难预测用户的阅读增长率,并且无法根据用户的粒度合理控制BloomFilter和FalsePositiveRate的大小。

HBase的

考虑到BloomFilteronRedis解决方案的问题,我们开始使用HBase存储用户的阅读历史记录并提供在线查询服务。读取的业务需求可以非常直观地映射到BigTable的数据模型。我们使用用户id作为rowkey,将访问的文档id用作限定符,时间戳恰好用于记录文档的读取时间。整个系统的可扩展性和成本明显优于直接使用RedisCluster存储BloomFilter。

随着读取数据量和业务查询量的快速增长,读取数据访问的极其稀疏性已经开始影响HBase的缓存命中率。如果cachemiss需要访问存储,HBase的存储模型有一个很长的IO路径。根据缓冲区渗透的级别,整个请求路径上可能有多个Java进程。任何过程的GC和IO都会对此访问的延迟产生重大影响,从而导致响应时间的大幅波动。响应时间波动在主页上是不可接受的。

HBase缓冲IO路径

在学习了前两代嵌入式架构解决方案的经验之后,我们开始设计新一代的读取就绪服务。在此设计中,我们在可用性,性能和可伸缩性方面设定了更高的目标。我们希望在过去的性能和可扩展性方面取得更多进展。

●高可用性

●?HBase的

●?BloomFilteronRedisCluster

●高性能

●?HBase的

●?BloomFilteronRedisCluster

●易于扩展

●?HBase的

●?BloomFilteronRedisCluster

让我们考虑如何从高可用性,高性能和易扩展的角度构建更好的读取服务,以满足优质企业的需求和挑战。

高可用性

当我们讨论高可用性时,这也意味着我们已经意识到故障一直在发生。依靠传统的手动操作和维护来确保复杂系统的高可用性是不现实的。我们需要系统地检测每个组件的状态以检测它们的故障。我们需要为系统中的组件设计一种自我修复机制,可以在发生故障时自动恢复而无需人工干预。最后,我们还需要隔离由各种故障引起的变化,以便业务方尽可能免于故障的发生和恢复。

故障监控,自动恢复和隔离更改

高性能

对于常见系统,核心组件越多,成本越高,成本越高。层级拦截的快速减少需要对核心组件提出更深层次的请求以提高性能。首先,我们通过缓冲插槽来扩展集群可以缓冲的数据大小。然后,在Slot中,单个Slot缓冲区数据集的读取和吞吐量通过多个副本得到改善,并且在系统的缓冲层中拦截大量请求以进行消化。如果请求不可避免地进入最终数据库组件,我们还可以使用更高效的压缩来继续降低物理设备上的IO压力。

分层同时

易于扩展

提高系统可扩展性的关键是减少有状态组件的范围。借助路由和服务发现组件,可以轻松扩展系统中的无状态组件。因此,通过扩大无状态服务的范围,缩小重型状态服务的比例可以显着帮助我们提高整个系统的可扩展性。除此之外,如果我们可以设计一些可以从外部系统恢复状态的弱状态服务,那么我们通常可以使用弱状态组件来部分替换重状态组件。随着弱状态组件的扩展和重型组件的缩小,整个系统的可扩展性可以进一步提高。

弱状态部分取代重态

RBASE

在高可用性,高性能和易扩展的设计理念下,我们设计并实现了RBase作为读取服务的基础。现在让我们从RBase的全局设计开始,了解高性能,高性能和易于扩展的设计概念是如何落实的。

RBase架构

客户端API和代理是完全无状态和可伸缩的组件。底层是由MHA管理的MySQL集群。可以从数据库或副本中恢复大量弱状态组件。这些弱状态组件的最核心部分是分层缓冲模块。可以从副本或数据库还原这些缓冲区模块的状态,因此它们的可伸缩性仍然非常好。缓冲区外的组件负责管理缓冲区的一致性。在他们的帮助下,缓冲模块可以完全避免无意义的CacheInvalidate来提高缓存命中率,从而大大降低了底层数据库系统的压力。除MySQL集群之外的所有组件(体系结构中唯一的重型组件)都具有自我修复功能。通过自我修复功能和全局故障监控,我们使用Kubernetes管理所有RBase服务组件,以确保整个服务的高可用性。

缓存是RBase设计中非常关键的组成部分。但是,它们的组织管理与常见的缓存系统不同。这些设计差异反映了我们的高绩效思维。现在让我们通过典型的计算机系统内存层设计介绍缓存系统设计的想法。

计算机系统内存分层图

在过去的几十年中,为了提高计算机系统的性能,业界已经为计算机系统增加了更多的CPU和更多的内核。随着CPU的内部处理能力不断提高,我们还为CPU添加了多级缓存,以弥补主现有带宽与CPU延迟之间的巨大差距。除此之外,我们进一步将主存储器与CPU分成组,以便在它们之间实现更快的本地连接,并且仅在需要交叉访问远程存储器时才通过互连总线进行通信。在这样的系统中,大多数读写操作发生在最靠近核心的L1或L2cache中。修改主存储器中的数据时,需要更复杂的机制和更长的时间来实现缓冲区的最终一致性。为了能够将计算机系统的性能带入不断改进的行业架构,设计权衡和智慧在这个架构中无处不在。

我们利用了内存分层设计的灵感,并采用了类似于RBase缓存一致性的设计。数据库层类似于计算机系统内存,类似的分层缓冲区设计,类似的cachethrough和cachecoherence组件设计。通往简单道路的道路是相同的,通过将架构的经典和成熟理念应用于软件系统设计,它可以非常有效。

核心组件。关键设计

让我们仔细研究一下读取系统中某些核心组件的关键设计。

代理

代理负责负载平衡和隔离失败

代理是读取服务的访问层组件。它以传统方式根据用户维度将缓冲区组织到多个槽中。每个槽负责数据集的子集。 Slot中可以有多个副本来共享同一批数据的读取压力。代理将相同的副本绑定到插槽中的同一会话,以确保会话内的一致性。当复制失败时,代理优先选择相同的槽。其他副本继续提出请求。在插槽中的所有副本同时失败的极端情况下,代理还可以选择其他插槽的活动节点来处理用户的请求。这时,我们支付不使用缓冲区来提高性能的成本。在极端情况下交换系统的可用性。

高速缓存

在由“用户”和“内容类型”和“内容”组成的空间中,由于“用户”维度和“内容”维度的基础非常高,因此它们处于数亿个级别,即使记录数量达数万亿。整个三维空间的数据分布仍然非常稀少。单独依赖底层存储系统的能力使得难以在大型和极稀疏的数据集上提供高吞吐量的在线查询,使得难以满足业务的低响应时间要求。此外,大而稀疏分布的数据集对高速缓存系统的资源消耗和命中率提出了巨大挑战。

读取数据的空间分布非常稀疏

件下的缓存命中率。

Cache BloomFilter

有许多方法可以提高缓存命中率。除了可以通过增加上述缓存数据密度来缓冲的增加的数据大小之外,我们还可以通过避免不必要的缓存失效来进一步提高缓存效率。因此,我们将缓存设计为writethroughcache以使用就地更新缓存来避免invalidatecache操作。通过数据更改订阅,我们可以确保相同数据的多个缓冲副本可以在短时间内达到最终协议而不会失败。另一方面,由于通读设计,我们可以将同一数据的多个并发查询请求转换为一个cachemiss加上多个缓冲区读取,从而进一步提高缓存命中率并降低渗透底层数据库系统的压力。

CacheThrough

缓冲系统的核心工作是拦截对大量热数据的访问,因此保持缓冲数据的热量是整个系统稳定性的关键因素。但是,作为一个不断发展和演变的业务系统,当系统滚动升级或复制扩展时,新启动的缓冲节点如何快速预热并进入状态?虽然我们可以选择逐步打开流量到新节点以避免冷缓冲的影响,但是我们也面临失败后自动恢复的情况,当我们没有时间等待新缓冲服务逐渐进入州。考虑到这一点,在新节点启动后,我们将从当前插槽中选择一个活动副本来迁移所有或足够的状态,以允许新节点快速进入工作状态,避免新节点引起的响应时间抖动缓冲不热。

缓冲区状态迁移

之前我们提到过计算机系统中的内存分层设计。接下来,我们将从使用类似分层缓冲区设计的好处的角度讨论将系统引入读取服务。实际上,缓冲系统无论如何提高命中率,我们总是需要面对cachemiss的场景。多层缓冲区的存在允许我们在不同级别的缓存上应用不同的配置策略,尝试进一步减少在每个新引入的缓存级别渗透到下一层的请求数。借助多层缓冲,我们可以让不同的缓冲级别关注来自空间和时间维度的数据热量。我们甚至可以使用多层缓冲机制,通过在多个数据中心部署的情况下进一步增加缓冲层的数量,进一步减少带宽消耗并延迟跨数据中心的数据访问的增加。

跨数据中心部署

作为内容社区,用户的读取数据是非常核心的行为数据。我们不仅对主页的个性化推荐有过滤要求,而且在个性化推送中也有类似的过滤要求。个性化推送是典型的离线任务。查询吞吐量较高,但响应时间可以放宽。虽然它们访问的数据源是相同的,但推送和主页访问的数据在热量分布上有很大不同。为了使服务不相互交互并为不同服务的数据访问特征选择不同的缓冲策略,我们进一步提供了一种用于隔离缓存标签的机制,以隔离来自多个不同商户的离线写入和查询。

业务独立缓冲策略和物理隔离

MySQL的

记录中单个副本的总数据大小。在大约13T时,平均单行记录仅使用10个字节的空间。在这个阶段,我们使用12个节点来传输这些数万亿的读取数据,并且在这样的集群规模下的手动操作和维护几乎是不可接受的。

绩效指标

到2019年初,当前一代的阅读服务已经在互联网上存在了一年多,并且在各种商业指标方面非常令人满意。目前,读取流量已达到每秒40,000行,30,000个独立查询和1200万个文档解释。在这样的压力下,读取服务响应时间的P99和P999仍然稳定在25ms和50ms。

阅读服务核心业务指标

完全阴云密布,面向未来

从阅读的业务指标来看,我们提出了一个满意的答案,但作为阅读服务的开发和运作,我们知道当前架构中仍然存在一些核心难点。第一个是MySQL的操作和维护。我们不仅需要考虑在数据卷继续扩展后重新排序表的可伸缩性问题,还需要考虑整个集群的高可用性以及节点物理故障后的恢复。随着每月数据扩展近1000亿的压力,我们的不安感日益增加。我们迫切需要一个完美的解决方案来解决MySQL集群的运行和维护问题。其次,读取系统的整体架构是针对在线业务而设计的,这使得难以将数据分析直接应用于这种架构。有了这些读取服务的问题,我们在不久的将来开始了新一轮的迭代。这一轮迭代的核心目标是完全覆盖读取服务,以实现全系统高可用性按需扩展的目标。

读取服务最痛苦的MySQL操作和维护问题是由于缺乏单机数据库的可伸缩性和可用性而导致的问题。解决此问题的最直接的解决方案是解决这些问题的本机分布式数据库。幸运的是,近年来,该行业在本地分布式数据库领域取得了很大进展,而CockroachDB和TiDB是该领域的两个优秀的开源项目。虽然它们的具体实施细节和技术路径不同,但它们在总体方向上有许多相似之处。

计算分布式分层数据库模式

考虑到读取服务过去是基于MySQL技术构建的,与MySQL兼容的TiDB比ReadroachDB具有更低的读取服务迁移阈值。此外,作为中国公司,TiDB背后的PingCAP可以在遇到困难时更容易找到帮助。基于这些考虑,我们最终选择使用TiDB作为读取服务的MySQL集群迁移的目标。由于TiDB与MySQL的良好兼容性以及生态工具的改进,整个迁移工作并不复杂。除了大多数工作负载密集型数据迁移工作之外,开发还需要调整CDC组件以匹配TiDBBinlog。

数据迁移

目前,TiDB正式建议使用一站式数据迁移工具DM完成从MySQL到TiDB的全数据迁移和增量数据同步。但是,考虑到准备迁移时读取服务数据已达到1万亿行的规模,使用DM进行逻辑初始完整数据迁移可能是不可接受的。尝试使用DM进行导入测试的结果也从数据中验证,即使不考虑后续数据量变大后可能的速度下降,逻辑上引入初始全部数据至少需要一个月。基于使用DM导入完整数据耗时的估算,我们决定使用TiDBLightning独立迁移全部数据。

MySQL到TiDB数据迁移

目前,TiDBLightning尚未与DM集成,因此整个迁移过程中有许多手动流程。迁移过程需要首先启动DM以在MySQL上收集增量Binlog,然后使用TiDBLightning将历史上的全部数据快速导入TiDB。导入完整数据后,DM负责将完整数据迁移过程中MySQL端生成的增量数据同步到TiDB,并保持双方数据的一致性。整个迁移过程看起来很简单,但实际上我们遇到了一些导致多次导入失败的不一致。

TiDBLightning导入数据的工作实际上需要两个独立的程序,tidb-lightning和tikv-importer,它们都是资源密集型应用程序。在转储开始时,我们可以使用很多服务器进行数据迁移。再加上TiDBLightning的巨大资源需求,我们尝试了各种部署模式,尝试使用最少数量的服务器来完成数据导入。但这些尝试毫无例外地失败了。以下是我们尝试过的部署方法,最终失败了。我希望能帮助你避免同样的问题。

最初,我们尝试在同一台机器上运行多组tidb-lightning和tikv-importer进程,同时将数据转储到多个MySQL上。这种方法最终导致OOMKiller无法终止该进程。接下来,我们尝试在多个tidb-lightning进程之间重用相同的tikv-importer,并使用多个tidb-lightning进程编码数据而不使用tikv-importer功能来提高tikv-importer的利用率。通过错开tidb-lightning进程的启动时间,我们成功地在数据导入的早期阶段提高了tikv-importer服务器的利用率。由于不同tidb-lightning任务的实际执行时间不同,很难防止多个tidb-lightning任务在导入执行后同时向同一个tikv-importer节点提交pour-request节点,导致失败目标服务资源或被OOMKiller杀死的现象。在经历了这些失败之后,我们意识到TiDBLightning惊人的导入速度也对应了非常高的资源需求。为了提高导入数据的速度,我们必须提供足够的硬件资源。认识到这一点,我们调整了策略,并尝试将最初计划部署TiKV的一些节点更改为TiDBLightning数据导入节点。节点减少导致存储空间不足的问题是我们通过暂时将群集的副本数量调整为1来减少TiKV的存储空间需求,并期望在导入完成后将副本数量恢复为3。这次我们成功导入了全部数据并开始使用dm增量来同步MySQL中的新写入。为了避免对实时增量同步的过度影响以及无法跟上MySQL的数据生成速度,我们为复制恢复设置了速度限制。在这种情况下,将45T数据从1个拷贝增加到3个拷贝的过程花费的时间太长,甚至远远超出了我们最初在数据导入中获得的好处。除了完成复制完全恢复之前的时间之外,如果在一个节点中发生任何硬件故障,则可能导致某些区域数据丢失,并且整个迁移过程将被取消。

记录迁移上需要4天才能完成。在dm的帮助下,导入全部数据的4天内生成的所有增量数据都会在不到一天的时间内导入。 16个MySQL实例上新写入的数据将近乎实时地通过dm同步到TiDB。集群即将到来。

业务迁移

在MySQL和TiDB之间的同步稳定后,我们开始了业务流量迁移的工作。与迁移过程类似的切换流量的过程并不总是很顺利。当我们自信地将所有在线100%流量同时切换到TiDB时,我们发现查询的响应时间显着增加,并且业务方的呼叫时间也迅速增加并触发了业务警报。因此,我们将立即将流量滚动到MySQL群集,并开始对陡峭的响应时间问题进行故障排除。

读取服务在cachemiss中仅具有不同的内容,并且在业务需求的数十毫秒内难以完成读取这样的全部数据。一旦我们完成对cachemiss的阻塞查询,我们将立即响应用户的查询请求。读取服务的这种操作模式确定我们的核心优化目标是被阻止的小查询的响应时间。因此,我们将不同查询的响应时间要求划分为具有不同优先级的低/正常/高SQL语句。使用提示告诉TiDB和TiKV为不同的查询使用不同的任务队列,以便在资源级别进行区分。隔离可以避免任务的每个级别受到其他级别任务的影响。此外,我们进一步调整业务端逻辑,通过使用低精度TSO和多路复用PreparedStatement进一步减少同步查询的延迟,进一步减少网络上的往返。

除了之前的在线查询侧逻辑调整之外,我们还需要将MySQLBinlog调整为TiDBBinlog,以确保将更改事件正确推送到订阅者。 TiDBBinlog的开发改编非常简单,只需在kafka上使用消息,并根据protobuf的定义将其转换为我们的内部更改消息格式。在这部分适应工作的验证过程中,我们发现TiDBBinlog只使用Kafka的0号分区来维护事务的全局有序约束。在我们的业务写入吞吐量中,只使用一个分区很容易。很容易超过kafka相应的经纪人。处理能力导致写入和消耗binlog事件的延迟。为此,我们对TiDBBinlog的排水组件进行了一些临时调整。根据配置,我们根据数据库或表选择目标分区来实现平衡负载。在验证过程中,PingCAP学生还根据我们的反馈进行了及时调整。最终启动TiDBBinlog时,TiDBBinlog事件延迟和吞吐量显着增强。

迁移效应

迁移到TiDB后的核心业务指标

经过两个月的迁移和灰度扩展后,读取服务已成功从MySQL迁移到TiDB,核心指标与MySQL保持相似的水平。迁移完成后,读取服务的所有组件都能够以高可用性进行扩展,并为服务主页的未来流量增长提供坚实的基础。

关于TiDB3.0

我们还支持一系列反作弊的风控业务,因为我们知道我们使用的结构和我们读过的结构相同。与读取服务极端的历史数据不同,反作弊业务具有更极端的写入吞吐量,但只需要查询最近48小时的在线存储数据。 TiDB 3.0的一些新功能比反作弊业务有了质的改进,因此我们在TiDB3.0rc1的反作弊业务中将TiDB 3.0引入生产环境,并在rc2之后不久启动了Titan存储。发动机。 TiDB3.0的gRPCBatchMessage和多线程RaftStore增加了集群吞吐量,因此我们可以用更少的资源解决相同的业务问题。 Titan引擎极大地提高了业务读写响应时间的稳定性。除此之外,3.0分区表功能还可以更好地利用业务48小时查询老化的业务特性来提高查询效率。

打开泰坦引擎的效果

在反作弊行业中从TiDB 3.0中获得的红利也非常适用于阅读服务。我们计划在3.0GA之后升级读取服务集群,并期望实现更高的资源效率。

结论

在阅读服务开发和一年多的发展过程中,我们获得了一些经验和教训,以便更好地了解如何支持我们作为支持部门的下游业务。首先,我们必须了解业务需求是为了根据业务的特点来支持系统,但在设计中我们仍然需要抽象出更一般的结构。作为一项支持性业务,我们必须从一开始就关注高可用性,并为业务提供稳定的后方。我们还需要关注高性能可扩展性,以便为未来的业务发展扫清障碍。最后,在这个云原生的时代,即使是商业研发也应该以开放的心态接受新技术,CloudNativefromGroundUp。

高可用性架构

改变互联网的构建方式

热门浏览
热门排行榜
热门标签
日期归档