序言
今天想和大家讨论下大数据在接入业务数据这一部分的设计,我指的业务数据是存在MySQL或者其它关系型数据库的数据,下面直接进入正题吧。
同步业务数据常见的几种方式
-
离线批量同步:
一次性全量拉取:逻辑上基本采用
select * from table的形式,在hive中一般表现为一个分区,也有可能是非分区表的形式。一次性增量拉取: 常见基于时间获取增量数据,例如
select * from table where updatetime< endtime and updatetime > starttime,在hive中会存在多个分区,每次增量拉取的数据会放入对应的分区下。对于以上两种离线同步我们可以使用sqoop来批量同步,使用简单但是也存在一些弊端: 1) 当业务库中数据量非常大时,拉取会对业务库造成很大的压力,甚至宕机,就算是拉取业务从库, 同时也会造成业务从库同步延迟,增加dba运维风险。 2) 当业务库数据量比较大时,`select * from table where updatetime< endtime and updatetime > starttime`这种SQL已经不 再走索引,对业务库的影响也不亚于全量读取。 3) 业务数据量大,我们在拉取时间时,耗时太长,影响下游数仓报表等产出。 4) 对于有些更新不频繁大表,会出现每次拉取大量未变化的数据,浪费资源性能。 5) 无法很好的应对下游对实时性要求较高的场景。 -
实时获取增量数据:
基于离线批量同步带来的种种问题,所以衍生出实时同步的架构。下面进行展开讲诉。
实时同步架构设计
所涉及技术栈
- canal:可以伪装成MySQL的slave,获取MySQL master的binlog日志,并实时解析binlog中各种操作的数据。
- kafka:是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者在网站中的所有动作流数据。
- flume/camus:用于消费kafka中数据写入hdfs。
- hive:基于Hadoop的一个数据仓库工具,用来进行数据提取、转化、加载,这是一种可以存储、查询和分析存储在Hadoop中的大规模数据的机制。
数据架构流程
整体架构如图,整个过程其实分为以下几步:
-
canal获取业务库binlog日志,binlog解析出来我们可以得到3种操作类型的数据,分别是insert,update,delete。以一个用户表
t_user为例,在MySQL中先后对此表进行新增,修改,删除,可以得到如下日志数据。主键 字段(假设是姓名) 数据库操作类型 1 张三 insert 1 李四 update 1 李四 delete 后面我们再基于这个日志数据merge还原ODS的线上数据。
-
canal采集的日志数据发送至kafka后,通过flume或者Camus按操作类型分类并落盘到binlog.db库,在
binlog.db库下事先建立3种操作类型的中间表,假设ODS库中一张同步表名为user,那binlog.db中对应3个中间表分别为user_insert,user_udpate,user_delete,分别用于存放日志数据中3中操作类型的数据。 -
将ODS中现存的数据与
binlog.db的3张中间表的数据进行merge,再将merge后的数据替换ODS数据,完成更新,此时下游任务即可读取最新更新的数据。
merge操作详细介绍
在完成日志数据实时写入binlog.db中间表后,更新线上数据的merge过程如下,简明扼要,继续使用用户表举例:
-
假设线上ODS存量表
user现有数据如下:id name age 1 张三 18 2 李四 20 3 王五 25 -
假设
binlog.db库下user_insert现有数据如下:id name age globid 4 老王 55 1588389000 -
假设
binlog.db库下user_update现有数据如下:id name age globid 1 张三 19 1588389001 2 李四 30 1588389002 1 张三 20 1588389003 -
假设
binlog.db库下user_delete现有数据如下:id name age globid 2 李四 30 1588389005
大家可能注意到了
globid这个字段,中间表中这个字段是怎么来的呢?
对canal二次开发,基于binlog中提取的executeTime时间戳加上canal服务器系统的微秒时间戳得来,
这样的`globid`会是一个高精度,起到可以区分binlog日志先后执行顺序的作用;
将`globid`与日志数据包装好发送到kafka,最后落于中间表用于merge逻辑排序。
-
接下来只需执行一条SQL即可得到merge后数据:
select l.id,l.name,l.age from ( select id,name,age,globid from ( select id,name,age,globid,row_number() over(partition by id order by globid desc) as rn from ( select id,name,age,0 as globid from ods.user union all select id,name,age,globid from binlog.user_insert union all select id,name,age,globid from binlog.user_update ) ) where rn=1 ) l left join (select id,globid from binlog.user_delete) r on l.id=r.id where is_empty(r.id) or (isnot_empty(r.id) and l.globid > r.globid)先存量数据与insert,update数据排序取最新数据,再让delete表左连接,剔除删除的数据,最终结果如下:
id name age 1 张三 20 3 王五 25 4 老王 55 当然也会有其它的合并逻辑,但总的目的是一样的。 最后将合并后的数据替换ODS中的
user表数据,完成回放更新。
当我们控制merge操作的间隔时间,其实就是数据的更新频率,每个小时合并一次,则数据每个小时就得到一次更新。
问题优化与总结
基于这个实现原理,整个实时同步数据落地完成,解决了离线批量拉取的痛苦,但是依旧还有一些问题和挑战。
- 整个流程涉及多个组件,canal、kafka、flume、hive、hdfs、MapReduce或者采用spark作为数据合并计算引擎。流程多也带来运维,数据质量,以及平台化的问题和挑战。
- 在我们合并的时候总是要将ODS存量数据参与合并,如果存量数据也是很大,那也将增加集群的计算负担,甚至也会影响最后数据产出,整体数据质量下降。
- 在某个同步环节出现问题,该如何恢复,例如flume宕机,内存中数据丢失,如何恢复数据。
- 为了数据质量,需要做哪些监控告警等。
欢迎留言讨论,针对这些问题,后面也会逐步更新一些实现细节和解决办法。原创文章,写作不易,喜欢可以点赞关注。