基于Flink百亿数据实时去重

基于传统的Set方法去重,以及弊端

  • 去重处理方法:

需要一个全局 set集合来维护历史所有数据的主键。当处理新日志时,需要拿到当前日志的主键与历史数据的 set 集合按照规则进行比较,若 set集合中已经包含了当前日志的主键,说明当前日志在之前已经被处理过了,则当前日志应该被过滤掉,否则认为当前日志不应该被过滤应该被处理,而且处理完成后需要将新日志的主键加入到set 集合中,set 集合永远存放着所有已经被处理过的数据

图片

 

  • 弊端:

百亿数据去重,就按照每天100亿数据来算,由于数据量巨大,所以主键占用的空间也大,如果主键占用空间小就意味着数据表示的范围小,就有可能造成主键冲突。 例如int类型的主键数据范围是-2147483648~2147483647,总共可以表示 42 亿个数,如果这里每天百亿的数据量选用 int类型做为主键的话,很明显会有大量的主键发生冲突,会将不重复的数据认为是发生了重复。如果选取 UUID 做为日志的主键,UUID 会生成 36位的字符串。 例如:"e6f9c6v6-4c6f-41c1-9d30-c47j09r1284a"。每个主键占用 36 字节,每天 1 百亿数据,36 字节 *100亿 ≈ 360GB,这仅仅是一天的数据量。 所以该 set 集合要想存储空间不发生持续地爆炸式增长,必须增加一个功能,那就是给所有的主键增加过期时间ttl。如果不增加 ttl,10 天数据量的主键占用空间就 3.6T,100 天数据量的主键占用空间 36T,所以在设计之初必须考虑为主键设定ttl。如果要求按天进行去重或者认为日志发生重复上报的时间间隔不可能大于 24 小时,那么为了系统的可靠性 ttl 可以设置为 36 小时。每天数据量 1百亿,且 set 集合中存放着 36 小时的数据量,即 100 亿 * 1.5 = 150 亿,所以 set 集合中需要维护 150 亿的数据量。且 set 集合中每条数据都增加了 ttl,意味着 set 集合需要为每条数据再附带保存一个时间戳,来确定该数据什么时候过期。例如 Redis 中为一个key 设置了 ttl,如果没有为这个 key 附带时间戳,那么根本无法判断该 key什么时候应该被清理。所以在考虑每条数据占用空间时,不仅要考虑数据本身,还需要考虑是否需要其他附带的存储。主键本身占用 36 字节加上 long 类型的时间戳8 字节,所以每条数据至少需要占用 44 字节,150 亿 * 44 字节 = 660GB。所以每天百亿的数据量,如果我们使用 set集合的方案来实现,至少需要占用 660GB 以上的存储空间

基于HBase 维护全局 set 实现去重

  • 去重处理方法:HBase 基于 rowkey Get的效率比较高,所以这里可以考虑将这个大的 set 集合以 HBase rowkey 的形式存放到 HBase 中。HBase 表设置 ttl 为 36小时,最近 36 小时的 150 亿条日志的主键都存放到 HBase 中,每来一条数据,先拿到主键去 HBase 中查询,如果 HBase表中存在该主键,说明当前日志已经被处理过了,当前日志应该被过滤。如果 HBase表中不存在该主键,说明当前日志之前没有被处理过,此时应该被处理,且处理完成后将当前主键 Put 到 HBase 表中。由于数据量比较大,所以一定要提前对HBase 表进行预分区,将压力分散到各个 RegionServer 上,避免产生数据热点问题。

  • 弊端:

  • HBase 去重到底能不能保证 Exactly Once?这里用计算PV的案例来分析:

  1. 假如 PV 信息维护在 Flink 的状态中,通过幂等性将 PV 统计结果写入到 Redis 供其他业务方查询实时统计的 PV 值。如下图所示,Flink处理完日志 b 后进行 Checkpoint,将 PV = 2 和 Kafka 对应的 offset 信息保存起来,此时 HBase 表中有两条rowkey 分别是 a、b,表示主键为 a 和 b 的日志已经被处理过了。接着往后处理,当处理完日志 d 以后,PV = 4,HBase 表中有 4 条 rowkey 分别是 a、b、c、d,表示主键为 a、b、c、d的日志已经被处理过了。但此时机器突然故障,导致 Flink 任务挂掉,如右图所示 Flink 任务会从最近一次成功的 Checkpoint处恢复任务,从日志 b 之后的位置开始消费,且 PV 恢复为 2,因为处理完日志 b 时 PV 为 2。但由于 HBase 中的数据不是由 Flink 来维护,所以无法恢复到 Checkpoint 时的状态。所以 Flink 任务恢复后,PV = 2 且HBase 中 rowkey 为 a、b、c、d。此时 Flink 任务从日志 c 开始继续处理数据,当处理日志 c 和 d 时,Flink 任务会先查询HBase,发现 HBase 中已经保存了主键 c 和 d,所以认为日志 c 和 d 已经被处理了,会将日志 c 和 d过滤掉,于是就产生了丢数据的现象,日志 c 和 d 其实并没有参与 PV 的计算。

    图片

  2. 不将 PV 信息维护在 Flink 状态中仅仅在 Redis 中保存 PV 结果,每处理一条数据,将 Redis中的 PV 值加一即可。如下图所示,PV 不维护在状态中,所以当处理完日志 b 进行 checkpoint 时,只会将当前消费的 offset信息维护起来。处理完日志 d 以后,由于机器故障,Flink 任务挂掉,任务依然会从日志 b 之后开始消费,此时 Redis 中保存的 PV=4,且HBase 中保存的 rowkey 信息为 a、b、c、d。紧接着开始处理 c 和 d,因为 HBase 中保存了主键 c、d,因此不会重复处理日志c、d,因此 PV 值计算正确,也不会出现重复消费的问题。

    图片

    这种策略貌似没有问题,但是问题百出。我们的任务处理某个元素需要两个操作:① 将 Redis 中 PV 值加一 ② 将主键 id 加入到 HBase由于 Redis 和 HBase 都不支持事务,所以以上两个操作并不能保障原子性。如果代码中先执行步骤 ①,可能会造成 ① 执行成功 ②还未执行成功,那么恢复任务时 PV=4,HBase 中保存主键 a、b、c,此时日志 d 就会重复计算,就会造成 PV值计算偏高的问题。如果代码中先执行步骤 ②,可能会造成 ② 执行成功 ① 还未执行成功,那么恢复任务时 PV=3,HBase 中保存主键a、b、c、d,此时日志 d 就会被漏计算,就会造成 PV 值计算偏低的问题。这里只是拿 HBase 举例而已,上述情况中外部的任何存储介质维护 set集合都不能保证 Exactly Once,因为 Flink 从 Checkpoint 处恢复时,外部存储介质并不能恢复到 Checkpoint时的状态。

终极版:使用 Flink 的 KeyedState 实现去重

既然外部存储介质不能恢复到 Checkpoint 时的状态,那使用 Flink 内置的状态后端就可以完美解决。当任务从 Checkpoint处恢复时,就可以拿到 Checkpoint 时的状态快照信息如下图所示,可以将主键信息维护在 Flink 的状态中,当处理完日志 b 时,将 PV=2和状态中的主键信息:a、b 一块保存到状态后端。无论后续什么情况发生,只要从 chk-1 对应的 Checkpoint 处恢复,那么会将 PV=2和状态中的主键信息:a、b 做为一个整体来恢复。所以就可以保障 Exactly Once 了。

图片

  • 如何使用 KeyedState 维护 set 集合?① Flink三种状态后端中这里选用 RocksDBStateBackend 将状态信息存储在 TaskManager 本地的 RocksDB数据库中,② 其次KeyedState 数据结构选用ValueStateValueState。当处理一条日志时,根据日志的主键 id 从 ValueState 中 get 数据,如果不为 null就认为当前处理的日志在之前已经被处理过了,此时应该被过滤;如果为 null 就认为当前日志在之前还没有被处理过,此时应该被处理,并且需要 update一个值到 ValueState 中,来标识当前日志被处理过了

调优

优化主键来减少状态大小,且提高吞吐量

将每天百亿数据量的主键通过 hash 算法转换为 long 类型,然后我们可以把 long 类型的数据当做主键来存储。(提问为啥要选用long类型?而不是int,那么hash冲突的概率如何呢?)

设置本地 RocksDB 的数据目录

建议在 flink-conf.yaml 中配置 state.backend.rocksdb.localdir 参数来指定 RocksDB在磁盘中的存储目录。当一个 TaskManager 包含 3 个 slot时,那么单个服务器上的三个并行度都对磁盘造成频繁读写,从而导致三个并行度的之间相互争抢同一个磁盘 IO,这样务必导致三个并行度的吞吐量都会下降。庆幸的是 Flink 的 state.backend.rocksdb.localdir参数可以指定多个目录,一般大数据所使用的服务器都会挂载很多块硬盘,我们期望三个并行度使用不同的硬盘从而减少资源竞争

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值