BaikalDB在百度统计的应用实践

原创
09/18 20:26
阅读数 7.4K

作者简介:林江红,百度商业平台研发部资深研发工程师。主要负责百度统计数据系统研发,对构建高性能、高可用的数据处理系统有较多实践和较深入的理解。

欢迎关注 Star github.com/baidu/BaikalDB 国内加速镜像库gitee

1. 百度统计介绍

百度统计是百度推出的一款稳定、专业、安全、全球领先的中文网站流量分析平台,为网站的精细化运营决策提供数据支持,进而有效提高企业的投资回报率。

1.1 百度统计是什么

百度统计产品的使用十分简单,只需要在页面中嵌入一段JS代码,就会自动开始记录用户行为,并沉淀为宝贵的数据资产。在此基础上,提供了用户分层、数据看板、访客流失分析、打通投放平台等各种手段,帮助客户将一个个的访客变成忠实的客户。

enter description here

经过12年的发展,累积服务客户数超过1千万,月活客户22万+,每天帮助客户沉淀数据资产150亿+

1.2 数据系统简介

先介绍数据处理系统的输入输出,以线下超市作为类比,统计的SDK就相当于超市中的监控探头,探头记录每个顾客逛超市的各种路径:走进超市、看了一件商品就走了/继续买买买、结账离开等;而统计的SDK则是记录每个访客进来之后打开一个页面就关闭了/继续浏览其他页面、下单购买、离开网站的行为: enter description here

统计的数据处理系统,就是处理这些监控到的明细行为,一方面通过按访客明细的形式提供给客户,另一方面,按照用户增长的方法论,提供各种聚合报告。所以,数据处理系统划分为预处理(产出明细数据)与报告(产出聚合数据)两个部分,其中报告的部分目前还有预定义和自定义两类实现,正在推进整合。为了应对每天150亿+条数据的处理,并且近实时地提供给客户,系统目前基于自研的流式框架,采用opera进行部署,共使用310+实例,正在推进统一至百度流式引擎streaming framework。

enter description here

BaikalDB承担了实时访客的访问明细存储聚合表中的KV存储两项核心功能。本文将介绍统计在这两方面的系统演进。

2. 实时访客演进

实时访客报告提供访客明细数据给客户,帮助客户及时根据访客特征优化网站。并与百度投放系统深度整合,可以一键屏蔽作弊流量。

2.1 报告样例及使用情况

实时访客分为顶部的banner及下方的访客明细:顶部banner披露最近三十分钟的PV/UV数,帮你轻松了解网站的运行情况(服务是否良好);下方的访问明细可以细粒度的分析访客的行为,来了就走?同一个IP大量重复访问?作弊流量消耗投放预算?

enter description here enter description here

实时访客命中了客户的痛点,是统计产品关注度最高的功能(网站概况是默认选项),其它高关注的功能依次是全部来源(帮客户追踪流量渠道)、搜索词(SEO)及推广概况(衡量投放效果):

enter description here

2.2 客户述求与术挑战

实时访客的核心述求是数据实时入库。如果时效性不够,那么该功能的客户价值将大打折扣:并不能反馈客户网站目前的运行情况、发现效果不及预期但是投放的预算已经花出去了。另一项核心述求是准确,也是整个统计的通用需求,这个部分和今天的主题关系不大,就不展开了。最后是亚秒级的查询服务响应延迟,这个是web服务比较一般性的要求。

  • 秒级写入延迟,涉及
    • 高峰写入QPS 30万+条/秒
    • 高峰写入吞吐 60MB+/秒
  • 亚秒级查询响应延迟
    • 总存储容量:15TB
  • 数据管理上
    • 数据可靠,不能丢失
    • 过期删除

2.3 基于mysql cluster的系统方案

百度统计实时访客起步比较早,上一次大规模的升级发生在2016年,将每个客户500条的上限大幅提升至5000条。当时并没有成熟的分布式数据库解决方案,所以我们选择了基于mysql cluster来构建。这里主要介绍应用层上的改造和在数据库性能上做的设计

2.3.1 降低写压力

高吞吐造成的主要问题是服务器的buffer pool快速耗尽(Everything is an index in InnoDB)、主从同步的开销,所以首先考虑60MB+/秒的写入吞吐。60MB+/秒的写入中,有接近50%是url等字段造成的,采用将字面签名,mysql仅写入字面,映射关系写入KV系统。 30万+的写入QPS,如果直接采用one-by-one的写入方式,通过并发写入的方式来实现的话,需要数千个session才能解决。直观的解决方法是采用batch write,那么,batch设置多少是合适的呢?只能结合业务场景实测:

enter description here

所以我们选择了1024做为batch size。 这里分享一个有意思的观测,ssd的表现并没有比sata更好,是因为mysql引入了change-buffer,将随机IO优化成了顺序IO。 扩展问题:batch 为什么会快?从服务端的角度,一个batch只会执行一次commit;从客户端的角度,节省网络延迟。

2.3.2 数据库性能

经过应用层改造之后,数据库的压力降低到30MB/s, 共计7TB数据需要存储。在这个体量下,依然需要考虑分库分表。 首先解决数据不丢的问题。mysql上一般采用主从同步来保证数据不丢,而主从同步的延迟要尽可能小,以此避免主节点故障时丢失太多的数据。然而,在高写入压力下,主库并发提交,但是从库的SQL thread是单线程,导致主从同步延迟无法收敛。当时百度内部验证过的mysql 稳定版本为5.6,并行主从复制的粒度是database,所以我们使用了16个mysql节点,每个节点内部拆分了64个database。这个限制在5.7中已经不存在了,具体可参考EMTS,不再展开。 下面解决高吞吐的问题。如前文所述,buffer pool耗尽将会导致写入吞吐急剧下降。统计的场景下实测结果如下:

enter description here

回顾我们的使用场景,是持续写入最新的数据,也就是时间序列数据的场景,所以自然的就考虑采用时间分区的方式,考虑如下因素:

  • 每个分区不能太大,保证正常写入性能
  • 分区数量不能太多,否则mysql性能会很慢。这点可能和选择分表或者文件系统在一个目录有过多文件性能不佳有关
  • 分区需要考虑方便数据淘汰机制,比如能按天清空(解决过期删除的问题)

最后,经过测试选择了每4小时作为一个分区的方式。

2.3.3 控制连接数

最后,为了解决数据写入实例与mysql在不同地域下,写入延迟高的问题,我们采用了异步API,并且每个写实例(共16个)使用8个线程,每个线程维护10个实例的连接,所以,每个mysql需要维护1280个链接。故而,我们将写实例和mysql分为两组,将每个mysql实例的连接数降低到640个。

2.3.4 小结

小结统计侧做的优化如下:

  • 将url签名入KV,降低吞吐
  • 拆分不同mysql实例,并在实例内部拆分64个database,解决主从同步的延迟
  • 将 SQL写入打包(需要测试得出合理的包大小),降低写入压力
  • 通过时间分区,并通过实测得出合理的分区条件,解决写入吞吐恶化的问题
  • 最后,将写实例和mysql分为两组,降低mysql连接数

enter description here

2.4 演进到BaikalDB

虽然经过大量的定制和改造后,支持了产品进行了500到5000的调整。但是,这个方案依然存在如下问题(按痛点排序):

  1. 手动分库分表,特别是分为两组的设计,导致系统演进特别困难,涉及数据分发的调整特别难以维护
  2. 由于分库分表的存在,客户数据问题的定位也变得十分困难。
  3. 共设计1024个数据库,新增字段的流程特别长
  4. 最后,涉及了KV和mysql两个系统

随着BaikalDB的日渐成熟,推进将实时访客切换到BaikalDB。使用BaikalDB的region自动分裂和TTL等功能,应用层仅需要做1024batch接口,其余无需关注。即使是 1024batch的逻辑,因为无需分库分表,也变得特别简单。极大的降低了使用成本。另外,由于BaikalDB具备优良的扩展能力,我们将url等字面也直接进行存储,降低系统的复杂度,也提供了类似模糊匹配的产品能力。最后,数据库的成本节省了25%。

最后,分享一个利用BaikalDB做累加的小技巧,来源实时访客顶部的最近三十分钟的访问趋势,在切换流量的过程中,趋势偶尔出现明显的尖峰。简化SQL如下方便理解:

INSERT INTO visit_stat (site_id, pv)
VALUES (1, 2)
ON DUPLICATE KEY UPDATE pv = pv + VALUES(pv)

经过分析,这些尖峰对应到客户端发起commit但是并未收到服务器响应。总所周知,数据库保证如果通知客户端写入成功,则这次写入不会丢失,但是,并不能解决客户端未收到响应的情况。这类case一般的解法是将写入操作变成幂等,客户端重试时,先检查数据库状态。所以,我们调整了表结构,增加时间戳当做version使用:site_id, pv, timestamp.;写入语句调整为UPDATE visit_stat SET pv = pv + VALUES(pv)WHERE site_id = 1 AND timestamp != VALUES(timesamp) 然而,由于同一个site_id会被不同的写入实例同时更新,依然会导致重复写入,所以,我们进一步的将写入实例标识加入主键中。

3. KV系统演进

下面介绍BaikalDB作为KV在统计系统中的应用。

3.1 为什么需要KV系统

以客户高关注的搜索词报告为例,我们将搜索词的签名、PV、UV、时长等信息存储在doris中,而签名与字面的映衬关系存储在KV系统中:

enter description here

为什么采用签名的方式呢?首先,以前doris并不支持直接存储字面,所以只能采用这个设计;其次,搜索词有大量的重复,这个比较好理解,比如同一个网站下不同细分、不同网站之间,搜索词均有大量重复的情况,所以采用KV的形式,可以消除重复信息,存储效率比较高;其次,存储整数除了IO上的减少外,计算时由于可以有更好的缓存等,计算效率比较高(细化)。但是,存储签名,也带来了利用不能模糊匹配、查询更复杂等问题。

3.2 保留高频

另外,网名的浏览行为中,存在大量的长尾浏览,比如url、搜索词等,基于如下几个方面的考虑,统计采用的抛弃长尾维度,仅保留高频维度的方式:

  • 首先是控制存储成本。doris表中采用的是聚合模型,大致可以理解为存储容量和数据行数相关,而和次数无关。所以,需要控制每个客户的数据行数,来控制存储成本
  • 然后是控制查询响应延迟。行数越多响应越慢,这个应该比较好理解。
  • 最后是客户视角上,统计这部分报告主要披露的是流量指标,所以大量长尾反而会干扰客户。 的确有部分客户需要精细化的分析长尾的流量,所以产品上也推出提升保留上限以及分析云。

3.3 现有实现及技术挑战

统计原有采用的是在预处理模块产出KV,并将KV写入MOLA集群,然后仅将ID下发至长尾抛弃模块,最后再产出高频KV。

enter description here

到这里可能会有一个疑问,为什么不再预处理模块做长尾抛弃?长尾抛弃需要将一个客户的数据散列到同一个处理节点,这样就导致处理节点之间负载不均衡。而预处理到长尾抛弃之间,我们设计了微批聚合,所以到Reduce模块时负载不均衡的问题已经得到极大的缓解。另一个原因是mysql cluster中仅存储签名,需要反查字面。 另外,当时由于物理资源的限制,北京mola集群容量不足。所以,我们将集群拆分为北京缓存集群和南京永久集群:北京集群采用全内存索引和SSD存储字面,负责30天内的数据查询,南京采用sata(rocksdb),负责相应较冷的数据查询。 基于上述设计,统计对KV系统的需求可以小结如下:

  • 数据量大
    • 容量:北京 10+TB; 南京250+TB
    • 行数:北京 270+亿;南京 1万亿

上面的存储容量是单副本,而读写的QPS也特别高:

  • 高并发
    • 北京:写17万/秒;读14万/秒
    • 南京:写11万/秒

3.4 从mysql cluster到BaikalDB

随着Mola系统的逐步退场,统计需要考虑将KV也切换至BaikalDB,降低系统的复杂度。

3.4.1 技术方案

统计对KV系统的需求,本质上就需要为不同数据设置不同的保留时间,直觉上BaikalDB的TTL功能可以很好的match需求。但是,有如下考虑:

由于统计的数据容量过大,全部采用SSD的成本过高,所以永久集群必须使用sata控制存储成本。SATA作为介质的BaikalDB,其写入性能和17万条/秒相去甚远,所以选择使用SSD做缓存集群,低延迟写入并负责近期热数据的查询;使用SATA作为永久集群,低成本保留历史数据,仅需保证在SSD集群中的KV对过期之前完成导入即可。

此时,SSD集群的需求是低延迟支持17万条/秒的写入,以及14万条/秒的查询。而SATA集群则需要抗住11万条/秒的写入。对SSD集群来说,14万条/秒的查询难以支撑,而SATA的11万条/秒的写入,也导致数据的GAP越来越大。所以,我们进一步的采用了对reduce模块产出的高频K,进行离线去重和Join,并使用BaikalDB的AFS importer完成离线导入。其中,离线去重是将不同reduce实例产出的高频K进行去重,极大降低对SATA集群的写入需求;而离线通过MR进行Join,则降低了SSD集群的查询压力。

最后,如果数据永久不清楚,即使使用SATA来存储,其成本也会越来越高,所以和产品协同,利用TTL机制,将过去长时间未访问的KV对删除。迁移时已经完成将18年前的数据清理,未来将会推进例行机制 整体的技术方案如下:

enter description here

3.4.2 升级收益

  • SSD查询QPS降低10万+ :这部分主要来源于通过离线Join转储的改造,不再通过直接查询线上的SSD集群来获取高频K对应的字面
  • KV集群成本降低3倍

4. 总结

本文介绍了BaikalDB在统计的实时访客和报告系统中的应用,代表了作为传统意义的数据库和作为KV系统的两种场景,结合应用层的调整,BaikalDB均能很好的满足业务需求。从使用方的角度,可以有下面几个点和同学们分享:

  1. BaikalDB 可抗30万+的写入QPS
  2. BaikalDB SSD写入吞吐是SATA的8倍左右,高频写的场景最好选择SSD
  3. 写入操作需要幂等,因为客户端提交commit后,可能收不到服务端的ack
展开阅读全文
打赏
0
8 收藏
分享
加载中
把他开源了吧
09/26 07:34
回复
举报
支持!
09/25 09:03
回复
举报
更多评论
打赏
2 评论
8 收藏
0
分享
返回顶部
顶部