砥砺前行-初学Flink的我如何快速定位并解决数据同步问题

742 阅读19分钟

前言

出差后回来就马不停蹄地开始解决问题,不过这次的问题让我对Flink的理解再度深化,我自己也是比较喜欢这样的工作,有挑战性才有意思嘛。本篇问题盘到最后发现也并不复杂,主要是我想在解决完问题后做一个复盘,在匆忙入局的情况下如何顶着压力和业务项目的繁重任务下,快速定位并解决一个我并不是特别熟悉的技术栈的问题。限于博主对Flink和TiDB并非特别深入理解,如有问题请指正,有好的建议和学习网站,请快快地给出,迫不及待了家人们!

问题背景

我所在的大组自己搞了一套小数仓,限于内部使用,目前采用Oracle->OGG->Kafka->Flink->TiDB的技术方案,Oracle和OGG外采,其他自建。类似MySQL的数据源采用了TiCDC和FlinkCDC去做数据的全量和增量同步,这个官方支持的其实问题就没那么多,调好配置,两边对齐基本就不会出什么大毛病。

由于历史原因或者路径依赖等各种各样的理由吧,团队没有用OGG直推TIDB的服务,可能是要花钱还是怎么的,反正初版的时候数据同步就没考虑。但是TIDB官方在这个版本是支持的,book.tidb.io/session4/ch…,后面问问我们现在的OGG服务商要不要额外收费,到时候如果版本兼容的话还是换了比较好,当然现在还是要解决这个问题。我个人是比较讨厌单纯用力大砖飞的思路解决问题,不优雅不技术,还是多一些思考比较好,当然没时间的话,还是撒钱办事比较快。

简单介绍下各组件的版本情况,Oracle是11g、OGG外采未知、Kafka3.1、Flink1.15.2、TiDB6.1。相对来说自建的都比较新,近两三年的版本,新特性的话,说实话没有太用到,还是传统用法。对于各组件的熟悉情况,Kafka这个比较熟悉,会基于软件的调优,因为我之前做过日志收集系统,所以对Kafka的日志场景有一定的深入理解。Flink,第三代实时计算引擎,也是我第一个接触的大数据相关组件。我不是专业的大数据开发,对于Flink的认知程度算是入门级别,有两三周的下班时间突击学习了视频资料。之前是另一个同事在做这块,我当时是打下手的,因此学习并不深入,也就是读懂代码,了解Flink基础概念的水平。五一节前这个同事跑路了,因为另一个项目拒绝叠叠乐!我用设计模式重构核心项目,给人榨干了,所以大数据相关的东西丢到了我的头上。悲催的是这两块都是我来负责,工作压力徒增。综上所述,我的Flink学习之路并不理想,很多地方我想尝试和修改的,都会受限于我当前的理论水平。

从三月开始,为了保证核心项目的正常上线,我的重心是偏向于业务开发,特别是同事在四月的交接期间,更是全力熟悉业务中,这也是天坑,想了解详情的可以看看上面叠叠乐的文章。五月开始,因为核心项目预生产环境和部分实验性项目用到了小数仓,所以小数仓的问题开始陆续暴露出来,首当其冲的就是数据异常,和数据源对不上。五月中旬应业务方强烈要求,出差去业务方所在地当面开发,直到五月底才回来,回来了就马不停蹄地解决数据同步问题。

说到这事吧,有个小插曲,就可乐了,我以为这个数据同步的事很急,领导在我出差前一天,说的是数据同步问题一定要解决,因为我要出差以及核心项目的保上线,所以这活只能丢给别人。原本我以为这事会在我出差的中途被人解决,但让我万万没想到的是,两周后我出差回来,原封不动......然后开始十万火急地催着我解决,再看看这中间其他人做了什么,给我大数据方面配合的小伙,出了一个方案。方案里写了个啥呢,众所周知的结构同步问题和日志如何优化......再一问有做吗?答:没有,就是领导让给方案。抽象,非常的抽象,但是值得点赞的是,后续在FlinkCDC的结构同步问题上他自己通过翻阅文档和资料成功解决了这个问题,一定程度上还算靠谱,但是本次问题的关键在于Oracle->TiDB的数据流异常,所以他这个操作我很迷惑,为什么不和我一块解决关键问题?背景大致就这些,简单一句话就是,都在等我发挥。

前期准备

在2月初的时候开始认真学Flink,然后为了实践,上手写了一些demo,那段时间我写文章的时候也有提到过,在学一些有意思的东西,就是指的Flink。主要是在协助同事维护数据同步的Flink代码,同时我也在做一些测试,验证我的想法和学习成果。最后是把我之前写的日志数据处理服务给改成了Flink版,算是在不断试错和碰壁中对Flink有了进一步的掌握。在同事交接的一个月内,压力倍增,项目和Flink并行前进,为了能更好地接手代码,我决定开始优化原有的Flink代码。注意这里是因为我同事也就是原开发者还在这,所以我搞错了能随时问他,如果没有这个条件,建议别瞎优化。因为Flink的资料很不好找,而且相对优质的开源项目而且是新版本Flink代码的很少,所以这个学习过程吧,很艰难。我是跟着B站学习的入门视频,在开发过程中看的最多的是Flink官方文档,偶尔会抱着侥幸心理搜一搜,但是大多时候没有解法。加了一些Flink交流群,这用处只能说聊胜于无,讨论氛围也就那样,提出问题很少有人给出解法。说到这个提问,还是有讲究的,给出背景、版本、现状和期望值才是一个合格的问题,瞎问真的是很抽象。

优化的第一步其实也只是蹒跚学步, 事实上我自己很喜欢在优化别人的代码时带入自己的代码习惯和规范,私以为让自己看得舒服才是工作好心情的第一步。Apache Flink 官方中文文档-1.15.4,B站视频的配套文档我也有看,但是我始终觉得还是官方文档最靠谱,但确实是比较尴尬,没有更好的资料了,想看别的也没地看,读者有好东西希望提点我一下,拜托啦,这对我真的很重要!

第二步是认真使用了Flink WebUI,在同事的指引下去理解看板提供了什么功能,大多数功能实际体验了一把。在我的观察下,我尝试着去调整并行度的配置,代码中做了一些配置化的功能,去实际理解Flink中JobManager、TaskManagers、算子槽位等概念。第三步是优化了日志的输出,之前是每执行一条SQL就要打印一次执行前后的日志,日志量很大,TB级磁盘也扛不住,经常磁盘告警,因此我就删除了成功执行的日志只打印出现异常的日志,并且引入Kafka,将异常以及影响行数为0的SQL及原信息收集起来做异常数据分析。

架构详解

数据源(Oracle、OGG和Kafka)

{
	"table": "INV.MTL_ONHAND_QUANTITIES_DETAIL",
	"op_type": "D",
	"op_ts": "2023-05-31 09:56:01.987865",
	"current_ts": "2023-05-31T09:56:03.661003",
	"pos": "00000000720480956530",
	"before": {
		"字段名": 值,
		......
	},
	"after": {
		"字段名": 值,
		......
	}
}

OGG监听Oracle日志,推送如上的信息到Kafka,OGG推送本身是有顺序性的,为了保证Kafka消费时也是顺序的,Topic分区设置为1,同理Flink消费并行度设置为1,多了也没用。

OGG推送的SQL执行日志消息体结构如上,有表名、操作类型、SQL执行时间、OGG推送时间、偏移量和SQL执行前后的值变化。注意这里如果op_type为I,也就是插入时,只有after有值。op_type为D表示删除,before有值,op_type为U时,前后都有值。

转换及输出(Flink和TiDB)

Flink代码这块总体来说还算简单,因为并没有做一些特别的操作,比如数据清洗和聚合之类的,仅仅是做了一个数据同步。对Flink的任务划分是按照抽取数据的数据源来分的,比如ERP、PLM和其他一些自研系统各自拥有一个独立的Flink任务。那么问题来了,ERP在Oracle见了上万张表,是不是都要接收呢?首先给Oracle配置OGG是收费的,而且是按表收费的,收费粒度很细.......目前大概是接了一百来张表,当时不是所有表都需要用的,因此有做筛选。在Kafka的Topic设计上,为了避免建立太多Topic分区影响Kafka性能,选择用Oracle的命名空间,一个命名空间下会有多张表,这些表的日志数据都将通过命名空间对应的Topic进行传输。

之前由于体量不大,一个ERP对应的Flink任务消费了全部命名空间的消息,当时是采用提高写入并行度来解决背压问题,为什么这么做呢?我猜应该是同事盲目信任了TiDB的并发写入能力。在确定如何抓取需要真实处理的数据时,采取了配置化的方法,在Flink任务启动时会从数据库获取配置需要读取的Topic以及需要处理的表table_name还有主键key,同时也产生了一个问题,初始化的时候只取一次,那么如果动态变更配置必须重启Flink任务才会生效。又因为不能每次Sink时实时去取配置,这样会影响实时效率,所以如何动态感知配置变动也成了一个问题,如果大佬们有好的想法,希望跟我说一说,我学习实践操作一把。

整个Flink的处理流如图,第一步Source是从Kafka监听数据,第二步Fliter是将OGG的消息反序列化为指定对象,填充相关配置信息,并筛选无需处理的数据。第三步Windows是按照表名分组,每三秒开窗,并按照SQL执行时间正序排列。最后一步Sink是自定义的RichSinkFunction,内部是将OGG消息体转换成可执行的SQL,再通过JDBC写入TIDB。这是原本的代码,后续我会详细讲解这里面的问题,以及我的一些疑惑,期待读者大大能耐心看下去。

收集问题

两天前的晚上,我发了这个沸点,当时是我在周一和周二两天内对已有问题的一个总结,说实话,我当时也算是一筹莫展。项目上线在下周二,项目每天都在测试和修改问题,占用了我大量的时间,但数据同步这块依旧是只能我一个人来搞,真是两面包夹芝士,蚌埠住了。我出差是上周六回来的,周一马不停蹄地开始着手去解决数据同步问题。诚然,我的同事们还是出力了,因为我在代码里有做异常SQL收集,所以他们能通过这些问题SQL总结出一些问题。比如Oracle变更表结构时,因为TiDB不能及时同步变更,因此JDBC执行DML语句时会报错。还有SQL影响行数为0的情况,他们猜测是SQL执行顺序问题。周一的时候,我也是尽力不受其他项目干扰,全力收集问题,因为解决问题的第一步永远都是找到有哪些问题。

收集问题我的第一选择是找日志,在taskManagers里面找日志无疑是大海捞针,所有任务的日志都混在里面,我目前还没时间去看能否隔离,如果读者们有懂哥希望大佬指点一下,后面会次优先级处理这个问题。日志里针对error部分查看了下,然后再对我之前写得异常日志做了数据分析,最后总结问题如下:

  1. 结构变动问题----需要OGG推送DDL语句,但是不兼容MySQL,放弃。临时解决方案,ERP变更表时通知PTM手动传输数据变更新表--这段时间内数据存在问题
  2. 科学计数法导致部分DML语句失效--已定位待解决,初步通过放弃精度截断数据的方式控制,1.341068923615803367779017269385769286725E-57疑似ERP默认值,无特殊意义,多表频繁出现--与ERP相关人沟通中
  3. SQL顺序问题,导致部分SQL影响行数为0--怀疑是OGG推送数据问题,已写程序手动保存数据验证中,顺道开始进行TIDB插入的优化
  4. 根据3的记录发现转SQL的工具类有问题,不是所有消费的数据都能被正常转为SQL,导致数据丢失

定位及解决问题

结构变动问题这个暂时不好解决,考虑到表结构变动不会太频繁,所以优先解决重要问题,数据异常。因为是用JDBC直连数据库,并且是用OGG消息转的SQL语句,所以我首先怀疑是SQL是否转换有问题,psvm快速做了个demo验证了下,SQL语句没有问题。然后我就单独写了个服务去消费OGG推送的某一张表的信息,我想要通过收集这些数据来发现问题。

根据以上数据分析,首先排除了SQL转换失败的猜测,因为几十万数据都能有正常的SQL输出,且没有转换出SQL的语句我会进行消息通知,及时分析处理。

做好了所有的准备工作(Flink异常信息通知、OGG单表消息记录),开始从易到难地解决问题。首先解决的是科学计数法导致部分DML语句执行报错的问题,说白了就是位数过长,1.341068923615803367779017269385769286725E-57科学计数法转换成数字是0.000000000000000000000000000000000000000000000000000000001341068923615803367779017269385769286725,当时看到这个数我就觉得很有问题,对照表中字段的注释,更是觉得奇怪。于是我问了相关业务负责人,去确认这个数字的意义,得到的结论总结一下就是小数点后六位四舍五入,这个问题也就这么解决了。

然后是开始排查影响行数为0的SQL,正常如果是按照顺序执行的SQL是不会出现影响行数为0的情况,因此我开始重点排查这个问题。因为此类异常数据众多,所以我就随便抽取数据开始定位,结果上来就发现了一个致命问题,OGG该传的字段不传。

结论就是OGG监听从库时权限没给够,导致日志数据传的有问题,OGG服务供应商在提醒下解决了这个问题。剩下就是不好排查的问题了,因为部分SQL当时影响行数为0,但是把SQL取出来放到数据库里影响行数就不是0了,因此我直接确认是SQL执行顺序问题。那么如何定位这个问题呢,这就要用到上面的OGG单表记录数据了。还是老规矩,从异常记录里挑选一位幸运观众,然后从单表记录中查询SQL执行历史记录,结果马上就发现了问题。

一条数据的新增和删除发生在三秒内,我第一时间想到了三秒开窗的Flink任务,直觉上感觉会出现问题,但是我又一想开窗里面实际上是根据SQL执行时间排过序的,看代码确实有这段排序逻辑,排除。因为是之前同事写的代码,第一时间我也没想着改,但是现在既然错都错了,那就放心大胆开搞。在思考数据有序性逻辑时,我突然想到一件事,我在Sink给了30的并行度,相当于是三十个线程,那这不就是典型的多线程场景吗,有序性问题是必然发生的。

想通了这点那就好改多了,首先我向OGG供应商的人确认了推送信息时是否有序,得到的结果是有序,当然我没有盲目相信,而是拿着我前面的单表数据验证了一下,发现确实如此。接着代码上直接干掉了开窗部分,从我的视角上来看是没有意义的,因为为了保证SQL执行的有序性,理应是一条条顺序执行,那么开窗得到的批量数据没有价值,何必多加一道步骤呢。最后是在SInk之前根据表名做了分组,确保每张表的数据被唯一算子消费,从而确保SQL执行的有序性。这个思路类似于Kafka如何确保消息有序性,Topic的分区数对应Flink的并行度,既然OGG源头确保了消息有序性,那么只要再确保在分区里消息是有序的,那么整个流程就是有序的。

复盘与思考

根据表名keyBy有一个弊端就是,部分算子会闲置,后续为每张表打上标记,根据标记分组,确保资源不会闲置。不知道各位看官有没有发现,本次问题确实比较简单,虽然一开始感觉问题很大,但是最后抽丝剥茧后其实只有两个痛点,数据同步如何确保消息有序性以及TiDB写入调优。TiDB写入调优的问题会留待下一篇文章探究,本篇按下不表。Flink我现在是边学边用,还是比较粗糙,有很多问题,我想发出来记录一下,当然更希望有大佬给指点一下,或者给一个能深度学习的地方让我加强下对Flink的理解。

  • Flink的监控指标有哪些是常用和需要重点关注的?
  • Flink有没有好的调优案例或者说一些常用的调优手段?
  • 有没有啥好的Flink开源项目可以参考或者实战项目视频可以学习?

当然我自己也是搜索了相关内容在学习,如果大家有好东西还愿意分享给我,无以为报,拜谢。要学的东西比较多,TIDB的也要看,new sql是够new的,哈哈,和MySQL还是有不少区别。

写在最后

距离上一篇六脉神剑-我在公司造了六个轮子-阅读10673赞147收藏292,已经过了一个月了,中间有两周时间出差了,生活比较不规律加班也很苦,周末的时候我都放松了,兄弟们,真是扛不住啦。本周也是在解决问题的过程中思考如何总结出文章,这个数据同步的问题还没有彻底解决,因为另一部分TiDB写入压力大的问题还在,因此下一篇如果不是八股的话,就应该是和TiDB有关。

Flink和TiDB的文章相对传统Java生态来说,确实参考资料很少,因此我需要大量的时间来佐证我的想法,文章需要打磨,当然也可能会是像这篇文章一样的阶段性总结。我对文章的要求是,我自己能从里面学到东西,最好能是一个场景或者一个体系。零零散散的知识点网上很多,但是不利于学习,我想经常学习总结的朋友们肯定有所体会,因此我一直以稍微高一点的标准来要求自己的产出,希望大家能够喜欢。

说回上篇,破万阅读量,哈哈真是让我非常意外,我的文章一般收藏挺高,精品文章的阅读量一般是两三千左右,但是上篇数据量全面暴增是我万万没想到的。从我个人角度来看,六脉神剑这篇文章算不上我个人心中的TOP1文章,因为它更像是一篇我工作快四年的技术阶段性成果总结。没错的,真是总结,全篇都是零零散散的一些知识点,杂而全但不精。事后总结看来,可能是我精准找到了这一类型的空缺,相比于牛逼的中间件,人家不需要宣传,自带官网和优秀的文档,一大批人参与社区共建,大部分人可能只是用,并不会深究其原理和源码,有一点学习门槛。相比于工具类,我的六脉神剑又高了一个档次,扩展性和易用性还有学习的价值明显更高,处于一个中间带,为向更上层楼的朋友们,提供了一个可学习和具有一定意义参考价值的中间件。还有的可能是喜欢我的写作风格,喜欢听我讲讲故事,吹吹牛逼,聊聊生活琐事。

生活不易,猫猫叹气。两周的出差生活,让我的心态产生了一些变化,放下了一件事,有点伤感。但是怎么说呢,听人劝吃饱饭,事不过三,该放弃就放弃。在考虑一些事情,会接着做一些准备,我是帝都谜语人,哈哈,希望大家快乐开心!我要让这痛苦压抑的世界绽放幸福快乐之花,向美好的世界献上祝福!!!