当业务发展到必定规模,实时数据仓库是一个必要的基础服务。从数据驱动方面考虑,多维实时数据分析系统的重要性也不言而喻。可是当数据量巨大的状况下,拿腾讯看点来讲,一天上报的数据量达到万亿级的规模,要实现极低延迟的实时计算和亚秒级的多维实时查询是有技术挑战的。前端
本文将介绍信息流场景下,腾讯看点的实时数据仓库和多维实时数据分析系统的技术架构。sql
能够先看一下,多维实时数据分析系统能够解决哪些痛点。好比:数据库
在进行开发以前,咱们作了这些调研。 浏览器
1. 离线数据分析平台可否知足这些需求,结论是不能知足。离线数据分析平台不行的缘由以下。缓存
2. 实时数据分析平台的话,事业群内部提供了准实时数据查询的功能,底层技术用的是 Kudu+Impala,Impala 虽然是 MPP 架构的大数据计算引擎,而且访问以列式存储数据的 Kudu。可是对于实时数据分析场景来讲,查询响应的速度和数据的延迟都仍是比较高,查询一次实时 DAU,返回结果耗时至少几分钟,没法提供良好的交互式用户体验。因此(Kudu+Impala)这种通用大数据处理框架的速度优点更多的是相比(Spark+Hdfs)这种离线分析框架来讲的,对于咱们这个实时性要求更高的场景,是没法知足的。服务器
通过刚才的介绍,再来看下咱们这个项目的背景。做者发文的内容被内容中心引入,通过内容审核链路,启用或者下架。启用的内容给到推荐系统和运营系统,而后推荐系统和运营系统将内容进行 C 侧分发。内容分发给 C 侧用户以后,用户会产生各类行为,曝光、点击、举报等,经过埋点上报实时接入到消息队列中。接下来咱们作了两部分工做,就是图中有颜色的这两部分。架构
咱们为何要构建实时数仓,由于原始的上报数据量很是大,一天上报峰值就有上万亿条。并且上报格式混乱。缺少内容维度信息、用户画像信息,下游没办法直接使用。而咱们提供的实时数仓,是根据腾讯看点信息流的业务场景,进行了内容维度的关联,用户画像的关联,各类粒度的聚合,下游能够很是方便的使用实时数据。并发
那就看下咱们多维实时数据分析系统的方案选型,选型咱们对比了行业内的领先方案,选择了最符合咱们业务场景的方案。框架
咱们多维实时数据分析系统分为三大模块分布式
难点主要在前两个模块:实时计算引擎和实时存储引擎。
这几个模块的具体实现,看一下咱们系统的架构设计。
前端采用的是开源组件 Ant Design,利用了 Nginx 服务器,部署静态页面,并反向代理了浏览器的请求到后台服务器上。
后台服务是基于腾讯自研的 RPC 后台服务框架写的,而且会进行一些二级缓存。
实时数仓部分,分为了接入层、实时计算层和实时数仓存储层。
实时存储部分分为实时写入层、OLAP 存储层和后台接口层。
这个系统最复杂的两块,实时计算和实时存储。
先介绍实时计算部分:分为实时关联和实时数仓。
实时维表关联这一块难度在于。百万级/s的实时数据流,若是直接去关联 HBase,1 分钟的数据,关联完 HBase 耗时是小时级的,会致使数据延迟严重。
咱们提出了几个解决方案:
能够看到,优化先后,数据量从百亿级减小到了十亿级,耗时从小时级减小到了数十秒,减小 99%。
实时数仓的难度在于:它处于比较新的领域,而且各个公司各个业务差距比较大,怎么能设计出方便,好用,符合看点业务场景的实时数仓是有难度的。
先看一下实时数仓作了什么,实时数仓对外就是几个消息队列,不一样的消息队列里面存放的就是不一样聚合粒度的实时数据,包括内容 ID、用户ID、C 侧行为数据、B 侧内容维度数据和用户画像数据等。
咱们是怎么搭建实时数仓的,就是上面介绍的实时计算引擎的输出,放到消息队列中保存,能够提供给下游多用户复用。
咱们能够看下,在咱们建设实时数据仓库先后,开发一个实时应用的区别。没有数仓的时候,咱们须要消费千万级/s 的原始队列,进行复杂的数据清洗,而后再进行用户画像关联、内容维度关联,才能拿到符合要求格式的实时数据,开发和扩展的成本都会比较高,若是想开发一个新的应用,又要走一遍这个流程。有了数仓以后,若是想开发内容 ID 粒度的实时应用,就直接申请 TPS 万级/s 的 DWS 层的消息队列。开发成本变低不少,资源消耗小不少,可扩展性也强不少。
看个实际例子,开发咱们系统的实时数据大屏,本来须要进行如上全部操做,才能拿到数据。如今只须要消费 DWS 层消息队列,写一条 Flink SQL 便可,仅消耗 2 个 CPU 核心,1G 内存。
能够看到,以 50 个消费者为例,创建实时数仓先后,下游开发一个实时应用,能够减小 98%的资源消耗。包括计算资源,存储资源,人力成本和开发人员学习接入成本等等。而且消费者越多,节省越多。就拿 Redis 存储这一部分来讲,一个月就能省下上百万人民币。
介绍完实时计算,再来介绍实时存储。
这块分为三个部分来介绍
咱们这里听取的是 Clickhouse 官方的建议,借助 ZK 实现高可用的方案。数据写入一个分片,仅写入一个副本,而后再写 ZK,经过 ZK 告诉同一个分片的其余副本,其余副本再过来拉取数据,保证数据一致性。
这里没有选用消息队列进行数据同步,是由于 ZK 更加轻量级。并且写的时候,任意写一个副本,其它副本都可以经过 ZK 得到一致的数据。并且就算其它节点第一次来获取数据失败了,后面只要发现它跟 ZK 上记录的数据不一致,就会再次尝试获取数据,保证一致性。
数据写入遇到的第一个问题是,海量数据直接写入 Clickhouse 的话,会致使 ZK 的 QPS 过高,解决方案是改用 Batch 方式写入。Batch 设置多大呢,Batch 过小的话缓解不了 ZK 的压力,Batch 也不能太大,否则上游内存压力太大,经过实验,最终咱们选用了大小几十万的 Batch。
第二个问题是,随着数据量的增加,单 QQ 看点的视频内容天天可能写入百亿级的数据,默认方案是写一张分布式表,这就会形成单台机器出现磁盘的瓶颈,尤为是 Clickhouse 底层运用的是 Mergetree,原理相似于 HBase、RocketsDB 的底层 LSM-Tree。在合并的过程当中会存在写放大的问题,加剧磁盘压力。峰值每分钟几千万条数据,写完耗时几十秒,若是正在作 Merge,就会阻塞写入请求,查询也会很是慢。咱们作的两个优化方案:一是对磁盘作 Raid,提高磁盘的 IO;二是在写入以前进行分表,直接分开写入到不一样的分片上,磁盘压力直接变为 1/N。
第三个问题是,虽然咱们写入按照分片进行了划分,可是这里引入了一个分布式系统常见的问题,就是局部的 Top 并不是全局 Top 的问题。好比同一个内容 ID 的数据落在了不一样的分片上,计算全局 Top100 阅读的内容 ID,有一个内容 ID 在分片 1 上是 Top100,可是在其它分片上不是 Top100,致使汇总的时候,会丢失一部分数据,影响最终结果。咱们作的优化是在写入以前加上一层路由,将同一个内容 ID 的记录,所有路由到同一个分片上,解决了该问题。
介绍完写入,下一步介绍 Clickhouse 的高性能存储和查询。
Clickhouse 高性能查询的一个关键点是稀疏索引。稀疏索引这个设计就颇有讲究,设计得好能够加速查询,设计很差反而会影响查询效率。我根据咱们的业务场景,由于咱们的查询大部分都是时间和内容 ID 相关的,好比说,某个内容,过去 N 分钟在各我的群表现如何?我按照日期,分钟粒度时间和内容 ID 创建了稀疏索引。针对某个内容的查询,创建稀疏索引以后,能够减小 99%的文件扫描。
还有一个问题就是,咱们如今数据量太大,维度太多。拿 QQ 看点的视频内容来讲,一天流水有上百亿条,有些维度有几百个类别。若是一次性把全部维度进行预聚合,数据量会指数膨胀,查询反而变慢,而且会占用大量内存空间。咱们的优化,针对不一样的维度,创建对应的预聚合物化视图,用空间换时间,这样能够缩短查询的时间。
分布式表查询还会有一个问题,查询单个内容 ID 的信息,分布式表会将查询下发到全部的分片上,而后再返回查询结果进行汇总。实际上,由于作过路由,一个内容 ID 只存在于一个分片上,剩下的分片都在空跑。针对这类查询,咱们的优化是后台按照一样的规则先进行路由,直接查询目标分片,这样减小了 N-1/N 的负载,能够大量缩短查询时间。并且因为咱们是提供的 OLAP 查询,数据知足最终一致性便可,经过主从副本读写分离,能够进一步提高性能。
咱们在后台还作了一个 1 分钟的数据缓存,针对相同条件查询,后台就直接返回了。
这里再介绍一下咱们的扩容的方案,调研了业内的一些常见方案。
好比 HBase,原始数据都存放在 HDFS 上,扩容只是 Region Server 扩容,不涉及原始数据的迁移。可是 Clickhouse 的每一个分片数据都是在本地,是一个比较底层存储引擎,不能像 HBase 那样方便扩容。
Redis 是哈希槽这种相似一致性哈希的方式,是比较经典分布式缓存的方案。Redis slot 在 Rehash 的过程当中虽然存在短暂的 ask 读不可用,可是整体来讲迁移是比较方便的,从原 h[0]迁移到 h[1],最后再删除 h[0]。可是 Clickhouse 大部分都是 OLAP 批量查询,不是点查,并且因为列式存储,不支持删除的特性,一致性哈希的方案不是很适合。
目前扩容的方案是,另外消费一份数据,写入新 Clickhouse 集群,两个集群一块儿跑一段时间,由于实时数据就保存 3 天,等 3 天以后,后台服务直接访问新集群。
腾讯看点实时数据仓库:DWM 层和 DWS 层,数据延迟 1 分钟。
远见多维实时数据分析系统:亚秒级响应多维条件查询请求,在未命中缓存状况下,过去 30 分钟的查询,99%的请求耗时在 1 秒内;过去 24 小时的查询,90%的请求耗时在 5 秒内,99%的请求耗时在 10 秒内。