# Oracle CDC 同步数据 Kafka
## 简介
本章节介绍如何使用Oracle自带的LogMiner来提取数据归档日志,并识别输出到Kafka集群,用于后期实时计算业务场景。
## 环境
OS: Windows 10
内存:32GB
部署:docker desktop
## 效果
### docker服务
![image-20200821145345466](image/ora2kafka-20200821145345466.png)
## StreamSets DataCollector
![image-20200821145548943](image/ora2kafka-20200821145548943.png)
#### 同步作业运行效果
![image-20200821145659612](image/ora2kafka-20200821145659612.png)
#### Oracle增删改操作更新Kafka
![image-20200821145927367](image/ora2kafka-20200821145927367.png)
## 一、Oracle11g 容器搭建
从docker hub上获取一个oracle11g的镜像,在本地docker中安装好oracle容器
```shell
docker run --name oracle11g -d -p 49162:1521 -e ORACLE_ALLOW_REMOTE=true oracleinanutshell/oracle-xe-11g
```
### 任务1:启用LogMiner
#### (1)作为具有DBA特权的用户登录数据库
#### (2)检查数据库日志记录模式:
```sql
select log_mode from v$database;
```
如果返回ARCHIVELOG,跳到任务2.
如果返回NOARCHIVELOG,继续以下步骤:
#### (3)关闭数据库:
```sql
shutdown immediate;
```
#### (4)启动、挂载数据库
```
startup mount;
```
#### (5)配置启用存档、打开数据库
```sql
alter database archivelog;
alter database open;
```
### 任务2:为数据库或表启用补充日志记录
要从重做日志检索数据,LogMiner需要数据库或表的补充日志记录。
对于要使用的每个表,在表级别上至少启用主键或“标识键”日志记录。使用标识键日志记录,记录只包括主键和更改的字段。
由于Oracle已知的问题,要为表启用补充日志记录,必须首先为数据库启用最小补充日志记录。
要在origin生成的记录中包含所有字段,请在表或数据库级别启用完整的补充日志记录。完整的补充日志记录提供了来自所有列的数据,包括未更改数据以及主键和已更改列的数据。
#### (1)要验证是否为数据库启用了补充日志记录,请运行以下命令:
```sql
SELECT supplemental_log_data_min, supplemental_log_data_pk, supplemental_log_data_all FROM v$database;
```
如果都返回YES,则跳到任务3;
#### (2)启用标识键或完整补充日志记录。
**启用标识键日志记录**
可以为数据库中的单个表或所有表启用标识键日志记录:
- 单个表
```sql
ALTER DATABASE ADD SUPPLEMENTAL LOG DATA;
ALTER TABLE .
```
- 所有表
```sql
ALTER DATABASE ADD SUPPLEMENTAL LOG DATA (PRIMARY KEY) COLUMNS;
```
**启用完整的补充日志记录**
可以为数据库中的单个表或所有表启用完整的补充日志记录.
- 单个表
```sql
ALTER DATABASE ADD SUPPLEMENTAL LOG DATA;
ALTER TABLE .
```
- 所有表
```sql
ALTER DATABASE ADD SUPPLEMENTAL LOG DATA (ALL) COLUMNS;
```
#### (3)提交变更:
```sql
ALTER SYSTEM SWITCH LOGFILE;
```
### 任务3:创建用户账号
创建一个与OCC Origin一起使用的用户帐户。可以根据所使用的Oracle版本以不同的方式创建帐户。
#### **Oracle 12c multitenant databases**
1. 作为具有DBA特权的用户登录数据库。
2. 创建常用用户帐号:
```sql
ALTER SESSION SET CONTAINER=cdb$root;
CREATE USER IDENTIFIED BY CONTAINER=all;
GRANT create session, alter session, set container, select any dictionary, logmining, execute_catalog_role TO CONTAINER=all;
ALTER SESSION SET CONTAINER=;
GRANT select on .
```
#### **Oracle 12c standard databases**
1. 作为具有DBA特权的用户登录数据库。
2. 创建常用用户帐号:
```sql
CREATE USER IDENTIFIED BY ;
GRANT create session, alter session, select any dictionary, logmining, execute_catalog_role TO ;
GRANT select on .
```
#### **Oracle 11g databases**
1. 作为具有DBA特权的用户登录数据库。
2. 创建实时数据同步用户帐号: rtsync
```sql
CREATE USER rtsync IDENTIFIED BY rtsync0820#;
GRANT create session, alter session, execute_catalog_role, select any dictionary, select any transaction, select any table to rtsync;
GRANT select on v_$logmnr_parameters to rtsync;
GRANT select on v_$logmnr_logs to rtsync;
GRANT select on v_$archived_log to rtsync;
GRANT select on gv_$logmnr_parameters to rtsync;
GRANT select on gv_$logmnr_logs to rtsync;
GRANT select on gv_$archived_log to rtsync;
```
3. 将需要实时同步日志的表的select权限赋权给rtsync用户:
```sql
GRANT select on HR.JOBS TO rtsync;
```
### 任务4:抽取LogMIner的catalog日志
oracle的catalog日志只能获取对表的DML操作日志,如果要连oracle的DDL日志也获取到,则需要配置redolog
#### 使用oracle的预设存储过程,在当前SCN中启动LogMiner
1. 数据库主机oracle用户登录,先确保/tmp目录中没有add_logfiles.sql文件
2. 作为具有DBA特权的用户登录数据库。
```sql
--- 添加已经存在的logfiles,并且设置DBMS_LOGMNR持续的从archivelogs中获取归档日志
SQL> set linesize 200 trimspool on pagesize 0
--- 添加已经存在的logfiles,忽略备份(STANDBY)的logfiles
SQL> select 'exec dbms_logmnr.add_logfile('''||member||''')'
from v$logfile
where type <> 'STANDBY'
and member in (select min(member) from v$logfile group by group#)
/
```
查询生成语句输出如下:
```shell
exec dbms_logmnr.add_logfile('/u01/app/oracle/fast_recovery_area/XE/onlinelog/o1_mf_2_g6c5nj25_.log')
exec dbms_logmnr.add_logfile('/u01/app/oracle/fast_recovery_area/XE/onlinelog/o1_mf_1_g6c5nhsl_.log')
```
3. 将上面的输出spool写入到/tmp/add_logfiles.sql文件中
```SQL
--- 将上面的输出spool写入到/tmp/add_logfiles.sql文件中
SQL> spool /tmp/add_logfiles.sql
SQL> /
SQL> spool off
```
4. 执行如下语句,开始启动logminer
```sql
SQL> exec dbms_logmnr.start_logmnr(options => DBMS_LOGMNR.DICT_FROM_ONLINE_CATALOG + DBMS_LOGMNR.CONTINUOUS_MINE + DBMS_LOGMNR.COMMITTED_DATA_ONLY)
```
5. 查询日志是否已经启动logMiner
```sql
select log_mode from v$database;
```
如果输出为ARCHIVELOG,则说明当前已经打开了logminer的catalog日志监听
### 任务5:抽取Log Miner Dictionary(Redo Logs)
当需要获取oracle的DDL日志,即表结构的各种变化,则需要使用redo logs作为字典源,必须在启动pipeline之前将Log Miner字典提取到redo logs。而且需要定期重复此步骤,以确保包含字典的redo logs仍然可用。
Oracle建议只在非高峰时间提取字典,因为提取会消耗数据库资源。
要提取Oracle 11g或12c数据库的字典,请运行以下命令:
```sql
EXECUTE DBMS_LOGMNR_D.BUILD(OPTIONS=> DBMS_LOGMNR_D.STORE_IN_REDO_LOGS);
```
要提取Oracle 12c多租户(CDB+多个PDB)数据库的字典,请运行以下命令:
```sql
ALTER SESSION SET CONTAINER=cdb$root;
EXECUTE DBMS_LOGMNR_D.BUILD(OPTIONS=> DBMS_LOGMNR_D.STORE_IN_REDO_LOGS);
```
## 二、安装StreamSets的DataCollector工具
如果你当前使用的是CDH环境,那么可以直接部署安装对应版本的DataCollector工具,如果你和我一样是一个测试玩家,那么可以和我一起继续玩下去
1. 命令行获取`streamsets/datacollector:3.14.0`的镜像
```shell
docker pull streamsets/datacollector:3.14.0
```
2. 创建容器之前,先需要初始化几个volume,为了组件扩展方便一点,我选择了这种部署方式
```shell
# 用于挂在配置文件
docker volume create sdc-conf
# 用于存放pipeline的元数据
docker volume create sdc-data
# 用于存放streamsets官方提供的组件库
docker volume create sdc-stagelibs
# 用于存放临时资源
docker volume create sdc-resources
# 用于存放用户自定义开发的组件
docker volume create sdc-user-libs
# 用于存放组件需要用到的扩展jar包,例如各种数据库的JDBC驱动
docker volume create sdc-streamsets-libs-extras
```
3. 获取完毕之后,启动一个对应的容器:增加一个-verbose参数是为了输出多一点日志信息
```shell
docker run --name sdc -d \
-v sdc-conf:/etc/sdc \
-v sdc-data:/data \
-v sdc-stagelibs:/opt/streamsets-datacollector-3.14.0/streamsets-libs \
-v sdc-resources:/resources \
-v sdc-user-libs:/opt/streamsets-datacollector-3.14.0/user-libs \
-v sdc-streamsets-libs-extras:/opt/streamsets-datacollector-3.14.0/streamsets-libs-extras \
-p 18630:18630 --network host streamsets/datacollector:3.14.0 dc -verbose
```
4. 此时你可以从本地登录datacollector的操作界面了,默认密码admin
![image-20200821151941970](image/ora2kafka-20200821151941970.png)
## 三、SDC安装JDBC和KAFKA组件
### 创建一个docker容器,部署操作volume
由于部署主机为Windows10环境,所以我们没有办法直接在本地访问docker创建出来的volume,所以我们先需要安装一个host docker container用户访问windows的volume,从而给SDC扩展功能。
```shell
docker run --net=host --ipc=host --uts=host --pid=host -it --security-opt=seccomp=unconfined --privileged --rm -v /:/host centos /bin/bash
```
切换主机环境:
```shell
chroot /host
```
如此该环境和windows的环境基本融合了
```
ls
```
![image-20200821152738840](image/ora2kafka-20200821152738840.png)
C就是我们windows的C盘,D就是D盘
```shell
docker volume ls
```
![image-20200821152916120](image/ora2kafka-20200821152916120.png)
```shell
docker inspect sdc-stagelibs
```
![image-20200821153238220](image/ora2kafka-20200821153238220.png)
如此我们就可以复制本地的扩展组件到对应的volume卷中了,如上图,我在sdc-stagelibs卷中创建了jdbc-lib和kafka_2_0-lib目录,用于安装组件
```shell
cd /var/lib/docker/volumes/sdc-stagelibs/_data
mkdir streamsets-datacollector-jdbc-lib/lib -p
mkdir streamsets-datacollector-apache-kafka_2_0-lib/lib -p
```
复制组件jar包到这两个目录中,组件jar包可以按照本套教程的第8章节《WSL编译》进行编译获得
```shell
cd /var/lib/docker/volumes/sdc-stagelibs/_data/streamsets-datacollector-jdbc-lib/lib
cp /host_mnt/d/iProject/streamsets/datacollector-3.14.0/dist/target/streamsets-datacollector-3.14.0/streamsets-datacollector-3.14.0/streamsets-libs/streamsets-datacollector-jdbc-lib/lib/* ./
cd /var/lib/docker/volumes/sdc-stagelibs/_data/streamsets-datacollector-apache-kafka_2_0-lib/lib
cp /host_mnt/d/iProject/streamsets/datacollector-3.14.0/dist/target/streamsets-datacollector-3.14.0/streamsets-datacollector-3.14.0/streamsets-libs/streamsets-datacollector-apache-kafka_2_0-lib/lib/* ./
```
部署完毕之后,我们重新启动这个sdc容器服务。
## 四、启动KAFKA、JDBC组件,上传JDBC驱动
因为是手动安装,所以还需要手动注册组件,点击右上方Package Manager按钮,分别注册Kafka和JDBC组件,如下:
### 点亮Kafka组件
![image-20200821154627723](image/ora2kafka-20200821154627723.png)
### 点亮JDBC组件
![image-20200821154841274](image/ora2kafka-20200821154841274.png)
### 创建一个测试用的pipline,就可以看到对应组件的图标了
![image-20200821155038311](image/ora2kafka-20200821155038311.png)
### 继续上传JDBC组件
![image-20200821155238344](image/ora2kafka-20200821155238344.png)
![image-20200821155312905](image/ora2kafka-20200821155312905.png)
可以把常用的JDBC驱动全部传上去。传完之后,可以浏览一下`sdc-streamsets-libs-extras`这个卷下面,就是我们传上去的jar包。
![image-20200821155517174](image/ora2kafka-20200821155517174.png)
## 五、创建一个pipeline,用于从Oracle的日志中获取发生变化的数据事件,写入数据到kafka
### 配置Oracle CDC Client组件,用于获取Oracle logminer日志
![image-20200821160138301](image/ora2kafka-20200821160138301.png)
配置一张表HR.JOBS,获取INSERT、UPDATE、DELETE、SELECT_FOR_UPDATE的操作事件,字典源选择为Online Catalog
![image-20200821160244057](image/ora2kafka-20200821160244057.png)
配置JDBC链接串
![image-20200821160312867](image/ora2kafka-20200821160312867.png)
配置用户密码
![image-20200821160407454](image/ora2kafka-20200821160407454.png)
### 配置一个kafka producer组件,用于向kafka写入数据
在对应的kafka集群创建一个topic: orasync
![image-20200821160924318](image/ora2kafka-20200821160924318.png)
数据写入kafka的格式使用JSON
![image-20200821161058721](image/ora2kafka-20200821161058721.png)
### 配置一个JS组件,加入操作信息
默认的写入kafka的内容只有数据体,但是我们还想要区分一下到底是INSERT/DELETE/UPDATE的哪一种,所以我们在中间添加了一个JS的Processors组件
![image-20200821161236406](image/ora2kafka-20200821161236406.png)
添加我们编写的脚本
![image-20200821161539127](image/ora2kafka-20200821161539127.png)
脚本内容:
```javascript
// Sample JavaScript code
var records = sdc.records;
for(var i = 0; i < records.length; i++) {
try {
// 克隆创建一个Record,这个是Streamsets DataCollector的内部数据传输结构
var newRecord = sdc.createRecord(records[i].sourceId);
// 创建一个map
var newMap = sdc.createMap(true);
// 按照官网的提示,直接获取header信息,可以records[i].oracle.cdc.operation,但是我并为测试成功,暂时写了一个循环,将表名和操作类型取出来
var headersMap = records[i].attributes
for(headKey in headersMap){
var headValue = headersMap[headKey];
if (headKey=='oracle.cdc.operation') {
newMap['opt']=headValue;
} else if (headKey=='oracle.cdc.table') {
newMap['table']=headValue;
}
}
// 添加原有数据到newMap
for(key in records[i].value){
var value = records[i].value[key];
newMap[key] = value;
}
// 赋值引用
newRecord.value = newMap;
// Write record to processor output
sdc.output.write(newRecord);
} catch (e) {
// Send record to error
sdc.error.write(records[i], e);
}
}
```
## 总结
最后将我们创建好的pipeline运行起来,对目标表进行增删改操作,去对应的kafka里面查看写入情况,就达到了我们开头介绍的效果。
一键复制
编辑
Web IDE
原始数据
按行查看
历史