debezium+kafka实现sqlserver数据同步(debezium-connector-sqlserver)

1.情景展示

在企业当中,往往会存在不同数据库之间的表的数据需要保持一致的情况(数据同步)。

如何将A库a表的数据同步至B库a表当中呢?(包含:新增、修改和删除)

往往不仅仅需要保持数据的一致性,还要保证数据的即时性,即:A库a表的数据发生变化后,B库a表也能立刻同步变化。

实时保持两表数据的一致性。

如何实现?

2.具体分析

要想及时知晓A库a表的数据变化,我们需要读取数据库的操作日志,从日志当中提取a表的操作日志。(至少要包含:新增、修改和删除)。

以sqlserver为例,就是读取sqlserver的变更日志,而读取日志提取a表操作记录,我们可以通过组件来完成,无需自己手动解析日志。

SQL Server 有表的变更日志操作记录。SQL Server 的变更数据捕获(Change Data Capture,简称 CDC)功能可以记录 SQL Server 表的插入、更新和删除活动。SQL Server 的操作会写日志,这是 CDC 捕获数据的来源。当开启 CDC 的源表进行插入、更新和删除活动时,CDC 会将变更数据捕获到变更表中,通过 CDC 提供的查询函数,我们可以捕获这部分数据。

为了使用 CDC,需要满足一些必要条件,如 SQL Server 版本需为 2008 以上,需要开启代理服务(作业),磁盘要有足够的空间保存日志文件,表必须要有主键或者是唯一索引。开启 CDC 的过程包括开启数据库的 CDC 功能,以及针对具体表的 CDC 功能。

3.解决方案

debezium-connector-sqlserver插件,可以很好的完成这个任务。

Debezium是一个开源的数据库事件捕捉和发布平台,旨在提供可靠的实时数据流。它基于分布式日志(如Apache Kafka)来捕获并传输数据库的变更事件,从而实现高效的数据同步和分发。通过使用Debezium的sqlserver连接器,可以轻松地将sqlserver数据库中的DML操作(包括INSERT、UPDATE、DELETE)的变更事件提取出来,并以实时的方式推送到Kafka消息队列中。

由于debezium-connector-sqlserver插件是结合kafka来实现的,我们自然需要用到kafka。

debezium-connector-sqlserver插件最终实现的效果是:监听sqlserver的变更日志,实时捕获sqlserver数据变更记录,并将变化的数据发布到kafka的主题当中

Debezium的sqlserver连接器捕获并记录sqlserver服务器上数据库中发生的行级更改,还包括在连接器运行时添加的表。可以通过Connector配置,为指定的schema和表获取更改事件,或者忽略、屏蔽或截断特定列中的值等过滤操作。

Debezium通过使用本地LogMiner数据库包或XStream API从sqlserver获取更改事件。

在开始之前,我们需要先了解一下kafka connect,通过它我们可以将其它系统与kakfa进行连接,完成主题的发布与订阅。

具体就是:通过该服务,我们可以使用REST API的方式调用kafka服务器来完成消息的发布与订阅。

更多关于kafka connect的用法,见文末推荐。

使用debezium官方提供的source connector,部署到apache kafka connect中,debezium的connector捕获到源数据库数据更新,发送到kafka集群中。

插件下载

地址:https://debezium.io/releases/

点击debezium的最新版本(More info)。

支持的版本号说明:

点击“installation guide”。

2.5.0.Final版本地址:https://debezium.io/documentation/reference/2.5/install.html

connector插件列表展示如下。

mysql connector 2.5.0版插件地址:https://repo1.maven.org/maven2/io/debezium/debezium-connector-mysql/2.5.0.Final/debezium-connector-mysql-2.5.0.Final-plugin.tar.gz

oracle connector 2.5.0版插件地址:https://repo1.maven.org/maven2/io/debezium/debezium-connector-oracle/2.5.0.Final/debezium-connector-oracle-2.5.0.Final-plugin.tar.gz

SQLserver connector 2.5.0版插件地址:https://repo1.maven.org/maven2/io/debezium/debezium-connector-sqlserver/2.5.0.Final/debezium-connector-sqlserver-2.5.0.Final-plugin.tar.gz

插件源码地址:https://github.com/debezium/debezium

插件安装

下载成功后,进行解压。

来到KAFKA_HOME目录下,创建一个plugins目录。

并将刚才解压的插件移到plugins目录下。

重命名在插件后面添加版本号。

参数说明

说明文档:

https://debezium.io/documentation/reference/2.5/connectors/sqlserver.html

这里我只讲自己用的的参数,其余参数说明及用法,可通过上述说明文档自行查阅。

具体的数据结构,下面有。 

name属性:代表的是连接器的名称,该名称具有唯一性!(名字随便起,但必须唯一)。

名字最好能让人望文生义,如:debezium-connector-source-sqlserver-73,这一看就知道:创建的是Source Connector,用的插件是:debezium-connector-oracle。

connector.class属性:Source Connector的实现类,在这里我们需要填:io.debezium.connector.oracle.SqlServerConnector(它是debezium-connector-oracle的源连接器)。

database.hostname属性:数据库服务器所属IP,如:127.0.0.1。

database.port属性:数据库端口号,如:1521。

database.user属性:数据库用户名,如:test。

database.password属性:数据库密码,如:123456。

database.server.id属性:给数据库设置id,该值具有唯一性,默认取值范围:[5400,6400],数据类型:int。

database.names属性:要监控的数据库名称,如:test。

database.encrypt属性:通过jdbc连接sqlserver使用ssl加密,默认值为true。

需要将此值设为false

不关闭的话,报错如下:com.microsoft.sqlserver.jdbc.SQLServerException: “Encrypt”属性设置为“true”且 “trustServerCertificate”属性设置为“false”,但驱动程序无法使用安全套接字层 (SSL) 加密与 SQL Server 建立安全连接。

database.sqlserver.agent.status.query属性:查询sqlserver代理的运行状态的sql语句,默认使用的sql语句为:

SELECT CASE WHEN dss.[status]=4 THEN 1 ELSE 0 END AS isRunning FROM [#db].sys.dm_server_services dss WHERE dss.[servicename] LIKE N'SQL Server Agent (%'

debezium-connector-sqlserver-2.5.0.Final.jar\io\debezium\connector\sqlserver\SqlServerConnection.class中的定义的AGENT_STATUS_QUERY的默认值是上面这个sql。

sqlserver代理服务是英文状态时,用这个sql查询自然没有问题;但是,如果安装的代理服务用的中文的话,那就查不到了,查不到就会报错。

因为在sqlserver当中,要想使用cdc必须开启代理服务,debezium-connector-sqlserver插件检测不到这个服务,自然就不工作了。

所以,我们需要将查询sqlserver代理的运行状态的sql语句指定成下面这个就可以了。

SELECT CASE WHEN dss.[status]=4 THEN 1 ELSE 0 END AS isRunning FROM [#db].sys.dm_server_services dss WHERE dss.[servicename] LIKE N'SQL Server Agent (%' OR dss.[servicename] LIKE N'SQL Server 代理 (%'

代理服务名称无论是中文还是英文,都能查得到。

schema.include.list属性:数据库架构,默认值:无。

这里需要设置成dbo

在sqlserver中,默认使用的架构为dbo(database owner)。

tasks.max属性:要运行的任务数,默认值:1。该值需要<=database.names的值。

table.include.list属性:要捕获数据变更记录的表名称列表,可以同时监控多张表。构成:schemaName.tableName,多个使用逗号隔开,如:dbo.t_patient。

schema.history.internal.store.only.captured.tables.ddl属性:默认值:false,代表的含义是:debezium会将被监控数据库下所有的表的变更记录,都进行捕获。

而实际上,我们只需捕获table.include.list里面设置的表,而不是所有的表。需将此值设为:true。

column.include.list属性:可以设置只捕获表中的部分字段变更记录,可以监控多张表的部分字段变更记录。构成:schemaName.tableName.columnName,多个请使用逗号隔开,如:dbo.T_PATIENT_ZS.CREATE_TIME,dbo.T_PATIENT_ZS.ID。

schema.history.internal.kafka.topic属性:连接器将在其中存储数据库模式历史记录的Kafka主题的全名,如:schema-history-test-63。

schema.history.internal.kafka.bootstrap.servers属性:kafka服务器地址,如:localhost:9092。

event.processing.failure.handling.mode属性:默认值:fail,表示的是:schema的处理模式。可选值:[fail,warn,ignore]。我们选择warn,而不是直接报错,更容易发现问题。

topic.prefix属性:将要发布的主题的名称前缀,该值具有唯一性(kafka会根据此主题前缀来生成主题名称。消费者需要根据topic名称来订阅数据)。

errors.log.enable属性:默认值:false。是否输出错误日志,可将值设为true。

skipped.operations属性:默认值:t。不需要监控的操作,可选值:c(insert/create),u(update),d(delete),t(truncate),none。

snapshot.mode属性:快照模式,默认值:initial,可选值:[initial,initial_only,when_needed,never,schema_only,schema_only_recovery]。

initial(默认)(初始全量,后续增量):连接器执行数据库的初始一致性快照,快照完成后,连接器开始为后续数据库更改流式传输事件记录。

initial_only(只全量,不增量):连接器只执行数据库的初始一致性快照,不允许捕获任何后续更改的事件。

schema_only(只增量,不全量):连接器只捕获所有相关表的表结构,不捕获初始数据,但是会同步后续数据库的更改记录。 

4.运行

准备工作

启动服务

启动zookeeper,启动kafka,启动kafka connect。

如果没有安装kafka或者不知道怎么启动,具体操作见文末推荐。

日志处理

第一步:数据库开启CDC

用账户密码登录要监控的表所在的数据库,并执行以下命令。

EXEC sys.sp_cdc_enable_db

或者

USE 要开启cdc的库名;
EXEC sys.sp_cdc_enable_db;  
GO

开启成功后,该库下模式里面会多出一个“cdc”。(默认只有dbo)

查询开启了cdc的数据库:

SELECT
	[name],
	is_cdc_enabled
FROM
	sys.databases
WHERE
	is_cdc_enabled = 1;

第二步:为表开启CDC

在sqlserver中,只为库开启cdc,是不行的。

我们还要给表开启cdc。

exec sys.sp_cdc_enable_table 
@source_schema='dbo',
@source_name='要监控的表名',
@role_name=null,
@capture_instance=DEFAULT

说明:

要监控的表,需要我们使用上述命令,一个一个的开,虽然很麻烦,但别无它法。

开启成功后,我们可以打开cdc模式。

以上两张表,就是当前库下开启cdc的表,当然,源表表名并不是如此。

我们也可以使用sql来查询当前库下已经开启cdc的表有哪些。

SELECT
	t.name AS TableName,
	s.name AS SchemaName
FROM
	sys.tables t
INNER JOIN 
    sys.schemas s ON
	t.schema_id = s.schema_id
WHERE
	t.is_tracked_by_cdc = 1;

另外,在sqlserver中,如果源表已经开启了cdc,当源表表结构发生变化时,该cdc是不会自动更新的,仍然监控原来的表字段信息

查询cdc表的详细信息

EXEC sys.sp_cdc_help_change_data_capture;

或者

USE 数据库名称
GO
EXEC sys.sp_cdc_help_change_data_capture
GO
开启代理服务

在sqlserver当中,要想使用cdc功能,必须启动代理服务

查询是否开启了agent服务。

SELECT
	CASE
		WHEN dss.[status] = 4 THEN 1
		ELSE 0
	END AS isRunning
FROM
	[库名].sys.dm_server_services dss
WHERE
	dss.[servicename] LIKE N'SQL Server Agent (%'
	OR dss.[servicename] LIKE N'SQL Server 代理 (%';

1:表示,查询的数据库已经开启了代理服务。 

发布主题

接口地址:

http://localhost:8083/connectors

请求数据:

{
    "config": {
        "column.include.list": "dbo.consultinfo.CONSULT_ADVICE,dbo.consultinfo.CONSULT_DATE,dbo.consultinfo.CONSULT_DOCTOR_NAME,dbo.consultinfo.CONSULT_FORM_NO,dbo.consultinfo.CONSULT_ORG_CODE,dbo.consultinfo.CONSULT_ORG_NAME,dbo.consultinfo.CONSULT_REASON,dbo.consultinfo.HEALTH_RECORD_NO,dbo.consultinfo.LAST_UPDATE_DTIME,dbo.consultinfo.NAME,dbo.consultinfo.ORG_CODE,dbo.consultinfo.PATIENT_ID,dbo.consultinfo.RESP_DOCTOR_NAME,dbo.consultinfo.ZOE_SYS_CLIENT_CODE,dbo.consultinfo.ZOE_SYS_COLLECT_TIME",
        "connector.class": "io.debezium.connector.sqlserver.SqlServerConnector",
        "database.encrypt": false,
        "database.hostname": "192.168.0.1",
        "database.names": "cdc_test_20240524",
        "database.password": "password",
        "database.port": "1433",
        "database.server.id": 142,
        "database.sqlserver.agent.status.query": "SELECT CASE WHEN dss.[status]=4 THEN 1 ELSE 0 END AS isRunning FROM [#db].sys.dm_server_services dss WHERE dss.[servicename] LIKE N'SQL Server Agent (%' OR dss.[servicename] LIKE N'SQL Server 代理 (%'",
        "database.user": "sa",
        "errors.log.enable": true,
        "errors.log.include.messages": true,
        "event.processing.failure.handling.mode": "warn",
        "schema.history.internal.kafka.bootstrap.servers": "localhost:9092",
        "schema.history.internal.kafka.topic": "schema-history-cdc_test_20240524-142",
        "schema.history.internal.store.only.captured.databases.ddl": true,
        "schema.history.internal.store.only.captured.tables.ddl": true,
        "schema.include.list": "dbo",
        "skipped.operations": "none",
        "snapshot.mode": "initial",
        "table.include.list": "dbo.consultinfo",
        "topic.prefix": "topic-cdc_test_20240524-142"
    },
    "name": "debezium-connector-source-sqlserver-142"
}

响应数据:

{
    "config": {
        "column.include.list": "dbo.consultinfo.CONSULT_ADVICE,dbo.consultinfo.CONSULT_DATE,dbo.consultinfo.CONSULT_DOCTOR_NAME,dbo.consultinfo.CONSULT_FORM_NO,dbo.consultinfo.CONSULT_ORG_CODE,dbo.consultinfo.CONSULT_ORG_NAME,dbo.consultinfo.CONSULT_REASON,dbo.consultinfo.HEALTH_RECORD_NO,dbo.consultinfo.LAST_UPDATE_DTIME,dbo.consultinfo.NAME,dbo.consultinfo.ORG_CODE,dbo.consultinfo.PATIENT_ID,dbo.consultinfo.RESP_DOCTOR_NAME,dbo.consultinfo.ZOE_SYS_CLIENT_CODE,dbo.consultinfo.ZOE_SYS_COLLECT_TIME",
        "connector.class": "io.debezium.connector.sqlserver.SqlServerConnector",
        "database.encrypt": "false",
        "database.hostname": "192.168.0.1",
        "database.names": "cdc_test_20240524",
        "database.password": "password",
        "database.port": "1433",
        "database.server.id": "142",
        "database.sqlserver.agent.status.query": "SELECT CASE WHEN dss.[status]=4 THEN 1 ELSE 0 END AS isRunning FROM [#db].sys.dm_server_services dss WHERE dss.[servicename] LIKE N'SQL Server Agent (%' OR dss.[servicename] LIKE N'SQL Server 代理 (%'",
        "database.user": "sa",
        "errors.log.enable": "true",
        "errors.log.include.messages": "true",
        "event.processing.failure.handling.mode": "warn",
        "name": "debezium-connector-source-sqlserver-142",
        "schema.history.internal.kafka.bootstrap.servers": "localhost:9092",
        "schema.history.internal.kafka.topic": "schema-history-cdc_test_20240524-142",
        "schema.history.internal.store.only.captured.databases.ddl": "true",
        "schema.history.internal.store.only.captured.tables.ddl": "true",
        "schema.include.list": "dbo",
        "skipped.operations": "none",
        "snapshot.mode": "initial",
        "table.include.list": "dbo.consultinfo",
        "topic.prefix": "topic-cdc_test_20240524-142"
    },
    "name": "debezium-connector-source-sqlserver-142",
    "tasks": [
        {
            "connector": "debezium-connector-source-sqlserver-142",
            "task": 0
        }
    ],
    "type": "source"
}

主题发布成功后,debezium将会自动捕获sqlserver数据变更日志,并将数据推送到kafka当中。

查询主题

主题的生成条件说明:

debezium source connector启动之后,当指定监控的表数据发生变更时,它才会创建主题(也就是往kafka当中推送数据)。

 

如果处于监控中的表一直没有数据变更,那这个主题就一直不会产生。

具体就是:

当snapshot.mode=schema_only时,source connector启动后,不会立马创建主题,而是直到源表数据发生变化时,才会创建主题(也就是:debezium-sqlserver插件通过读取日志,监控到该表发生了变化,会自动将变化的数据推送到kafka上,这时,主题自然就生成了)。

当snapshot.mode=initial时,source connector启动后,如果源表已经有数据,它会立马创建主题;

反之,如果源表一条数据都没有,不会立马创建主题,直到源表有新增数据时,才会创建主题。 

主题生成规则:

一张表对应一个主题。

sqlserver主题构成:topic.prefix.databaseName.schemaName.tableName

这里t_patient表对应的主题是:topic-cdc_test_20240524-142.cdc_test_20240524.dbo.consultinfo。

查看已发布的主题

请求地址:

http://localhost:8083/connectors/debezium-connector-source-sqlserver-142/topics

响应数据: 

{"debezium-connector-source-sqlserver-142":{"topics":["topic-cdc_test_20240524-142","topic-cdc_test_20240524-142.cdc_test_20240524.dbo.consultinfo"]}}

消费数据

通过产生的主题消费数据。

切换到KAFKA_HOME\bin\windows目录下,输入cmd,按Enter键打开cmd窗口。

输入以下命令并执行

kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic topic-cdc_test_20240524-142.cdc_test_20240524.dbo.consultinfo  --from-beginning

执行结果如下:

{"schema":{"type":"struct","fields":[{"type":"struct","fields":[{"type":"string","optional":false,"field":"ORG_CODE"},{"type":"string","optional":false,"field":"PATIENT_ID"},{"type":"string","optional":false,"field":"CONSULT_FORM_NO"},{"type":"int64","optional":false,"name":"io.debezium.time.Timestamp","version":1,"field":"LAST_UPDATE_DTIME"},{"type":"string","optional":true,"field":"HEALTH_RECORD_NO"},{"type":"string","optional":true,"field":"NAME"},{"type":"string","optional":true,"field":"CONSULT_REASON"},{"type":"string","optional":true,"field":"CONSULT_ADVICE"},{"type":"string","optional":true,"field":"CONSULT_ORG_CODE"},{"type":"string","optional":true,"field":"CONSULT_ORG_NAME"},{"type":"string","optional":true,"field":"CONSULT_DOCTOR_NAME"},{"type":"string","optional":true,"field":"RESP_DOCTOR_NAME"},{"type":"int64","optional":false,"name":"io.debezium.time.Timestamp","version":1,"field":"CONSULT_DATE"},{"type":"string","optional":true,"field":"ZOE_SYS_CLIENT_CODE"},{"type":"int64","optional":true,"name":"io.debezium.time.Timestamp","version":1,"field":"ZOE_SYS_COLLECT_TIME"}],"optional":true,"name":"topic-cdc_test_20240524-142.cdc_test_20240524.dbo.consultinfo.Value","field":"before"},{"type":"struct","fields":[{"type":"string","optional":false,"field":"ORG_CODE"},{"type":"string","optional":false,"field":"PATIENT_ID"},{"type":"string","optional":false,"field":"CONSULT_FORM_NO"},{"type":"int64","optional":false,"name":"io.debezium.time.Timestamp","version":1,"field":"LAST_UPDATE_DTIME"},{"type":"string","optional":true,"field":"HEALTH_RECORD_NO"},{"type":"string","optional":true,"field":"NAME"},{"type":"string","optional":true,"field":"CONSULT_REASON"},{"type":"string","optional":true,"field":"CONSULT_ADVICE"},{"type":"string","optional":true,"field":"CONSULT_ORG_CODE"},{"type":"string","optional":true,"field":"CONSULT_ORG_NAME"},{"type":"string","optional":true,"field":"CONSULT_DOCTOR_NAME"},{"type":"string","optional":true,"field":"RESP_DOCTOR_NAME"},{"type":"int64","optional":false,"name":"io.debezium.time.Timestamp","version":1,"field":"CONSULT_DATE"},{"type":"string","optional":true,"field":"ZOE_SYS_CLIENT_CODE"},{"type":"int64","optional":true,"name":"io.debezium.time.Timestamp","version":1,"field":"ZOE_SYS_COLLECT_TIME"}],"optional":true,"name":"topic-cdc_test_20240524-142.cdc_test_20240524.dbo.consultinfo.Value","field":"after"},{"type":"struct","fields":[{"type":"string","optional":false,"field":"version"},{"type":"string","optional":false,"field":"connector"},{"type":"string","optional":false,"field":"name"},{"type":"int64","optional":false,"field":"ts_ms"},{"type":"string","optional":true,"name":"io.debezium.data.Enum","version":1,"parameters":{"allowed":"true,last,false,incremental"},"default":"false","field":"snapshot"},{"type":"string","optional":false,"field":"db"},{"type":"string","optional":true,"field":"sequence"},{"type":"string","optional":false,"field":"schema"},{"type":"string","optional":false,"field":"table"},{"type":"string","optional":true,"field":"change_lsn"},{"type":"string","optional":true,"field":"commit_lsn"},{"type":"int64","optional":true,"field":"event_serial_no"}],"optional":false,"name":"io.debezium.connector.sqlserver.Source","field":"source"},{"type":"string","optional":false,"field":"op"},{"type":"int64","optional":true,"field":"ts_ms"},{"type":"struct","fields":[{"type":"string","optional":false,"field":"id"},{"type":"int64","optional":false,"field":"total_order"},{"type":"int64","optional":false,"field":"data_collection_order"}],"optional":true,"name":"event.block","version":1,"field":"transaction"}],"optional":false,"name":"topic-cdc_test_20240524-142.cdc_test_20240524.dbo.consultinfo.Envelope","version":1},"payload":{"before":null,"after":{"ORG_CODE":"222","PATIENT_ID":"2","CONSULT_FORM_NO":"2","LAST_UPDATE_DTIME":1718122576000,"HEALTH_RECORD_NO":"15","NAME":null,"CONSULT_REASON":"娴嬭瘯","CONSULT_ADVICE":null,"CONSULT_ORG_CODE":null,"CONSULT_ORG_NAME":null,"CONSULT_DOCTOR_NAME":null,"RESP_DOCTOR_NAME":null,"CONSULT_DATE":1718122576000,"ZOE_SYS_CLIENT_CODE":null,"ZOE_SYS_COLLECT_TIME":null},"source":{"version":"2.5.0.Final","connector":"sqlserver","name":"topic-cdc_test_20240524-142","ts_ms":1718615856314,"snapshot":"last","db":"cdc_test_20240524","sequence":null,"schema":"dbo","table":"consultinfo","change_lsn":null,"commit_lsn":"00000304:00000104:0001","event_serial_no":null},"op":"r","ts_ms":1718615854366,"transaction":null}}

说明:

消费的数据,如果含有中文,中文会出现乱码,不用理会;(因为windows中文版,cmd的编码集默认为:GBK,而数据库一般采用的是UTF-8)。

消费的数据,如果含有数值类型,会被自动编码成base64格式,这个也不用管。(这是使用kafka-consume-consumer.bat造成的,它本身还是数值类型)

源库源表数据

5.同步数据

这样一来,我们就实现了数据同步的前半部分:抽取数据 push。 

示例:

至于将kafka中存储的数据更新到别的数据库(同步数据 pull),有两种实现方式:

第一种:使用插件io.debezium.connector.jdbc.JdbcSinkConnector(推荐使用)。

它可以和io.debezium.connector.mysql.SqlServersConnector插件无缝对接,不需要进行额外的数据转换操作。

说明:

当我们使用主键字段进行数据同步时(通常情况下),目标表字段的主键的名称需要与源表的主键名称完全一致(大小写也必须保持一致),否则,使用JdbcSinkConnector会报错:

主键字段不存在。

第二种:自定义开发消费组件。

当然,官方的jdbc插件也不是万能的,例如:

源表表名与目标表表名不一致;

目标表字段B需要对应源表表字段A,而不是A-->A;

在进行数据同步时,目标表需要增加时间戳字段或者实现假删除等等个性化需求,官方插件就不能用了。

我们只能自己开发kafka connect组件了。

posted @ 2024-06-17 16:52  Marydon  阅读(75)  评论(0编辑  收藏  举报