1 背景
最近团队接到这样一个需求,其他团队开发的业务系统需要监控我们负责的业务系统的数据库变更情况,当表发生INSERT,UPDATE及DELETE操作时,相关的业务系统能实时获取数据的变化信息。
经过讨论,团队决定使用Debezium实现需求,于是便对Debezium进行了较深入的学习。下面给大家分享一下自己对Debezium认识与理解。
2 Debezium的介绍
Debezium针对多种数据库,分别实现了同步数据库的Connector,这些Connector以插件的形式运行在Kafka Connect集群上。
Debezium并不是一个平台,它是对Kafka Connect的Connector和Task的一种实现。Debezium现在已支持以下数据库:
- MongoDB
- MySQL
- PostgreSQL
- SQL Server
- Oracle (Incubating)
- Db2 (Incubating)
- Cassandra (Incubating)
其中,Oracle、Db2和Cassandra的Connector还在研发中。
Kafka Connect的介绍及Debezium的原理将在后面的章节说明,下面我们先尝试通过Debezium官方提供的Docker Image搭建测试环境,体现一下Debezium捕获PostgreSQL数据库变化情况的效果。
3 快速搭建Debezium测试环境
目前,Debezium最新的Stable版本是1.3。 Debezium已经把要用到的Component打包成了Docker的Image,因此,我们只需要安装并启动Docker后就可以按下面的步骤快速搭建测试环境了。
3.1 运行Zookeeper
docker run -it --rm --name zookeeper -p 2181:2181 -p 2888:2888 -p 3888:3888 debezium/zookeeper:1.3
3.2 运行Kafka
docker run -it --rm --name kafka -p 9092:9092 --link zookeeper:zookeeper debezium/kafka:1.3
其中,--link
用来链接2个容器,使得源容器(Zookeeper container)和接收容器(Kafka container)之间可以互相通信,并且接收容器可以获取源容器的一些数据,如源容器的环境变量。
例如,在启动Zookeeper的container时设置了--name zookeeper
,即alias为zookeeper;在启动Kafka的container时候,设置了--link zookeeper:zookeeper
使其链接到Zookeeper的container,那么Docker将根据Zookeeper container的IP和正在监听的端口为Kafka的container自动生成一些环境变量。启动的时候,启动脚本将使用这些环境变量启动Kafka。这些环境变量名的格式为 <alias of origin container>_PORT_<listening port>_TCP
。使用以下命令进入Kafka的container:
docker exec -it <container id of Kafka> /bin/bash
进入容器后查看环境变量:
env | grep -E "ZOOKEEPER_PORT_[0-9]+_TCP="
Docker已根据Zookeeper container的IP和正在监听的port为Kafka container生成了与相关的环境变量:
Kafka的启动脚本就是使用这些环境变量启动Kafka的。
3.3 启动PostgreSQL
docker run -it --rm --name postgres -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres debezium/example-postgres
Debezium提供的PostgreSQL镜像在启动时会创建inventory schema及进销存管理系统需要的表,我们使用pgAdmin可以看到相关的表:
3.4 运行Debezium
docker run -it --rm --name connect -p 8083:8083 -e GROUP_ID=1 -e CONFIG_STORAGE_TOPIC=my_connect_configs -e OFFSET_STORAGE_TOPIC=my_connect_offsets -e STATUS_STORAGE_TOPIC=my_connect_statuses --link zookeeper:zookeeper --link kafka:kafka --link postgres:postgres debezium/connect:1.3
Debezium的container启动时需要传入如下环境变量:
- GROUP_ID: 分组ID,若需要启动多个Debezium的实例组成集群,那么它们的GROUP_ID必须被设置为一样(以后的文章会介绍原因)。
- CONFIG_STORAGE_TOPIC:下面需要调用Debezium提供的RestFUL API管理connector,connector的信息就是保存在CONFIG_STORAGE_TOPIC指定的kafka topic下。
- STATUS_STORAGE_TOPIC: connector的状态信息被保存在STATUS_STORAGE_TOPIC指定的kafka topic下。
- OFFSET_STORAGE_TOPIC: connector监控数据流的offset,若我们使用的是PostgreSQL Connector,那么OFFSET_STORAGE_TOPIC指定的topic中存的就是PostgreSQL的lsn。
3.5 创建Connector
经过上面4个步骤后,Debezium的测试环境就搭建好了,现在需要调用Debezium提供的API创建connector,使Debezium与数据库之间建立关系。我们把下面的payload POST到http://<ip addr of debezium>:8083/connectors/
。
{
"name": "inventory-connector",
"config": {
"connector.class": "io.debezium.connector.postgresql.PostgresConnector",
"database.hostname": "postgres",
"database.port": "5432",
"database.user": "postgres",
"database.password": "postgres",
"database.dbname": "postgres",
"database.server.name": "debezium",
"slot.name": "inventory_slot",
"table.include.list": "inventory.orders,inventory.products",
"publication.name": "dbz_inventory_connector",
"publication.autocreate.mode": "filtered",
"plugin.name": "pgoutput"
}
}
下面为完成的curl命令:
curl -i -X POST -H "Content-Type:application/json" -H "Accept:application/json" <ip addr of debezium>:8083/connectors/ -d "{ \"name\": \"inventory-connector\", \"config\": { \"connector.class\": \"io.debezium.connector.postgresql.PostgresConnector\", \"database.hostname\": \"postgres\", \"database.port\": \"5432\", \"database.user\": \"postgres\", \"database.password\": \"postgres\", \"database.dbname\" : \"postgres\", \"database.server.name\": \"debezium\", \"slot.name\": \"inventory_slot\", \"table.include.list\": \"inventory.orders,inventory.products\", \"publication.name\": \"dbz_inventory_connector\",\"publication.autocreate.mode\": \"filtered\", \"plugin.name\": \"pgoutput\" } }"
payload有两个字段,name是connector的名字,config是connector的配置信息,下表为config中的字段的解释:
字段名称 | 描述 |
---|---|
connector.class | connector的实现类,本文使用的是io.debezium.connector.postgresql.PostgresConnector,因为我们的数据库是PostgreSQL |
database.hostname | 数据库服务的IP或域名 |
database.port | 数据库服务的端口 |
database.user | 连接数据库的用户 |
database.password | 连接数据库的密码 |
database.dbname | 数据库名 |
database.server.name | 每个被监控的表在Kafka都会对应一个topic,topic的命名规范是<database.server.name>.<schema>.<table> |
slot.name | PostgreSQL的复制槽(Replication Slot)名称 |
table.include.list | 如果设置了table.include.list,即在该list中的表才会被Debezium监控 |
plugin.name | PostgreSQL服务端安装的解码插件名称,可以是decoderbufs, wal2json, wal2json_rds, wal2json_streaming, wal2json_rds_streaming 和 pgoutput。如果不指定该值,则默认使用decoderbufs。 本例子中使用了pgoutput,因为它是PostgreSQL 10+自带的解码器,而其他解码器都必须在PostgreSQL服务器安装插件。 |
publication.name | PostgreSQL端的WAL发布(publication)名称,每个Connector都应该在PostgreSQL有自己对应的publication,如果不指定该参数,那么publication的名称为dbz_publication |
publication.autocreate.mode | 该值在plugin.name设置为pgoutput才会有效。有以下三个值: all_tables - debezium会检查publication是否存在,如果publication不存在,connector则使用脚本CREATE PUBLICATION <publication_name> FOR ALL TABLES创建publication,即该发布者会监控所有表的变更情况。 disabled - connector不会检查有无publication存在,如果publication不存在,则在创建connector会报错. filtered - 与all_tables不同的是,debezium会根据connector的配置中的table.include.list生成生成创建publication的脚本: CREATE PUBLICATION <publication_name> FOR TABLE <tbl1, tbl2, tbl3> 。例如,本例子中,“table.include.list"值为"inventory.orders,inventory.products”,则publication只会监控这两个表的变更情况。 |
下面结合本例子中connector的配置信息对几个重点属性进行进一步说明:
publication.name
、publication.autocreate.mode
和table.include.list
Debezium根据这三个参数在PostgreSQL中创建一个名为dbz_inventory_connector
的publication
,它会监控inventory.orders
和inventory.products
两个表的数据变更。我们可以在pgAdmin中执行下面的sql查询数据库中的publication
:
SELECT A.*, B.schemaname, B.tablename
FROM pg_publication A
INNER JOIN pg_publication_tables B
ON A.pubname = B.pubname;
slot.name
Debeziumhui会在PostgreSQL创建一个名为inventory_slot
的复制槽,本例中创建的connector需要通过该复制槽获取数据变更的信息。
可以通过以下sql查看复制槽的信息:
select * from pg_replication_slots;
上图中,active_pid为78,即进程ID为78的wal_sender进程已经在使用该复制槽与Debezium交互了。
database.server.name
和table.include.list
当connector获取到数据变更的信息后,会把该信息转化为统一的数据格式,并发布到Kafka的topic中。Debezium规定一个表对应一个topic,topic的名字的格式为 <database.server.name>.<schema name>.<table name>
,本例中的两个表的数据变更消息将保存到Kafka的topic debezium.inventory.orders
和debezium.inventory.products
中。
3.6 测试
现在,可以开始测试Debezium的功能了,下面以products表为例演示Debezium捕获数据的效果:
订阅products表的topic
我们可以使用Kafka提供的kafka-console-consumer.sh
订阅products表的topic debezium.inventory.products
:
/kafka/bin/kafka-console-consumer.sh --bootstrap-server 172.17.0.3:9092 --topic debezium.inventory.products
插入新的产品信息
INSERT INTO inventory.products VALUES(110, 'football', 'Premier League', 1);
Kafka的消费者命令行工具收到了来自Debezium发布的数据变更消息:
格式化后的消息体如下,其中schema字段在此先忽略,重点放payload.before
,payload.after
及payload.op
字段上:
{
"schema": {
...
},
"payload": {
"before": null,
"after": {
"id": 110,
"name": "football",
"description": "Premier League",
"weight": 1.0
},
"source": {
"version": "1.3.0.Final",
"connector": "postgresql",
"name": "debezium",
"ts_ms": 1606026352617,
"snapshot": "false",
"db": "postgres",
"schema": "inventory",
"table": "products",
"txId": 603,
"lsn": 34245336,
"xmin": null
},
"op": "c",
"ts_ms": 1606026353058,
"transaction": null
}
}
由于是insert操作,所以op为c (create),before为null,after为我们插入的数据。
更新产品信息
UPDATE inventory.products SET name = 'soccer' WHERE id = 110;
格式化后的消息体如下:
{
"schema": {
...
},
"payload": {
"before": {
"id": 110,
"name": "football",
"description": "Premier League",
"weight": 1.0
},
"after": {
"id": 110,
"name": "soccer",
"description": "Premier League",
"weight": 1.0
},
"source": {
"version": "1.3.0.Final",
"connector": "postgresql",
"name": "debezium",
"ts_ms": 1606026918325,
"snapshot": "false",
"db": "postgres",
"schema": "inventory",
"table": "products",
"txId": 604,
"lsn": 34246872,
"xmin": null
},
"op": "u",
"ts_ms": 1606026918592,
"transaction": null
}
}
进行更新产品信息的操作后,consumer将收到一条op为u (update)的信息,before为修改前的数据,after为修改后的数据。从上面的消息,我们对比before和after,就可以发现,id为110的产品的名字从football更新为soccer。
删除产品信息
DELETE FROM inventory.products WHERE id = 110;
格式化后的消息体如下:
{
"schema": {
...
},
"payload": {
"before": {
"id": 110,
"name": "soccer",
"description": "Premier League",
"weight": 1.0
},
"after": null,
"source": {
"version": "1.3.0.Final",
"connector": "postgresql",
"name": "debezium",
"ts_ms": 1606027203225,
"snapshot": "false",
"db": "postgres",
"schema": "inventory",
"table": "products",
"txId": 605,
"lsn": 34248240,
"xmin": null
},
"op": "d",
"ts_ms": 1606027203663,
"transaction": null
}
}
进行删除产品信息的操作后,consumer将收到一条op为d (delete)的信息,before为刪除前的数据,after为null。
至此,已经通过Debezium官方提供的Docker Image快速搭建了测试环境,并通过对inventory.products进行了insert, update和delete操作测试Debezium的运行效果了。但这只属走眼观花,要深度掌握Debezium仍需研究两个课题,PostgreSQL的流复制(Streaming Replication) 及Kafka Connect的运行原理。我会在之后再写文章说明相关概念,感谢大家。