01-利用Debezium捕获PostgreSQL的数据变化

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.classconnector的实现类,本文使用的是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.namePostgreSQL的复制槽(Replication Slot)名称
table.include.list如果设置了table.include.list,即在该list中的表才会被Debezium监控
plugin.namePostgreSQL服务端安装的解码插件名称,可以是decoderbufs, wal2json, wal2json_rds, wal2json_streaming, wal2json_rds_streaming 和 pgoutput。如果不指定该值,则默认使用decoderbufs。

本例子中使用了pgoutput,因为它是PostgreSQL 10+自带的解码器,而其他解码器都必须在PostgreSQL服务器安装插件。
publication.namePostgreSQL端的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.namepublication.autocreate.modetable.include.list

Debezium根据这三个参数在PostgreSQL中创建一个名为dbz_inventory_connectorpublication,它会监控inventory.ordersinventory.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.nametable.include.list

当connector获取到数据变更的信息后,会把该信息转化为统一的数据格式,并发布到Kafka的topic中。Debezium规定一个表对应一个topic,topic的名字的格式为 <database.server.name>.<schema name>.<table name>,本例中的两个表的数据变更消息将保存到Kafka的topic debezium.inventory.ordersdebezium.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.beforepayload.afterpayload.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的运行原理。我会在之后再写文章说明相关概念,感谢大家。

  • 16
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
要在Spring Boot应用程序中使用Debezium和OpenGauss来捕获变更数据,您需要执行以下步骤: 1. 在Spring Boot项目中添加Debezium和OpenGauss的依赖项。 2. 配置Debezium连接到OpenGauss数据库并启动Debezium引擎。 3. 创建一个Kafka生产者并将Debezium捕获的变更数据发送到Kafka主题。 4. 从Kafka主题中消费变更数据。 具体实现步骤如下: 1. 添加Debezium和OpenGauss的依赖项 在pom.xml中添加以下依赖项: ``` <dependency> <groupId>io.debezium</groupId> <artifactId>debezium-core</artifactId> <version>1.3.0.Final</version> </dependency> <dependency> <groupId>io.debezium.connector</groupId> <artifactId>debezium-connector-opengauss</artifactId> <version>1.3.0.Final</version> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.2.12</version> </dependency> <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-clients</artifactId> <version>2.5.1</version> </dependency> ``` 2. 配置Debezium连接到OpenGauss数据库并启动Debezium引擎 在application.properties中添加以下配置: ``` # Debezium configuration debezium.connector.name=opengauss debezium.connector.class=io.debezium.connector.opengauss.OpenGaussConnector debezium.offset.storage=kafka debezium.offset.storage.topic=dbhistory.opengauss debezium.offset.storage.partitions=1 debezium.offset.storage.replication.factor=1 debezium.snapshot.mode=when_needed debezium.poll.interval.ms=5000 # OpenGauss configuration database.hostname=localhost database.port=5432 database.user=postgres database.password=password database.dbname=mydb database.server.name=myserver ``` 然后创建一个Debezium引擎实例,如下所示: ``` @Configuration public class DebeziumEngineConfiguration { @Value("${debezium.connector.name}") private String connectorName; @Value("${debezium.connector.class}") private String connectorClass; @Value("${debezium.offset.storage}") private String offsetStorage; @Value("${debezium.offset.storage.topic}") private String offsetStorageTopic; @Value("${debezium.offset.storage.partitions}") private int offsetStoragePartitions; @Value("${debezium.offset.storage.replication.factor}") private short offsetStorageReplicationFactor; @Value("${debezium.snapshot.mode}") private String snapshotMode; @Value("${debezium.poll.interval.ms}") private long pollIntervalMs; @Value("${database.hostname}") private String hostname; @Value("${database.port}") private int port; @Value("${database.user}") private String user; @Value("${database.password}") private String password; @Value("${database.dbname}") private String dbname; @Value("${database.server.name}") private String serverName; @Bean public Configuration debeziumConfiguration() { Configuration config = Configuration.create() .with("connector.class", connectorClass) .with("offset.storage", offsetStorage) .with("offset.storage.topic", offsetStorageTopic) .with("offset.storage.partitions", offsetStoragePartitions) .with("offset.storage.replication.factor", offsetStorageReplicationFactor) .with("name", connectorName) .with("database.hostname", hostname) .with("database.port", port) .with("database.user", user) .with("database.password", password) .with("database.dbname", dbname) .with("database.server.name", serverName) .with("snapshot.mode", snapshotMode) .with("poll.interval.ms", pollIntervalMs); return config; } @Bean public DebeziumEngine<ChangeEvent<String, String>> debeziumEngine() { DebeziumEngine<ChangeEvent<String, String>> engine = DebeziumEngine.create(Json.class) .using(debeziumConfiguration()) .notifying(this::handleEvent) .build(); return engine; } private void handleEvent(ChangeEvent<String, String> event) { // Handle event } } ``` 3. 创建一个Kafka生产者并将Debezium捕获的变更数据发送到Kafka主题 首先创建一个Kafka生产者实例,然后在Debezium引擎中添加以下配置: ``` .with("database.history.kafka.bootstrap.servers", "${spring.kafka.bootstrap-servers}") .with("database.history.kafka.topic", "dbhistory.opengauss") ``` 然后在handleEvent()方法中将Debezium捕获的变更数据发送到Kafka主题,如下所示: ``` @Autowired private KafkaTemplate<String, String> kafkaTemplate; private void handleEvent(ChangeEvent<String, String> event) { String key = event.key(); String value = event.value(); kafkaTemplate.send("mytopic", key, value); } ``` 4. 从Kafka主题中消费变更数据 创建一个Kafka消费者实例,然后从Kafka主题中消费变更数据,如下所示: ``` @KafkaListener(topics = "mytopic") public void processMessage(ConsumerRecord<String, String> record) { String key = record.key(); String value = record.value(); // Process message } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值