前面聊了单线程限制------LogMiner解析速度上限就在1万到1.5万条/秒,日志量越大性能越差。
那怎么办?总不能业务等着你慢慢解析吧。
很多团队想到的解决办法是:一个实例不够,那就多开几个。
一个库开好几套采集实例,一个实例采一批表,每个实例独立解析自己负责的那部分日志。听起来很合理对吧?横向扩展嘛。
但实际效果往往跟预期完全相反。
一个真实的场景
之前跟一个做数据平台的团队聊,他们刚开始用Flink CDC采一个Oracle库,十几张表,跑得挺稳。
后来表越来越多,加到五六十张,延迟开始上来了。他们的解决办法是:再起一套Flink CDC任务,把表拆成两批,两个任务并行采。
延迟确实降了一点。但好景不长,表继续加,延迟又上来了。于是再拆------三套、四套、五套。
到最后,他们在一个Oracle库上跑了七套Flink CDC任务,每套连接器都在源库上开自己的LogMiner会话。结果呢?
延迟没降多少,源库先扛不住了。
CPU使用率从原来的30%多涨到了70%以上,PGA内存被吃掉了一大块,业务高峰期数据库响应明显变慢。DBA找过来问:你们到底在数据库上跑了什么?
为什么"堆实例"解决不了问题?
因为每套实例的成本,都记在源库头上。
第一,每个LogMiner会话都要占用源库的CPU。
LogMiner虽然被限制只用一个CPU核心,但那是每个会话一个核。你开七套实例,就是七个LogMiner会话在源库上跑。七个核同时干活,对源库的CPU压力是叠加的。
第二,每个LogMiner会话都要占用PGA内存。
前面聊过大事务OOM------LogMiner的解析结果存在PGA里,事务越大,PGA占用越高。七套实例并行跑,七份PGA同时消耗,内存压力是七倍。
第三,每个实例都要独立扫描日志。
LogMiner的工作方式是"从指定的起始SCN开始扫描redo文件"。七套实例意味着七次独立的扫描。同样的日志内容,被重复读取了七遍。磁盘I/O、内存带宽全都被浪费了。
第四,日志切换的协调问题更复杂。
Oracle的redo log是循环使用的。多套实例并行读取,每套都有自己的检查点SCN。如果某套实例落后太多,它需要的归档日志可能已经被清理了------检查点失效,任务挂掉。
第五,SCN一致性更难保证。
多套实例独立解析,各自的SCN进度不一样。如果下游需要合并这些变更流,怎么保证事务的全局顺序?一个事务的数据块修改可能被实例A采到,回滚块的修改被实例B采到------跨实例的事务归集,几乎不可能做对。
麻烦在哪?
吞吐没上去多少,源库先被拖慢。
七套实例的吞吐,不是七倍,可能只有两倍多。因为源库的资源是有限的------CPU、内存、磁盘I/O,你开再多实例,总量不变。实例之间互相争抢,每个实例的速度都往下掉。
运维复杂度翻倍。
一套实例的时候,配置、位点、监控、告警,一套就够了。七套实例意味着:
- 七份配置文件要维护
- 七个位点要跟踪
- 七套监控要配
- 七个任务可能在不同时间出问题
一个任务挂了,你得先搞清楚是哪个实例、采的哪批表、位点在哪里、为什么挂。排查时间成倍增加。
源库的DBA会来找你。
这是最现实的问题。你跑了七套LogMiner,占用了源库大量CPU和PGA。业务高峰期数据库变慢,第一个被问责的就是你的CDC链路。
有个DBA跟我说过一句话:"你们的同步任务,比我们自己的业务还费资源。"
裸日志解析怎么解决?
TLA的思路不太一样------单进程多线程,把算力加在解析机上,而不是加在生产库上。
具体来说:
第一,解析机独立部署。
TLA部署在一台独立的服务器上,不跑在源库上。它通过读取归档日志或者在线日志文件来解析,不需要在源库上开任何会话、跑任何进程。
第二,单进程多线程并行。
不需要开多个实例。一个TLA进程,内部用多个线程并行解析日志。线程之间共享内存空间,避免了跨进程通信、上下文切换、数据复制这些开销。
第三,算力加在解析机上。
想提高吞吐?加解析机的CPU核数、加内存。源库毫无感知------它只是提供了一个日志文件,谁在读、读多快,跟它没关系。
第四,只扫描一次日志。
多个线程协同工作,按数据块或按日志段拆分任务,每个线程处理自己负责的那部分。同一份日志只被扫描一次,不重复读取。
第五,顺序控制在解析阶段内建。
多线程并行解析的同时,按顺序构建事务流。不需要后置排序,不需要多实例协调。
我们团队在做TLA的时候,就是按这个思路设计的。单进程多线程解析引擎,部署在独立服务器上,通过读取归档日志和在线日志做解析。实测小字段场景能跑到10.8万条/秒,内存占用稳定,不随日志量增长而恶化。目前Oracle版本已经跑通了,后续MySQL、PG和国产数据库的支持也在推进中。
一个直观的对比
假设你需要从同一个Oracle库采集100张表,峰值日志量很大。
堆实例的方式:
| 实例数 | 源库CPU占用 | 源库PGA占用 | 实际吞吐 | 运维复杂度 |
|---|---|---|---|---|
| 1套 | 1核 | 中等 | 1万条/秒 | 低 |
| 3套 | 3核 | 较高 | 2万条/秒 | 中 |
| 7套 | 7核 | 很高 | 2.5万条/秒 | 高 |
| 10套 | 10核 | 极高 | 可能反而下降 | 失控 |
单进程多线程的方式:
| 解析机配置 | 源库CPU占用 | 源库PGA占用 | 实际吞吐 | 运维复杂度 |
|---|---|---|---|---|
| 4核 | 0 | 0 | 5万条/秒 | 低 |
| 8核 | 0 | 0 | 10万条/秒 | 低 |
| 16核 | 0 | 0 | 20万条/秒 | 低 |
这个对比的核心差异是:堆实例是把成本加在源库上,单进程多线程是把成本加在解析机上。
源库是生产系统,它的CPU和内存应该留给业务。解析机是辅助系统,它的资源可以按需扩展,不影响任何人。
再说一个容易被忽略的点
多实例方案的数据一致性,比单进程多线程更难保证。
假设一个事务修改了两张表:表A和表B。表A由实例1采集,表B由实例2采集。如果实例1先解析完,实例2还没解析到,下游就会先收到表A的变更,后收到表B的变更。如果下游需要两张表的数据保持事务一致性,这个顺序就错了。
单进程多线程方案不存在这个问题。所有线程在同一个进程内协同工作,顺序控制在解析阶段内建。事务的完整性由解析引擎统一保证,不依赖多实例之间的外部协调。
说句实在话
想提速,只能"堆实例"------这是单线程LogMiner方案的宿命。
因为单进程的解析能力上不去,只能靠加进程、加实例来横向硬堆。但每个实例的成本都记在源库头上。吞吐没上去多少,源库先被拖慢。实例一多,配置、位点、监控全都翻倍,运维迅速失控。
TLA走的是另一条路------单进程多线程,在一台独立的解析机上用多个线程并行解析日志。想扩展,加的是解析机的CPU和内存,源库毫无感知。
这是"LogMiner vs 裸日志解析"系列的第六篇。后面会继续聊其他几个坑------大事务OOM、SCN追不上、在线日志读写冲突,等等。
欢迎交流。
补充:文中提到的性能数据和资源消耗情况基于内部测试和公开社区案例,实际效果受硬件配置、数据库版本、日志大小等因素影响。