这套OGG同步Oracle到Kafka的链路,我在生产环境里前前后后调了小半年,踩过的坑不比写业务代码少。刚开始接手的时候,团队里对这种组合的认知基本停留在"OGG是Oracle家官方的同步工具,Kafka是消息中间件",但真到了要把两者串起来跑数据,才发现里面的版本兼容、字符集、大字段、消息顺序、延迟控制这些细节,随便哪个没处理好,下游就是数据对不上账的灾难。
这篇博文不打算写成官方文档的复读机。我按照实际搭建的顺序,从架构选型、环境准备、源端配置、目标端Kafka Handler配置,到链路启动后的校验和调优,把我自己的配置参数和排查思路完整放出来。如果你正打算把Oracle的增量数据实时同步到Kafka,或者已经部署了但总出幺蛾子,这篇应该能帮你少走不少弯路。
1. 为什么是OGG而不是自研采集程序:从一次线上事故说起
先说结论:OGG同步Oracle到Kafka这条路,本质上不是"同步工具"和"消息中间件"的简单拼接,而是一条完整的变更数据捕获(CDC)链路。OGG读取Oracle的redo log和archive log,把这些变更解析成OGG自己的Trail文件,再通过网络投递到目标端的Replicat进程,最后由Kafka Handler把变更记录写入指定的topic。
1.1 自研轮询方案的教训
之前我们团队用的方案是自研轮询程序:业务表加一个LAST_UPDATE_TIME字段,程序每隔几秒扫一次,把大于某个水位线的数据捞出来塞进Kafka。这个方案在数据量小的时候确实简单好用,但一旦业务量上来,问题就全暴露了。
最典型的一次事故是这样的:某天下午大促预热,订单表的数据量暴涨,轮询程序里的一个SQL走了全表扫描,直接把源库的CPU打到100%。数据库是集团的核心库,整个业务链路都跟着抖动。更麻烦的是,因为轮询程序有自己的查询水位,事务一旦跨过水位线,很容易出现重复扫和漏扫并存的情况——下游的宽表数据对不上,数据团队加班排查了两天才把口径理清。
自研方案的另一个痛点是:原理上就落后。只要业务表没有合理的增量索引,每一次增量扫描都是一次全表或大范围索引扫描,数据规模上来以后,源库的性能开销是指数级上升的。而OGG直接读Oracle的online redo log和archive log,不产生任何业务SQL查询,对源库的负载影响可以忽略不计。
1.2 OGG这条链路的数据流拆解
OGG同步Oracle到Kafka,完整的链路是:Oracle redo log -> Extract进程 -> 本地Trail文件 -> Data Pump进程 -> 网络传输 -> 远端Trail文件 -> Replicat进程 -> Kafka Handler -> Kafka topic。
这里面的关键点很多人容易忽略:Extract进程和Data Pump进程是两个不同的进程,Extract负责从日志里抓变更并写入本地Trail文件,Data Pump负责把Trail文件通过网络传输到目标端。拆成两个进程的好处是:源端抽取和网络传输解耦,即使网络抖动,Extract也不会abend,只会让本地Trail文件继续累加,网络恢复后Data Pump追赶进度就行。
目标端Kafka侧的写入,是通过GoldenGate for Big Data(简称GGBD)的Replicat进程完成的。GGBD是OGG产品线里专门面向大数据生态的组件,它不直接把变更apply到数据库,而是通过一系列的Handler把变更写入Kafka、HDFS、HBase这类系统。我们用的就是其中的Kafka Handler。
1.3 适合用OGG同步到Kafka的场景
不是所有Oracle到Kafka的需求都适合上OGG。我梳理下来,更适合走OGG的场景有几类:
- 源库是核心交易库,对性能极其敏感,不能接受额外的查询压力。OGG只读日志,不影响业务SQL。
- 数据变更量大、实时性要求高,需要秒级延迟。日志解析的方式天然比轮询SQL快。
- 下游需要事务级一致性。OGG的Trail文件里记录了事务边界,同一事务的变更在Replicat端可以保证按序写入。
- 需要全量+增量一体化同步。OGG支持通过初始化加载(Initial Load)先把存量数据导过去,再无缝切换到增量。
反过来,如果数据量很小、变更频率很低、下游也不在意重复和乱序,那自研定时任务反而是成本更低的方案。OGG的部署和运维复杂度,起步就有一定门槛,杀鸡用牛刀没必要。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 部署前必须确认的版本兼容矩阵与Oracle日志姿势
OGG这套东西,版本兼容是最容易埋雷的环节。我见过不少人下了一个OGG安装包,解压完直接就开始配,配了半天发现无法抽取或者无法启动,最后检查版本才发现源端Oracle版本和OGG版本不匹配。OGG的版本兼容逻辑比较严格:OGG版本需要和Oracle数据库版本匹配,GoldenGate for Big Data的版本也需要和目标端Kafka的版本匹配。
2.1 版本兼容矩阵速查
直接给结论。源端Oracle 11g/12c/19c,建议使用OGG 12.3.x或19.1.x,其中19.1.x对19c数据库支持最友好。我们生产环境用的是Oracle 12.2 + OGG 12.3.0.1,稳得一批。如果你的数据库是Oracle 12c但没打补丁,注意OGG需要数据库的补充日志功能完整,有些精简版或私有化改造过的库可能不支持,要先验证。
目标端Kafka版本,GGBD 12.3.x对应Kafka 0.10/0.11,GGBD 19.1.x对Kafka的兼容更宽泛,但也不要盲目追新。Kafka 0.11引入了幂等生产者(idempotent producer)和事务API,如果你的下游不需要这些特性,用老版本的Kafka Handler反而少踩坑。
下面这张表是我当时整理的版本对照,直接抄就行:
| 组件 | 版本 | 说明 |
|---|---|---|
| Oracle DB | 11.2.0.4 / 12.1 / 12.2 / 19c | 需要开启归档和补充日志 |
| OGG for Oracle | 12.3.0.1 / 19.1.0 | 源端Extract/Dump |
| GoldenGate for Big Data | 12.3.2.x / 19.1.0 | 目标端Replicat + Kafka Handler |
| Kafka | 0.10.x / 0.11.x / 2.x | 兼容性需按GGBD版本来 |
| JDK | 1.8 | GGBD的Java Adapter必须用Java 8 |
提示:OGG的安装包分为标准版和Big Data版,标准版没有Kafka Handler。目标端一定要使用GoldenGate for Big Data发行版,这点非常容易搞错。
2.2 Oracle侧必须做的三项准备
源端Oracle如果不开归档,OGG白搭。OGG的Extract进程有两种抓取模式:在线日志(online redo log)和归档日志(archive log)。只开归档不开补充日志,即使OGG能抓,也无法拿到主键列和唯一键列的旧值(before image),下游做更新操作时根本不知道哪条记录被改了。
我每次搭建OGG链路,Oracle侧会按下面的顺序操作:
sql复制-- 1. 开启归档日志(需要重启数据库)
shutdown immediate;
startup mount;
alter database archivelog;
alter database open;
-- 2. 开启最小补充日志(所有OGG场景必须)
alter database add supplemental log data;
-- 3. 如果同步的表有主键,还需要开启主键补充日志
alter database add supplemental log data (primary key) columns;
开启补充日志这一步经常被忽略,但它的作用是告诉Oracle把变化数据中涉及的主键列、唯一键列额外记录到日志里。没有主键日志,OGG在目标端生成的UPDATE语句就会因为WHERE条件不完整而无法正确定位旧记录。如果只是测试环境,开最小补充日志就够了;生产环境我建议把主键补充日志也开了,代价是日志量增加一些,但对同步数据准确性是质的提升。
2.3 创建OGG数据库用户和目录结构
OGG需要一个源端数据库用户来连接数据库并管理抽取进程。这个用户不需要DBA权限,但需要的权限不少。我的做法是新建一个专门用户,按最小权限授权,避免直接使用system甚至sys账户:
sql复制create user ogg identified by ogg123 default tablespace users temporary tablespace temp;
grant connect, resource to ogg;
grant select any dictionary to ogg;
grant select any table to ogg;
exec dbms_goldengate_auth.grant_admin_privilege('ogg');
这里有个重要细节:dbms_goldengate_auth.grant_admin_privilege这个包会授予OGG访问LogMiner相关视图的权限,这是OGG日志解析的关键。如果没有这一步,Extract进程连数据库都登录不了,或者登录后抓不到日志。
OGG自己的目录结构,在安装OGG软件后,用ggsci里的CREATE SUBDIRS命令自动生成。默认会有dirprm、dirrpt、dirdat等目录,其中dirprm存放参数文件,dirrpt存放报告文件,dirdat存放Trail文件。dirdat目录要注意磁盘空间,Trail文件积累速度非常快,我们在生产环境单独挂了1TB的盘给它,并且设置了自动清理策略。
3. 源端OGG配置:从GLOBALS到Extract/Dump进程的关键参数
源端配置是整套链路的基石。很多人上来就建Extract进程,忽略了GLOBALS文件和Manager进程的参数,后面各种灵异问题都从这里来。我按照配置顺序一一展开。
3.1 GLOBALS文件和Manager进程
GLOBALS文件是OGG的全局参数文件,放在OGG安装目录下,名字就叫GLOBALS,没有扩展名。我在这里面配置了CHECKPOINTTABLE,目的是让OGG的检查点信息写到数据库表中,而不是只写在本地文件里。这样即使OGG进程异常重启,也能从数据库表里恢复最后的处理位置,避免数据重复或丢失。
code复制GGSCI> edit params ./GLOBALS
CHECKPOINTTABLE ogg.checkpoint
这里的ogg.checkpoint是一张表,会由OGG自动创建。但需要先执行ADD CHECKPOINTTABLE。如果漏了这一步,Extract进程无法做可靠的断点续传。
Manager进程是OGG的守护进程,所有其他进程都由它拉起。我的mgr参数如下:
code复制GGSCI> edit params mgr
PORT 7809
DYNAMICPORTLIST 7810-7820
AUTOSTART ER *
AUTORESTART ER *, RETRIES 5, WAITMINUTES 3
PURGEOLDEXTRACTS ./dirdat/*, USECHECKPOINTS, MINKEEPHOURS 72
PURGEOLDEXTRACTS这条非常关键,它表示Trail文件在检查点确认且保留72小时后自动清理。如果不配,dirdat目录会一直涨,最终把磁盘写满,OGG进程abend。我见过一个客户环境,就是因为没配清理策略,Trail文件把挂载盘写满,链路停了三天。
3.2 Extract进程:从日志里抓变更
Extract进程是整个OGG链路里最核心的进程。它在源端Oracle上运行,直接读取redo/archive log,解析出DML变更。我建的抽取进程名字叫EXTORA,参数文件如下:
code复制GGSCI> edit params EXTORA
EXTRACT EXTORA
USERIDALIAS ogg
EXTTRAIL ./dirdat/ea
DDL INCLUDE ALL
TABLE udata.*;
GETUPDATEBEFORES
逐行解释一下:
- EXTRACT EXTORA:声明进程名。
- USERIDALIAS ogg:这里用了别名,需要在OGG的credential store里配置。用别名的好处是密码不会明文写在参数文件里。配置命令是
ALTER CREDENTIALSTORE ADD USER ogg@orcl PASSWORD ogg123 ALIAS ogg。 - EXTTRAIL ./dirdat/ea:指定本地Trail文件路径和前缀。ea是两位字符前缀,OGG会生成类似ea000000001的文件。
- DDL INCLUDE ALL:是否同步DDL操作。如果下游只需要数据变更,这条可以注释掉,能减轻不少日志解析压力。
- TABLE udata.*:指定要同步的表,支持通配符。
- GETUPDATEBEFORES:获取UPDATE操作前的镜像值。这个和数据库补充日志配合,保证下游能拿到完整旧值。
这里还需要注意,OGG解析的是日志,因此Oracle的补全日志级别决定了OGG能拿到多少旧值。如果你的表和我在生产环境一样有主键,建议在表级别再开一次补充日志:
sql复制alter table udata.orders add supplemental log data (all) columns;
这个ALL级别会把所有列都记录到日志中,数据准确率高,但日志量也会明显增加。如果只关心部分关键列,可以用(primary key)替代(all)。
3.3 Data Pump进程:和网络抖动说再见
Data Pump进程负责把本地Trail文件传输到目标端。名字可以叫DPORA,参数文件如下:
code复制GGSCI> edit params DPORA
EXTRACT DPORA
PASSTHRU
RMTHOST 192.168.10.20, MGRPORT 7809
RMTTRAIL ./dirdat/ra
TABLE udata.*;
PASSTHRU表示数据泵进程只做传输,不解析数据内容,这样性能更好。RMTHOST指定目标端的OGG Manager地址端口。RMTTRAIL是目标端的Trail文件路径和前缀。
有人会问:能不能不用Data Pump,直接让Extract进程把数据发到远端?可以,但我不推荐。部署Direct模式意味着Extract进程既要解析日志又要网络传输,一旦网络抖动,Extract进程很可能因为无法及时写入远端而abend,源端就要重新从日志定位位置,影响较大。加了Data Pump后,本地Extract只负责写本地文件,Data Pump负责网络传输,两边的故障影响被隔离开来,这是一个非常实用的生产经验。
在GGSCI里执行启动命令:
code复制GGSCI> start mgr
GGSCI> start EXTRACT EXTORA
GGSCI> start EXTRACT DPORA
通过info all可以查看进程状态,RUNNING表示正常。再看stats extora能看到抽取条数。
4. 目标端:GoldenGate for Big Data的Kafka Handler配置拆解
目标端和源端的思路完全不一样。源端的核心是抓取日志,目标端的核心是把OGG的Trail文件内容转换成Kafka消息并写入topic。这部分我踩的坑最多,特别是Kafka Handler的属性和Topic映射模板,每一个都值得单独拉出来说。
4.1 GGBD的Replicat进程参数
GoldenGate for Big Data安装完成后,同样用ggsci操作,但进程类型和源端有区别。目标端的Replicat进程不是连接数据库apply,而是通过Java Adapter调用Kafka Producer。我的复制进程叫REPKAFKA,参数文件如下:
code复制GGSCI> edit params REPKAFKA
REPLICAT REPKAFKA
TARGETDB LIBFILE libggjava.so SET property=dirprm/kafka.props
REPORTCOUNT EVERY 1 MINUTES, RATE
GROUPTRANSOPS 100
MAP udata.*, TARGET udata.*;
- TARGETDB LIBFILE libggjava.so:这是GGBD特有的配置,表示使用Java Adapter库。
- SET property=dirprm/kafka.props:指定Kafka Handler的属性文件,所有关于Kafka的配置都在这。
- GROUPTRANSOPS 100:将多个事务操作合并成一批处理,减少Kafka写入次数,提高吞吐。
- MAP udata., TARGET udata.:源表的映射规则,这里TARGET只是逻辑名,实际写入哪个topic由Kafka Handler的属性文件决定。
在GGSCI里建好进程并启动:
code复制GGSCI> add replicat repkafka, exttrail ./dirdat/ra
GGSCI> start replikat repkafka
注意ADD REPLICAT时指定的exttrail必须和源端Data Pump写的远端Trail路径一致,否则目标端看不到数据。
4.2 kafka.props:决定消息格式和Topic映射
kafka.props是Kafka Handler的核心配置文件。我维护过两套,一套用JSON输出,一套用Avro输出。如果下游是数据团队用Spark/Flink直接消费,我更推荐Avro,带schema管理,字段类型不会丢;如果下游只是用简单的脚本消费或者需要人肉看数据,JSON更方便。
以下是JSON输出的kafka.props关键配置:
code复制gg.handlerlist=kafkahandler
gg.handler.kafkahandler.Type=kafka
gg.handler.kafkahandler.KafkaProducerConfigFile=custom_kafka_producer.properties
gg.handler.kafkahandler.TopicMappingTemplate=ogg_${tableName}
gg.handler.kafkahandler.Format=json
gg.handler.kafkahandler.OperationType=update
几个配置的细节:
- TopicMappingTemplate使用
${tableName}动态映射表名。比如源表是udata.orders,写入的topic就是ogg_orders。如果多张表要进同一个topic,可以写死topic名,比如TopicMappingTemplate=ogg_all_data,但这样下游要自己区分表名,通常不推荐。 - OperationType=update表示UPDATE操作也要输出完整字段。如果你只关注插入,可以改成insert。不要小看这个参数,它决定了Kafka消息里是否包含更新前的旧值。
- Format=json表示每条Kafka消息是一个JSON对象,包含op_type(操作类型)、table_name、schema_name、timestamp和字段键值对。
4.3 自定义Kafka Producer属性:生产级写入参数
KafkaProducerConfigFile指向的custom_kafka_producer.properties,是直接透传给Kafka Producer的配置。以下是我生产环境的模板:
code复制bootstrap.servers=192.168.10.31:9092,192.168.10.32:9092,192.168.10.33:9092
key.serializer=org.apache.kafka.common.serialization.StringSerializer
value.serializer=org.apache.kafka.common.serialization.StringSerializer
acks=1
retries=3
batch.size=65536
linger.ms=20
buffer.memory=134217728
max.block.ms=60000
这些参数里,acks和linger.ms是最影响延迟与吞吐组合的。如果下游要求秒级延迟,acks=1已经足够,不必用acks=all,后者写入延迟高。生产环境我配置的是acks=1 + retries=3,兼顾了数据不丢和延迟。batch.size和linger.ms是Kafka Producer的批量发送机制,batch.size=64KB + linger.ms=20,实测在中等变更量下,单条消息延迟可以控制在200ms以内,同时吞吐比默认配置提升了一倍多。
还有个容易被坑的地方:Kafka 0.11之后如果开启了幂等生产者(enable.idempotence=true),Producer会要求retries大于0,同时acks必须是all。如果你的Kafka版本较新,而你的下游又没法容忍数据重复,可以开启幂等,但要接受延迟上升的代价。我没有开启幂等,因为OGG的Trail文件本身带检查点,配合Kafka的消费者group管理offset,实际场景下数据重复的概率已经很低了。
4.4 Kafka侧的消息结构与数据校验
Kafka侧的topic在启动Replicat前需要提前创建。如果不提前创建,OGG的Kafka Handler默认auto.create.topics.enable=true时也会自动创建,但分区分副本可能不符合预期。我推荐手动创建:
code复制kafka-topics.sh --zookeeper zk1:2181,zk2:2181,zk3:2181 --create --topic ogg_orders --partitions 6 --replication-factor 2
分区数的多少会影响下游并行消费的能力。我按同步表的业务量来定,核心大表分区数16,小表6。分区数一旦定了,后续增加分区会导致同一主键顺序错乱,所以一开始要规划好。
启动后,可以用kafka-console-consumer直接看消息内容:
code复制kafka-console-consumer.sh --bootstrap-server 192.168.10.31:9092 --topic ogg_orders --from-beginning
正常的JSON消息长这样:
json复制{"op_type":"I","table_name":"ORDERS","schema_name":"UDATA","timestamp":"2025-01-15T10:23:45.123Z","data":{"ORDER_ID":1001,"AMOUNT":299.00,"STATUS":"PAID"}}
op_type里I是insert,U是update,D是delete。如果一条UPDATE语句把多个字段都改了,OGG会输出一条完整的UPDATE记录,包含修改后的所有字段值。
5. 启动同步链路后的第一轮数据校验与常见异常排查
配置全部完成、进程都RUNNING,不代表同步就真的没问题。链路启动后,我最先做的不是看Kafka里有没有消息,而是做三道校验,确保数据在链路里没有丢失、没有变形。
5.1 三件必做的校验
第一,全量行数核对。我在源库执行select count(*) from udata.orders,然后到Kafka里消费这个表的topic,估算消息总数。两者数量差在合理范围内(如果开了批量更新,一条SQL可能对应多条记录,要看具体变更)。这一步能快速发现是否有表漏配、Mapping是否错误。
第二,抽样字段对比。我在源库随机取一条主键数据,比如ORDER_ID=1001,然后在Kafka里消费到对应消息,逐字段对比类型和值。重点检查DECIMAL、DATE、TIMESTAMP这些容易被转成字符串的字段——OGG默认把Oracle的NUMBER类型转成BigDecimal,如果下游用JSON格式消费,可能出现精度丢失的问题。
第三,事务变更对比。在源库执行一条UPDATE,把某条记录的STATUS从PAID改成REFUND,然后在Kafka里消费,确认收到对应的U记录,且字段修改正确。这一步验证了日志抓取、Trail传输、Kafka写入的整条链路。
5.2 我实际遇到过的三类异常
第一类是Extract进程abend,报告里报"OGG-01224"或类似错误。这类问题大概率是表结构变更导致LogMiner无法解析。比如有人给表加了一个字段,但没执行ALTER TABLE ... ADD SUPPLEMENTAL LOG GROUP,OGG抓取时拿不到新字段的日志数据,就会abend。解决办法是重建这张表的补充日志组,或者重启Extract。
第二类是Kafka消息延迟逐渐增大。明明源库变更量不大,Kafka里却没有消息进来。用stats replikat查看Replicat进程的统计,发现它一直停留在某个位置,大概率是Kafka Handler写不进去。排查思路是先看custom_kafka_producer.properties里的bootstrap.servers能否连通,再用命令行手动producer发一条消息测试,最后看OGG的ggserr.log和报告文件。我遇到过的一个问题是Kafka topic的partition leader所在的broker磁盘满了,Producer无法写入,一直重试。
第三类是乱序问题。OGG默认情况下,同一事务内的操作在Replicat端是按顺序写入的,不同事务之间的顺序不做全局保证。如果下游强依赖全局顺序(比如需要按时间线聚合状态变化),必须在OGG层面开启相关配置。Kafka侧也可以设置分区键策略,比如按主键hash到固定分区,保证同一主键的变更一定落到同一个分区,这样同一个订单的所有变更就是有序的。我这里同步的是订单数据,我让Kafka Handler把主键作为消息key,这样同一条订单的变更天然有序,顺序问题迎刃而解。
5.3 CLOB字段同步的特殊处理
热词里有人搜"oracle clob",说明很多人在OGG同步CLOB字段时踩了坑。CLOB在Oracle里是大型字符串字段,OGG默认在解析日志时可能只读取前2000字节,超过的部分会丢。我处理过一次CLOB字段不同步的问题,现象是Kafka消息里该字段只有一半内容,后面的字符变成了乱码。
解决方案分两种:
- 单条CLOB数据不超过8KB的,可以在源端Extract参数里加
SQLCLOB参数,提高CLOB的读取上限。 - 单条CLOB数据很大的(比如存了长文本或JSON串),建议在表映射时用
COLMAP把CLOB字段转成VARCHAR2类型再传输,或者在下游单独处理,不要让OGG全量拉取CLOB内容,这会严重拖慢同步性能。
我在生产环境的选择是:把CLOB字段排除在OGG同步之外,下游需要这个字段时通过接口反查。原因很简单,核心同步链路不想被大字段拖累,这种大字段的数据价值密度低,却会占用大量的日志解析和Trail存储成本,性价比不划算。
6. 性能调优:从每秒几百条到几万条的改动清单
链路跑通之后,接下来就是性能和稳定性的事了。我最初上线时,OGG同步的吞吐只有每秒几百条,后来一步步调到了每秒几万条,这里面的关键参数调整值得完整记录。
6.1 瓶颈往往不在OGG,而在Kafka Producer
第一次发现吞吐上不去,我用stats replikat看到记录数只有每秒几百,但源库的变更量明明有几千。后来排查发现,瓶颈不在OGG的抓取,而在Kafka Handler写入Kafka时的批次设置。默认的Kafka Producer配置batch.size很小,每条消息都单独发,网络往返开销严重拖慢了吞吐。
调整策略:
- batch.size从16KB调到256KB,让Producer攒更多消息再发送。
- linger.ms从0调到100,允许消息在缓冲池里多等一会儿,凑大批次。
- buffer.memory从64MB调到512MB,避免高吞吐时缓冲区不够报错。
调整完之后,单条消息的延迟从原来的几十毫秒上升到一百毫秒左右,但吞吐直接翻了几十倍。如果你的业务对延迟不敏感而更看重吞吐,这个方向是对的。
6.2 GROUPTRANSOPS和REPORTCOUNT的配合
目标端Replicat参数里的GROUPTRANSOPS也很关键。它控制OGG在写入Kafka前最多把多少个事务操作合并为一组。我最初设的GROUPTRANSOPS 100,后面调到500,吞吐又涨了一截。但注意,这个参数不能无限调大,合并的事务多意味着提交延迟增大,一旦下游需要准实时看到数据,过大的GROUPTRANSOPS会拉高端到端延迟。我最终取了一个平衡值:200,延迟在可接受范围,吞吐也能保持在每秒一万条以上。
REPORTCOUNT EVERY 1 MINUTES, RATE这个参数看着平平无奇,但它会在OGG报告文件里定期输出处理速率。排查性能问题时,这个报告文件就是第一手证据,可以看到是哪个时间段、哪个表的处理速率出现了下降。
6.3 Kafka消费端拉高吞吐的配套设置
OGG把数据写进Kafka只是链路的一半,下游消费速度同样影响整体体验。如果你的消费端是Java应用,建议把消费线程数和分区数对齐,每个线程消费一个分区,并用手动提交offset的方式,避免自动提交导致的大量重复消费。
Kafka侧参数,consumer的fetch.min.bytes和fetch.max.wait.ms可以适当调大。默认情况下,单次poll拿到很少数据,消费端拉取频繁但效率低下。我把fetch.min.bytes设为1MB,fetch.max.wait.ms设为500,这样消费者一次能拉取到更多消息,减少了网络往返和线程空转。
6.4 关于Kafka消息延迟高和集群维护的提醒
热词里有一个是"kafka消息延迟高",这在OGG同步场景里通常会让人误判为Kafka集群的问题。实际上,OGG同步链路里消息延迟高的第一个检查点,不应该是Kafka,而是OGG的两个进程状态。用info all看Extract和Replicat是否都在RUNNING,再用lag命令看Replicat的延迟时间。如果Replicat的lag持续增长,再去查Kafka Producer配置和Kafka集群负载。很多次所谓的"Kafka延迟"问题,最后都是OGG目标端Replicat进程没有正确启动或者被abend了,数据根本没到Kafka,自然消费不到。
Kafka集群本身的容量规划也要跟上。OGG同步的大流量通常集中在业务高峰期,如果topic的副本数只有1,某个broker挂了,生产者和消费者都会受影响。建议topic副本数至少2,broker节点数至少3。磁盘方面,Kafka的保留策略(retention.ms)建议配合业务需求设置,不要无限期保留,否则磁盘迟早吃满。我这边核心topic保留7天,非核心topic保留24小时,既满足下游回溯需求,又控制了存储成本。
7. 稳定运行后的运维习惯与常见误区
链路跑起来不是终点,一个数据同步链路后续的运维工作量和初次搭建几乎一样多。我在稳定运行阶段养成的几个习惯,有效降低了告警频率。
每天定时看OGG进程状态和lag指标。我用的是一个简单的shell脚本,定时通过ggsci执行info all和lag,把结果输出到日志文件,配合监控平台做告警。只要lag持续超过设定阈值,就触发告警,运维同学介入。这比等到下游反馈数据不对再排查高效得多。
每次源端数据库变更,先检查是否影响OGG抓取。比如加字段、删字段、修改表名,都要同步评估影响。OGG对表结构变更敏感,尤其是新增字段如果不做补充日志配置,可能导致Extract进程无法解析,进而abend。我总结了一个习惯:任何结构变更前,先查一下这张表是否在OGG同步范围内,如果在,就把变更后的DDL发给OGG处理,或者干脆在变更窗口期内停止同步,变更完成后再恢复。
参数文件的备份和版本管理也不容忽视。OGG的参数文件散落在dirprm目录,我每次调整参数都会用Git管理目录快照,并记录变更原因。出现过一次同事误改了kafka.props里的TopicMappingTemplate,导致下游一个小时的消费逻辑全错,回滚参数才恢复。从那以后,参数文件改动必须走变更流程,并留存diff记录。
最后一个误区:不要迷信OGG能百分之百保证数据不丢。OGG的检查点机制已经非常可靠,但在极端情况下(比如磁盘损坏、日志丢失),源端归档日志缺失会导致部分数据无法补充。即使有OGG,上游数据库的备份策略和归档日志保留策略也不能放松。我这边归档日志保留7天,和OGG链路的延迟容忍度对齐,一旦出现日志丢失,还能从备份中恢复。
8. 一些值得提前知道的性能和兼容性心得
最后分享几个我在不同项目和环境中反复验证过的心得,这些不一定写在官方文档里,但确实影响使用体验。
关于Oracle数据库版本和OGG版本的关系,我遇到过一个特殊情况:Oracle 12c R2(12.2)和OGG 12.3.0.1的组合在某些Linux发行版上,下载后的OGG文件需要先赋可执行权限才能运行。这不是大问题,但新人在解压安装包后经常卡在启动不了这一步,报错是权限不足。解决方式很简单:chmod -R 755 /ogg_home。
关于字符集,Oracle和Kafka之间的字符集不一致会导致中文乱码。我的建议是,在Oracle侧将数据库字符集设置为AL32UTF8,Kafka侧消息统一用UTF-8编码。如果源库已经是ZHS16GBK,在OGG的参数文件里指定SETENV (NLS_LANG=AMERICAN_AMERICA.ZHS16GBK),确保Extract解析日志时按照正确的字符集读取。乱码问题的定位通常很隐蔽,查日志看不出异常,只有消费端看到中文变成问号才暴露。这一步的验证要提前做,不要等到业务系统反馈。
关于Kafka topic的数据量监控,我建议对每个topic单独配置消息量监控和消费延迟监控。OGG同步最怕的是消息积压,一旦Replicat或Kafka生产者出现性能瓶颈,topic里的消息会快速积压,下游看到的数据时间越来越滞后。我在生产环境配置了消费延迟超过10分钟即告警的规则,实际运行中这个阈值基本不会被触发,一旦触发必有大问题。
关于GGBD和Kafka的版本升级,我不建议在生产环境刚上链路的第一周就升级任何组件。先让链路在固定版本下稳定运行一段时间,记录基准性能数据,再做升级评估。OGG和Kafka的组合涉及Oracle数据库、OGG组件、GGBD组件、Kafka集群四个部分,任何一个升级都可能影响整条链路。我这边当年因为Kafka集群从0.10升级到2.x,GGBD的Kafka Handler版本不匹配,导致Replicat进程无法启动,最后花了一个晚上回滚Kafka版本才恢复。从此我养成了升级前必查版本兼容矩阵的习惯,并且操作前必做GGBD在测试环境的完整验证。
OGG同步Oracle到Kafka,说难不难,说简单也不简单。它把Oracle日志解析、数据捕获、网络传输、消息队列写入这些复杂机制封装在一起,但真正用起来还是要对每一层的原理有足够理解。我自己在这一路上踩过的坑、调过的参、排查过的故障,都变成了上面这些文字。如果你正在规划或维护这样一条链路,希望这些经验能帮你更快地把数据稳稳地送进Kafka。
