数据平台架构设计中,ClickHouse如何完胜Druid?

周路 2022-01-13 10:08:28

导读:本文的主题是基于ClickHouse的广告数据平台架构实践。包括广告业务面临的现状,为什么会使用ClickHouse来提供数据多维分析服务,如何基于ClickHouse的优势和特点在适应亿贝广告业务场景的前提下来设计系统平台架构,实践过程中遇到的技术问题和解决方案,以及数据平台在日常使用、版本迭代、质量监控过程中是如何去做的。

 

具体将围绕以下几部分展开:

 

  • 亿贝广告业务场景介绍

  • 选择ClickHouse

  • 亿贝广告数据平台建设实践

 

一、亿贝广告业务场景介绍

 

 
1、亿贝广告业务场景

 

首先和大家分享下亿贝的广告业务是什么样的。我们这里讨论的主要是亿贝第一方卖家广告业务场景。卖家可以与亿贝合作,通过广告的形式可以给自己商品带来销量,点击,从而提高他们的营业额。

 

亿贝的广告形式主要分为两种,一种是基于销量的方式,每帮助卖家成交一笔订单,亿贝都会从中收取相应的广告费;第二种是基于点击的模式,通过智能推荐的算法可以将不同的商品推荐给不同的用户,然后用户通过点击自己感兴趣的商品形成点击给卖家带来流量,亿贝就会通过这些流量向商家收取一定的广告费用。所以说在这种场景下给商家提供一个广告项目报表就是必不可少的。

 

卖家需要了解他在站点广告投放上的表现是怎么样的,主要会通过点击率或者销售率来反应,这些数据都会实时的发生在个人页面上,可以看到用红框标出来的就是广告商品,当页面上发生了广告商品的点击或者曝光,就会伴随一些商品信息,通过建立的数据管道流到分析引擎,然后通过右边这种图展现给卖家,卖家可以对商品进行各个广告维度的分析,从而来指定下个季度或者下个年度的广告策略以及预算。这个就是主要的业务场景。

 

图片

 

 
2、亿贝广告系统全景图

 

这里是亿贝广告系统架构的全景图。

 

图片

 

可以看到亿贝的广告位主要是出现在首页,搜索页,商品页面,推荐栏,移动端。这部分商品的数据会通过离线和实时两条链路最终流向流入分析引擎,所以亿贝的广告引擎也是典型的lambda架构。

 

为什么会使用离线和实时两条线呢,主要因为电商的数据在时序上会发生一些业务关系,比如一笔订单发生几天后产生退款,这种场景在实时数据流中是比较难处理的,我们会通过离线数据对这种情况进行修正。与此同时,我们也会通过点击对卖家进行收费,这就会产生同行之间的恶性竞争,比如说,卖家会故意点击对手卖家的商品产生不必要的广告费用,我们的系统也会对这种行为进行监控。这种监控逻辑在实时和离线都会存在,但是有一些逻辑还需要离线情况来计算,因此也可以看到,我们是从离线数据看出最终数据一致性的标准。实时数据需要依赖隔天的离线数据进行修正,这就涉及了离线数据的替换和更新问题,这部分我会在之后详细介绍。

 

数据流的计算引擎的分析之后会通过第三方API的方式提供给卖家,同时也会给站内的一些服务提供API接口,也会有UI提供给分析师团队,给他们提供一个市场策略的数据支持。

 

 
3、面临的挑战

 

亿贝的广告场景给研发团队带来的主要技术挑战有:

 

首先,亿贝是一个国际化的电商网站,卖家是分布在世界各地的,所以数据也要根据不同的时区展现不同的报表,这个问题在数据量大的情况下做实时分析不是很容易的,常规的解决方案会带来比较大量的数据冗余,我们也是通过利用ClickHouse的一些特点,解决了这个问题。

 

第二点是数据规模非常大,我们的规则引擎会承接亿贝站上所有的流量,每天会有百亿级的数据插入,这些流量如果直接接入ClickHouse分布式表的话,肯定是不行的,会对ClickHouse分布式服务器带来大的压力,所以我们是通过一套自研的计算引擎来解决这个问题。

 

第三点是数据的实时性要求,这个问题同样会根据数据量的增加而变得棘手。

 

同时由于我们采用的是离线和实时分离lambda 架构,所以离线数据更新的原子性和数据导入的一致性也成为了要解决的问题。

 

最后在业务场景下,也会对数据有各种要求,各种业务要求和规则变更也会给版本的迭代带来一些挑战,我们也是通过一些巧妙的设计解决了这些问题。

 

二、选择ClickHouse

 

 
1、ClickHouse VS Druid

 

图片

 

基于druid做的一个分析引擎,是2012年开源的,是 Apache基金会旗下项目,它一直都是以千亿级数据提供亚秒级的查询延迟而闻名,社区比较活跃,为数据导入提供了许多模版,同时支持内建多种数据源。但是在广告场景下,基于benchmark分析来看,ClickHouse会比druid有许多可取之处。

 

首先是数据存储方面,ClickHouse的数据压缩和列式存储会极大节省存储空间,而druid和其他多数数据库都是基于时序的,druid在查询大范围的数据时会出现性能问题,而利用ClickHouse的分区键优化可以有效的解决这个问题。

 

在查询方面,druid的排序,聚合能力都不太好,灵活性和扩展性也不够,比如缺少join,子查询,主键排序等这些需求。而这些用SQL都可以通过ClickHouse来支持解决。同时ClickHouse也是一个MPP的计算架构,通过主键索引,向量化引擎处理,多处理器并发和分布式查询这些方式,压榨CPU的资源,在数据查询方面具备显著的优势。

 

除此之外,运维成本也是我们主要考虑的范围之一,Druid的运维是非常复杂的,虽然支持多种数据摄入的组件,但是组件的构成是比较复杂的,节点数量有6种之多,而ClickHouse架构采用的是对等节点的设计,节点就有一种类型,没有主从节点,如果使用副本功能也是也是采用外部的Zookeeper来同步数据段,所以说给运维工作提供了不少便利。

 

在数据摄入方面,Druid通过引入实时数据的索引,把实时数据处理成一个个分段,并归并到历史数据,成为分段之后的数据是不能写入的,由于并发实时处理的索引数量是有限的,所以我们就设置了三个小时的时间窗口限制索引的任务量,因此超过三个小时时间窗口的数据就没有办法成功写入,同时上游数据延迟,就会造成实时数据的消费过于滞后,这部分数据在实时管道中就会缺失了,而ClickHouse就没有这些问题,再加上自主研发的计算引擎系统,可以很大程度上提高ClickHouse的数据消费能力,最终决定把分析引擎从Druid切换到ClickHouse。

 

图片

 

这个是ClickHouse和Druid离线数据摄入的时间消耗图对比图。我们是按天来做数据的摄入,每天有百亿级的数据量,可以看到,ClickHouse的数据摄入量时间还是比较稳定的,每天在50分钟就可以完成。而Druid需要更长的时间来完成,它还会受到集群的计算资源的影响,最长可达数十个小时,这对于批量的数据修复和导入工作也会带来一些困难。

 

ClickHouse之所以做到这么快,是因为我们把数据的聚合计算和分片逻辑都搬到了Spark上完成,这就降低了ClickHouse在数据摄入过程中的计算资源上的开销,同时也利用了Spark的并行处理能力,这部分我会在之后详细介绍。同时利用Spark处理的中间结果,我们可以做一个数据质量监控和数据回滚的重要标准,所以说这种设计的方案也算是一个一举两得的方案,数据摄入的时间显著降低给平台的升级和架构都带来了很多便利。虽然ClickHouse可以支持分区的API的操作,通过touch或者detouch的操作完全数据迁移,但是它不能利用数据分区和分片逻辑发生重构的场景下,因此我们自研的这个架构可以承担起数据架构升级和数据迁移的一些重任。以上这些好处都是我们采用ClickHouse之后能够获得的。

 

 
2、表引擎与主键设计

 

我们看一下在亿贝的场景下采用了ClickHouse的哪些存储引擎可以最大限度发挥它的优势。

 

图片

 

由于我们要处理的用户行为数据量非常大,同时这些行为数据通常是在不同维度的聚合表现在报表上,这种聚合主要是一种相加的合并聚合,除此之外为了保证数据的高可用,我们也使用了副本的功能,因此我们使用了ClickHouse的复制汇总合并树来完成这样的存储。我们实时管道会将明细数据插入ClickHouse,然后集群的后台任务会自动将排序键相同并且位于同一分区的数据聚合起来,虽然聚合过程是异步完成的,但是不会影响到我们的查询结果,这样的数据预聚合也极大节省了数据存储量,也节省了存储空间。

 

一般情况下,ClickHouse表的主键和排序键是相同的,但是采用了汇总合并树引擎之后我们可以单独的指定主键,这种场景下我们建议删除不需要排序或者索引功能的维度从主键当中剔除出去,因为主键运行的时候需要全部加载到内存中,尽量减小主键大小,也可以很大程度上提升查询的效率。

 

我们知道ClickHouse是以文件块作为一个单元存储的,从右图可以看出ClickHouse是通过分区管理文件块,索引的过程也是通过分区键先筛选出目标分区,再通过主键和排序键建立序数索引,快速定位到查询语句所要查询的数据段,ClickHouse用排序键索引来进行跳跃的扫描,所以就建议在建表的时候,把记录在业务生命周期中不变的字段放在排序键,通常是distinct(count())的数据量越小,放的越靠前,这对查询有更好的帮助。

 

 
3、压缩与低基 

 

除了结合业务场景选用ClickHouse的合并树家族和主键优化之外,我们还对ClickHouse其他一些压缩算法的特性进行了探索。

 

图片

 

这些压缩算法都能显著地减少源数据压缩存储量,这也是ClickHouse列式存储的一大优势,降低查询IO,列式存储也在数据查询中的不同列选择的压缩算法等级,可以在数据压缩和数据查询效率做一个平衡。

 

ClickHouse默认选择的LZ4的压缩算法,除此之外,一般数据列可以选一些压缩率高的算法,比如ZSDT,LZ4HC,对于类似时间序列单调增长的数据可以选用DoubleDelta, Gorilla这种特殊的压缩算法。在生产数据集上,我们的ZSDT算法对于字符串类型数据压缩效果比较显著,LZ4和LZ4HC是对非字符串类型数据压缩效果比较好,更高的压缩率意味着更小的存储空间,这样可以提高查询的性能,但是对CPU的开销也是很大的,所以数据插入的性能就会受到影响,我们在生产上测试使用了LZ4HC等级6的压缩算法,可以节省30%的存储资源,但是这会对实时数据摄入的效率产生60%左右的降低,所以也需要根据情况选择合适的压缩算法。

 

对于基数比较低的列,尤其是一些枚举列,可以采用Click House的LowCardinality来降低数据的存储,从而降低整体数据的存储,如果采用压缩算法LowCardinality可以将数据空间缩小25%,这也是我们生产数据的测试结果。我们也做过极限测试,使用最大压缩率,再配LowCardinality这种方式,可以将原来数据压缩到整体的13%左右。

 

 
4、商品信息表的实时更新与连接

 

有些场景需要结合商品的详情给卖家展示投放广告的表现,这些场景主要包括比如卖家想要获得某种商品的表现如何,某个价格区间的商品表现如何,这就需要我们对用户的行为数据结合商品的信息筛选和排序。

 

在数据分析领域通常会使用宽表的形式来解决这个问题,但是宽表带来的数据膨胀问题也是比较明显的,相比于商品信息的变更,用户行为数据的变更会更加频繁,这会带来非常大的数据量的冗余。除此之外添加商品信息进入用户行为数据的话,也会给整个系统带来各个方面的依赖,同时附带在用户行为数据的商品状态是行为发生时候的状态,并不能满足我们根据商品的现状来产生报表的需求,所以我们最终也是放弃了这种大宽表的解决方案,用维护一张用户信息表来完成需求。

 

图片

 

亿贝会持续的采集商品的最新状态,这就涉及到数据的更新问题,Click House在计算更新方面做的并不好,同时也不支持事务,所以我们就另辟蹊径,用ClickHouse中的合并家族树中的替换合并树来完成更新的操作,它可以用预先设定好的聚合条件,将排序键相同的记录聚合起来,我们设定了用专门的一列来记录记录的时间戳,聚合规则选择时间戳最新的记录保留下来,这样仅通过插入的方式就可以完成数据的更新。

 

还有个小问题,我们无法保证它已经完成更新了,因为操作都是在后台的任务中完成的,那么同一时刻可能存在排序键相同的两条记录,针对这个问题我们也是采用了客户端的聚合函数argmax,它就好比是替换合并树中客户端的实现,能够将未合并的数据进行合并,保证我们查询商品记录的一致性。除此之外,我们也会周期性的采取OPTIMIZE操作,确保ClickHouse能尽可能多的完成数据的合并。

 

还有一种方案是使用聚合合并树,同样也可以实现数据的更新,它的实现就复杂一些,它要求客户端在数据插入之前能知道当前的状态,因为它的更新操作是通过删除和添加完成的,因此在更新之前,需要知道是添加新记录还是删除老记录的前提下,采取添加新记录,所以这种成本是比较高的,我们最终也没有选择这种聚合合并树的实现方式。

 

 
5、广告数据架构

 

图片

 

我们再来看一下数据是如何分片的。上图可以看到,我们是对数据进行了水平方向和垂直方向的切分,在水平方向是将数据根据天进行分区,以小时为粒度进行聚合,这就使我们可以支持不同时区的数据查询场景,按天分区可以快速的定位到数据的查询分区,这样就能节省不少计算资源,在竖直方向上我们对数据进行了业务逻辑的分片,因为我们的查询大多是依据卖家的表现进行查询的,将数据根据卖家ID进行分片,不同卖家的数据会落到不同的分片上,这样一来对于充分利用分布式集群资源来提供服务是比较有利的,除此之外,我们的点击表,销量表,展示表也是使用的相同的分区键,这样就可以将表之间的join操作分配的本地服务器去完成,节省了网络资源,也可以提升查询效率。

 

 
6、基于广告业务场景的存储优化

 

图片

 

我们的数据架构也是经过了踩坑和更新迭代的过程,存储方面我们是采用了商家ID和卖家ID进行数据分片,主要是为了避免查询过程中的数据热点问题,将查询分配到更多的机器上去完成,这就会有一个问题,高的QPS就会使得数据路由到多个节点上去计算,这就会造成大量的网络开销,分布式节点上的计算资源也会很快耗尽,本地节点的计算资源又没有得到充分的利用,这样导致QPS在几百左右就达到了瓶颈。所以我们要将几个请求集中到少数的服务器上进行计算,从而降低网络开销,充分利用副本节点的资源。

 

第一套方案是单纯基于卖家ID进行数据分片,卖家的查询只会落到一个分片上去计算,QPS得到了显著的提高,这样就很容易到了数千的QPS,因此会带来数据热点问题,对于商品特别多的卖家,比如售卖图书类的卖家,它有上百万的商品(图书)落到节点上会给集群带来热点的压力,虽然QPS高,但是这种极端情况下的延迟高。

 

我们又探讨了折中方案,将卖家的数据在时间维度上进行Hash,创建了一个用于分片的辅助列,用来存放行为数据的年周,年周可能是0-53的数据,将卖家的数据平均分配到53个分片上去,这样就避免了对整体集群的查询,同时也避免了数据热点的问题,相当于是在QPS和延迟两个方面做了权衡,QPS也是达到了数千的要求。

 

三、亿贝广告数据平台建设实践

 

 
1、系统架构

 

图片

 

上图是广告数据平台系统架构的概览。可以看到我们使用了非常多的大数据技术栈。

 

离线部分主要是基于Hadoop生态。离线明细数据会在T+1天的时间就绪,我们用Spark定义了通用数据处理任务,通用任务是可以配置的,这个配置主要是建立HDFS上的字段和ClickHouse表中的字段的关系,当有新的click表需要接入的时候,可以通过配置文件的方式,尽快完成上线。

 

ETL的任务调度我们采用了Spring batch的技术栈,它具备成熟的任务调度面板可以通过UI配置日常任务和数据的修正任务。在Spark任务提交方面我们是采用了LIVY服务器,可以提高job调度管理的效率,也是提供Spark集群交互的API接口,我们能灵活的监控批处理触发的任务, 一般也会有一个Spark Cluster的解决方案, 这种解决方案对系统的耦合度会比较高,像这种方案就可以单独把LIVY Server分离出来,可以服务于其它平台的任务调度需求。

 

在在线数据方面,我们采用的是Kafka生态下的技术栈,通过Flink消费上游kafka Topic中的事件进行数据装配,再通过定制的ClickHouse的JDBC再将数据插入ClickHouse。

 

在离线和实时部分都没有采用ClickHouse原生的JDBC,主要也是为了提高数据的摄入和稳定性,这部分我们也会在之后详细介绍。这里的技术栈主要是使用了kafka和airflow,kafka主要用于实时数据摄入过程中的缓冲和预计算,airflow主要负责离线数据摄入的调度,它以clickhouse的副本节点为单元控制数据摄入的进度,这里需要强调的是,我们把离线数据摄入的调度。业务方面的调度,实际的ClickHouse的数据摄入任务调度解耦开来,主要也是提供一个平台化的服务,这样ClickHouse可以承接除了广告需求之外的其他分析需求的数据摄入,那么我们的数据调度,业务也可以调度一些任务去完成除了ClickHouse之外其他存储的数据摄入的需求。

 

 
2、实时数据写入 

 

这里我们再来介绍一下实时数据摄入是如何实现的。

 

图片

 

数据摄入是kafka搭建的外部计算系统。首先将数据按业务进行分片,由kafka直接写入ClickHouse集群的各个本地表,一个分片上的数据对应kafka的一个主题,为了保证足够的数据吞吐量,kafka每个主题的分区个数都要大于ClickHouse的副本个数,这样做的目的是可以保证,ClickHouse的副本可以并行的形式消费同一主题下的记录,如果kafka的节点或者ClickHouse副本节点产生宕机,kafka集群可以通过rebalance的形式保证系统的高可用,这种方案具备更好地写入性能,因为每个分片的数据都是被并行点对点写入的,将数据吞吐量也是均摊到所有ClickHouse节点上,给我们的系统扩容也带来的极大的灵活性,通过目前的方案我们实现了百万级每秒的数据吞吐量,跟源系统的延时也是控制到秒级。

 

 
3、离线替换系统架构

 

图片

 

上图是我们离线数据摄入的架构图。我们的批处理任务分为两种类型,一种是日常的数据摄入,一种是历史数据的修正,包括数据迁移。用户可以通过批处理控制不同的优先级批处理任务,然后定时的进程会拉起优先级较高的批处理任务优先处理。

 

离线数据摄入的第一步是锁定ClickHouse的space, 在这一阶段click的space确认不会发生表结构或数据分布的变化,那这一操作不会影响实时数据,也不会影响查询。随后批处理任务调度会获取click space的分片信息,并通过LIVY服务器传给ETL任务,然后成功触发ETL任务并且来监控它的状态。Spark任务在这里的作用主要是预分片和预聚合,它会根据预分配好的任务在Spark任务中完成不同维度的数据聚合,并将数据根据分片的信息写到对应的文件目录。从右下角这幅图可以看出,每个文件目录对应几个分片,每个任务会生成文件的详情,就是包含的具体的分片信息,或者对应这个文件下哪些数据,这些数据的一些详情,这些会通过后续的数据导入过程提交的数据摄入任务调度。数据摄入调度会根据这个详情把这些数据摄入任务分配给ClickHouse的每个副本节点,副本节点在接收到任务后,会到指定目录下拉取文件到本地,再通过ClickHouse的client导入数据。

 

我们会将每个分片的数据按照副本的个数进行倍数的均分,使得每个节点都能获得相同数量的导入任务,通过这种方式我们也是将离线数据的摄入吞吐量与系统架构解耦,充分利用Spark并行处理的优势,同时能够通过扩充副本的数量来灵活控制数据摄入的速率。在完成了数据摄入步骤之后,数据摄入任务调度会按照节点来完成数据质量验证,这一环节也是通过比对数据导入之前的输出详情与实际导入的结果完成的,所以我们的离线数据摄入分为两个环节,一个是Spark job的预处理环节,还有一个是ClickHouse的数据摄入,用户可以通过各个带服务的UI来监控任务的进度。

 

 
4、分区数据替换

 

图片

 

现在讲一下离线数据更新是如何保证大规模的数据一致性的。离线数据和实时数据相比会有一天的延时,用户看到最近一天的数据都是实时数据。之前数据架构环节我们提到数据的分区是按天为粒度来进行分区的,因此这种方式也是将实时数据和离线数据隔离开来了。

 

这部分离线数据替换主要是发生副本节点通过ClickHouse的client的数据导入阶段,副本节点首先会将HDFS上的文件下载到本地节点,随后在本地创建一个ClickHouse的临时表,将数据通过ClickHouse的client导入到临时表中,再通过分区的API atouch的detouch将临时表的分区添加进主表,所以我们是叫数据替换是因为我们完成的是分区的替换,而不是数据记录的更新。

 

在这个过程中导入的离线数据对用户目前是不可见的,因为我们是通过版本控制来完成数据可见性的控制,通过上面的过程,我们就完成了离线数据的导入。数据导入过程本身不会花费太长时间,因为并行度比较大,主要的时间消耗是逐表数据的验证上,当数据出现质量问题,我们也会通过重试来完成数据的导入。

 

 
5、数据替换的原子性 

 

图片

 

我们再来看一下不同版本的数据对于用户的可见是如何实现的。因为我们这里处理的是线上系统,ClickHouse又是一个集群的部署方式,这就使得我们不能保证同一时间完成所有节点的数据替换。对于用户而言,是没有办法接受查询结果是不断变的,因此我们就需要做到数据替换过程的原子性。刚才已经介绍了我们如何使用ClickHouse的分区数据来进行插入的,对于数据的可见性,我们使用了ClickHouse的内存字典。

 

字典中包含了两个列,分别是A和B,它们记录了最近两次数据替换之后的数据版本,我们把它称做版本列,另外一列叫active列,它记录了每个分区当前活跃版本所在的版本列。那这样每次我们进行数据替换,数据的版本号替换到非活跃版本列,原子操作完成的主要方式就是将active列指向非活跃的版本列,这样就可以实现数据离线替换的原子性,同时提供了数据版本回退的能力。

 

因为我们有不同版本的数据,一旦线上的监控系统发现有数据问题,也可以在很短的时间回退到上一版本。这里的数据版本是作为一个特殊的列存在于每个数据表中的,查询的时候会指定版本号,可以通过dictGetOrDefault的方式来实现,用ClickHouse的条件优化关键词PREWHERE预先指定版本。这样在查询结果中,就可以只包含活跃的数据。

 

大家可能会注意到我们这种离线数据替换方式会带来一个问题,如果持续替换一个数据分区会引入许多无用的分区数据。在查询过程中,首先会通过PREWHERE扫描活跃的版本,这就意味着版本数越多,扫描时间越长。我们也是设置了一个定时的任务,来定期清理不需要的数据版本。在查询语句上也做了优化,之前是在字典上维护活跃版本的,所查询的数据需要下发到各个节点上才能查到当前数据的活跃版本,后来我们是将字典迁移到分布式数据表中,查询的时候就可以在分布式表中率先获得版本号,再通过GLOBAL IN这种方式下将分区键下发到各个节点。

 

这样做的好处就是利用ClickHouse的分区键索引可以跳过以往的数据版本,直接定位到最新的数据版本,也是通过这种方式,我们的查询延迟也是得到了进一步的优化。

 

 
6、Zookeeper压力问题与解决方案

 

图片

 

由于离线数据和实时数据的摄入都是通过本地表完成的,那数据的全局同步和复制任务主要由Zookeeper来完成,在数据摄入体量很大的情况下,就给Zookeeper带来了巨大的压力。ClickHouse不是一个完全的分布式系统,同时也不是一个典型的MPP架构,它与传统的MPP架构相比缺少了全局的元数据管理,如果想扩展任务到多个节点,还需要借助外部组件的一些协同。ClickHouse的协同能力是比较弱的,没有中心节点就会导致很多问题,比如数据的同步和复制都是依赖外部的Zookeeper集群来完成的。

 

我们在第一版本中也遇到了由于Zookeeper压力太大造成的一些系统问题,从ClickHouse这端反应出来的就是与Zookeeper的会话频繁超时,某些副本的本地表停留在只读模式,还有就是ClickHouse的操作发生超时,从Zookeeper集群这端观察到的就是不断的断开网络连接,尝试重连,疯狂打印建立连接的日志,这对数据查询不会造成影响,但是会对实时和离线的数据摄入带来隐患,主要体现就是离线数据替换无法成功导入或者成功同步而失败。

 

那实时数据如果无法成功插入数据,多次尝试之后,会去消费者节点的触发kafka分区的rebalance, 将当前的分区重新分配到ClickHouse的其它副本节点尝试插入,这种错误不会经常发生,我们可以通过重试最终将数据导入,对数据的摄入效率就带来了很大的影响,这个问题主要原因在于Zookeeper的性能会根据客户端的连接数的增多而极度下降,在我们的系统中会有数千个Zookeeper的客户端连接,ClickHouse的操作需要借助Zookeeper的协同,而Zookeeper的压力过大就会造成操作超时,如果Zookeeper的操作超时就会造成会话超时,因为在超时之后,Zookeeper会尝试重连ClickHouse,重建会话,这就进一步加大了Zookeeper的压力,最后就会引发雪球效应。

 

解决这个问题的方案可以设置operationtimeout,尽量减少不必要的全局操作,这些操作都可以缓解压力,但是如果真正是一个治本的方案,我们还是要解决Zookeeper中心压力的问题,我们将Zookeeper的集群进行进一步拆分,不同的集群管理不同的分片,通过这种方式降低了Zookeeper的压力,也解决了这个问题。

 

 
7、数据替换的一致性保证

 

图片

 

我们再来看一下系统中是如何把控数据质量的。可以看到数据质量会在各个阶段进行把控。刚才提到数据任务调度组件会对Spark数据的输出进行校验,数据插入组件也会在数据替换时将临时表和预处理的数据进行比对,数据摄入后,也会有数据质量监控平台对线上数据各个生命周期的数据进行监控,一旦有问题也是做到及时预警,也是及时控制版本,尽量避免线上版本的数据发生错误的时间。

 

 
8、基于ClickHouse的广告应用架构

 

图片

 

上图是一个整个的查询架构。我们会统一的提供数据访问层对外提供API,数据查询主要是分为同步和异步两种调用方式,数据引擎是ClickHouse,查询服务层会将请求发给ClickHouse来进行查询,我们遇到一些QPS比较高的需求或者是做一些聚合度比较高的查询,这种需求对ClickHouse引擎不是很友好,我们也会集成一些其它的数据存储,补全ClickHouse在服务端的一些短板,所以我们通过查询服务层将查询路由到适合的数据存储引擎上来完成一次数据的查询。

 

 
9、测试与发布 

 

图片

 

最后再介绍一下系统的测试和发布。每当有大版本数据更替的时候,会存在一个双数据源的阶段,流量通过将老数据源切换到新数据源的方式来完成发布。拿第一次升级来举例,在查询应用中集成镜像转发的依赖,将生产中的查询镜像到新版本的查询服务上,这两个镜像调用都会被最终返回到镜像的服务器上,通过比对镜像服务器上的结果来判断系统的状态,从而确保系统发布的质量。

 

作者丨周路
来源丨公众号:DataFunTalk(ID:datafuntalk)
dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn
活动预告