数据库数据变动实时监听

使用Debezium、Postgres和Kafka进行数据实时采集(CDC)

码匠君

9小时前

1. 背景

一直在完善自己的微服务架构,其中包含分布式工作流服务的建设,目前采用的是Camunda工作流引擎。使用Camunda工作流,就会涉及到工作流引擎的用户体系如何与现有用户体系集成的问题(Flowable、Activity也类似)。现有设计中,工作流定位偏重于企业内部流程的流转,因此系统中设计了单位、部门、人员以及人事归属与Camunda工作流用户体系对应。

功能设计完成,就面临另外一个问题,如何解决现有人事体系数据如何【`实时`】同步至Camunda工作流引擎中。如果现有体系数据与工作流数据在同一个库中,相对比较好解决。而微服务架构中,不同服务的数据通常存放在不同数据库中,那么就需要进行数据的同步。采用的方式不同,可以取得的效果也相同。

最初考虑如下两种方案,但是都略感不足:

ETL:使用ETL工具进行数据同步是典型的方式,可以选择工具也比较多。开源的ETL工具增量同步问题解决的并不理想,不使用增量同步数那么数据同步始终存在时间差;商业的ETL工具增量同步解决的比较好,但是庞大且昂贵。

消息队列:消息队列是多系统集成普遍采用的方式,可以很好地解决数据同步的实时问题。但是数据同步的两端都需要自己编写代码,一端写生产代码一端写消费代码,生产端代码还要捆绑现有体系数据所有操作,需要的编写量比较大。

查询对比的大量的资料,最终选择了Debezimu来解决以上问题以及未来更多数据同步的问题。

2. Debezium介绍

RedHat开源的Debezium是一个将多种数据源实时变更数据捕获,形成数据流输出的开源工具。

它是一种CDC(Change Data Capture)工具,工作原理类似大家所熟知的Canal, DataBus, Maxwell等,是通过抽取数据库日志来获取变更的。

官方介绍为:

Debezium is an open source distributed platform for change data capture. Start it up, point it at your databases, and your apps can start responding to all of the inserts, updates, and deletes that other apps commit to your databases. Debezium is durable and fast, so your apps can respond quickly and never miss an event, even when things go wrong

Debezium是一个分布式平台,它将您现有的数据库转换为事件流,因此应用程序可以看到数据库中的每一个行级更改并立即做出响应。Debezium构建在Apache KAFKA之上,并提供Kafka连接兼容的连接器来监视特定的数据库管理系统。

Debezium现在已支持以下数据库:

MySQL

MongoDB

PostgreSQL

Oracle

SQL Server

Db2

Cassandra

Vitess

与ETL不同,Debezimu只支持在生产端连接数据库,消费端不支持连接数据库,而是需要自己编写代码接收Kafka消息数据。分析下来这种方式更加灵活,还可以很好利用现有微服务架构中的Kafka。

3. 快速搭建Debezimu测试环境。

目前,Debezium最新的Stable版本是1.6。 Debezium已经把要用到的Component打包成了Docker的Image,因此,我们只需要安装并启动Docker后就可以按下面的步骤快速搭建测试环境了。

3.1 运行Zookeeper

docker run -it --name zookeeper -p 2181:2181 -p 2888:2888 -p 3888:3888 debezium/zookeeper:1.6

3.2 运行Kafka

docker run -it --name kafka -p 9092:9092 --link zookeeper:zookeeper debezium/kafka:1.6

3.3 运行PostgreSQL

docker run -it --rm --name postgres -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres debezium/example-postgres:1.6

上面代码中使用的是:

debezium/example-postgres:1.6,查看Debezimu官方文档以及其它示例都是这个。实际上Debezimu对postgresql 9~13都进行了Docker封装,可以根据自己的需要在Docker Hub中选择相应的PostgreSQL版本。

debezium/postgres很小,使用也比较方便,而且也进行了必要的设置,无须再进行额外的配置就可以直接使用。

3.4 运行Debezimu Connect

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.6

Debezium的container启动时需要传入如下环境变量:

GROUP_ID: 分组ID,若需要启动多个Debezium的实例组成集群,那么它们的GROUP_ID必须被设置为一样

CONFIG_STORAGE_TOPIC:下面需要调用Debezium提供的RestFUL API管理connector,connector的信息就是保存在CONFIG_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": "fulfillment-connector", 

  "config": {

    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",

    "database.hostname": "192.168.99.100",

    "database.port": "5432",

    "database.user": "postgres",

    "database.password": "postgres",

    "database.dbname" : "postgres",

    "database.server.name": "fulfillment",

    "table.include.list": "public.inventory"

  }

}

"name":注册到Kafka Connect服务的Connector名称

"connector.class":PostgreSQL connector class名称

"database.hostname":PostgreSQL 数据库地址

"database.port":PostgreSQL 数据库端口

"database.user":PostgreSQL 数据库用户名

"database.password":PostgreSQL数据密码

"database.dbname":连接的PostgreSQL数据库

"database.server.name":虚拟的数据库Server名称,可以根据实际需求定义,消费Kafka数据时要使用该值

"table.include.list":监听的数据表列表,以","分割。PostgreSQL要将表名写全,格式"<Schema-name>.<table-name>"。如果没有特定的Schema,那么就是默认的`public`

下面为完成的curl命令:

curl -i -X POST -H "Accept:application/json" -H "Content-Type:application/json" localhost:8083/connectors/ -d '{"name": "fulfillment-connector", "config": {"connector.class": "io.debezium.connector.postgresql.PostgresConnector", "database.hostname": "192.168.99.100", "database.port": "5432", "database.user": "postgres", "database.password": "postgres", "database.dbname" : "postgres",    "database.server.name": "fulfillment", "table.include.list": "public.inventory" }}'

上面是示例,因为使用的是Windows,个人觉得curl不方便,换用postman:

3.6 Docker Compose 配置

为了方便使用,将以上Docker命令整合为Docker Compose配置,具体如下:

version: "3"

services:

  postgres:

    image: debezium/postgres:13

    container_name: postgres

    hostname: postgres

    environment:

      POSTGRES_USER: herodotus

      POSTGRES_PASSWORD: herodotus

    ports:

      - 5432:5432

  zookeeper:

    image: debezium/zookeeper:1.6

    container_name: zookeeper

    restart: always

    ports:

      - 2181:2181

      - 2888:2888

      - 3888:3888

  kafka:

    image: debezium/kafka:1.6

    container_name: kafka

    restart: always

    ports:

      - 9092:9092

    environment:

      ZOOKEEPER_CONNECT: zookeeper:2181

      BOOTSTRAP_SERVERS: kafka:9092

    depends_on:

      - zookeeper

  connect:

    image: debezium/connect:1.6

    container_name: connect

    restart: always

    ports:

      - 8083:8083

    environment:

      GROUP_ID: 1

      CONFIG_STORAGE_TOPIC: herodotus_connect_configs

      OFFSET_STORAGE_TOPIC: herodotus_connect_offsets

      STATUS_STORAGE_TOPIC: herodotus_connect_statuses

      BOOTSTRAP_SERVERS: kafka:9092

    depends_on:

      - kafka

4. 外部数据库配置

上一章节,介绍了Debezimu测试环境的方式,其中使用的debezium/postgres是已经进行过配置的,所以使用起来比较方便。在实际使用过程中,很多时候是使用独立搭建PostgreSQL,那么就需要对PostgreSQL进行配置。

4.1 以Docker的方式运行基础组件

本章节主要介绍Debezimu与独立的PostgreSQL数据库连接,因此除了PostgreSQL以外,Zookeeper、Kafka、Debezimu Connect仍旧使用Docker方式部署。具体部署的Docker Compose配置如下:

version: "3"

services:

  zookeeper:

    image: debezium/zookeeper:1.6

    container_name: zookeeper

    hostname: zookeeper

    environment:

      ZOOKEEPER_SERVER_ID: 1

    ports:

      - 2181:2181

      - 2888:2888

      - 3888:3888

  kafka:

    image: debezium/kafka:1.6

    container_name: kafka

    hostname: kafka

    ports:

      - 9092:9092

    environment:

      BROKER_ID: 1

      ZOOKEEPER_CONNECT: zookeeper:2181

      KAFKA_LISTENERS: LISTENER_INNER://kafka:29092,LISTENER_OUTER://0.0.0.0:9092

      KAFKA_ADVERTISED_LISTENERS: LISTENER_INNER://kafka:29092,LISTENER_OUTER://192.168.101.10:9092

      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LISTENER_INNER:PLAINTEXT,LISTENER_OUTER:PLAINTEXT

      KAFKA_INTER_BROKER_LISTENER_NAME: LISTENER_INNER

      KAFKA_ALLOW_PLAINTEXT_LISTENER: 'yes'

      KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'true'

    depends_on:

      - zookeeper

  connect:

    image: debezium/connect:1.6

    container_name: connect

    hostname: connect

    ports:

      - 8083:8083

    environment:

      GROUP_ID: 1

      CONFIG_STORAGE_TOPIC: herodotus_connect_configs

      OFFSET_STORAGE_TOPIC: herodotus_connect_offsets

      STATUS_STORAGE_TOPIC: herodotus_connect_statuses

      BOOTSTRAP_SERVERS: kafka:9092

    depends_on:

      - kafka

其中Kafka Listener相关的配置,是为了解决Spring Kafka连接Kafka会出现:`Connection to node -1 could not be established. Broker may not be available.`问题。

4.2 修改PostgreSQL配置

Logical Decoding功能是PostgreSQL在9.4加入的,它是一种机制,允许提取提交到事务日志的更改,并在输出插件的帮助下以用户友好的方式处理这些更改。输出插件使客户机能够使用更改。

PostgreSQL connector 读取和处理数据库变化主要包含两个部分:

Logical Decoding 输出插件:根据选择可能需要安装输出插件。运行PostgreSQL服务之前,必须配置`replication slot `来启用你所选择的输出插件,有以下几个输出插件供选择:

decoderbufs: 是基于`Protobuf`的,目前由Debezimu社区维护

wal2json :是基于`JSON`的,目前由wal2json社区维护

pgoutput:在PostgreSQL 10及以上版本中是标准的Logical Decoding 输出插件。是由PostgreSQL社区维护,由PostgreSQL自己用于Logical Replication。这个插件是内置安装的,所以不需要额外安装。

Java代码(就是连接Kafka Connect的代码):负责读取由Logical Decoding 输出插件产生的数据。

Logical Decoding 输出插件不支持DDL变更,这意味着Connector不能把DDL变更事件发送给消费者

Logical Decoding Replicaiton Slots支持数据库的`primary`服务器。因此如果是PostgreSQL服务的集群,Connector只能在`primary`服务器激活。如果`primary`服务器出现问题,那么connector就会停掉。

4.2.1 修改PostgreSQL配置

在${PostgreSQL_HOME}/13/data目录下,找到postgresql.conf。

修改以下配置:

wal_level=logical

max_wal_senders=1

max_replication_slots=1

wal_level:通知数据库使用 logical decoding 读取预写日志

max_wal_senders: 通知数据库独立处理WAL变更的独立进程数量

max_replication_slots: 通知数据库处理WAL变更流所允许最大replication slots数目

配置完成后记得重启数据库

4.2.2 设置数据库权限

需要给PostgreSQL 用户分配replication权限。定义一个PostgreSQL role,至少分配REPLICATION和LOGION两项权限,示例代码如下:

CREATE ROLE <name> REPLICATION LOGIN;

具体操作可以参考以下脚本:

-- pg新建用户

CREATE USER user WITH PASSWORD 'pwd';

-- 给用户复制流权限

ALTER ROLE user replication;

-- 给用户登录数据库权限

grant CONNECT ON DATABASE test to user;

-- 把当前库public下所有表查询权限赋给用户

GRANT SELECT ON ALL TABLES IN SCHEMA public TO user;

4.3 创建Connector

把下面的payload POST到http://<ip addr of debezium>:8083/connectors/

{

    "name": "herodotus-connector",

    "config": {

        "connector.class": "io.debezium.connector.postgresql.PostgresConnector",

        "database.hostname": "192.168.101.10",

        "database.port": "15432",

        "database.user": "athena",

        "database.password": "athena",

        "database.dbname" : "athena",

        "database.server.name": "herodotus",

        "slot.name": "herodotus_slot",

        "table.include.list": "public.sys_organization",

        "publication.name": "herodotus_public_connector",

    "publication.autocreate.mode": "filtered",

    "plugin.name": "pgoutput"

    }

}

postman 界面操作如下图:

下面结合本例子中connector的配置信息对几个重点属性进行进一步说明:

Slot.name

按照上例Debezium会在PostgreSQL创建一个名为`herodotus_slot`的复制槽,本例中创建的connector需要通过该复制槽获取数据变更的信息。

可以通过以下sql查看复制槽的信息:

select * from pg_replication_slots;

上图中,active_pid为14200,即进程ID为14200的wal_sender进程已经在使用该复制槽与Debezium交互了

database.server.name和table.include.list

当connector获取到数据变更的信息后,会把该信息转化为统一的数据格式,并发布到Kafka的topic中。Debezium规定一个表对应一个topic,topic的名字的格式为 <database.server.name>.<schema name>.<table name>,本例中的表的数据变更消息将保存到Kafka的topic

herodotus.public.sys_organization中。

可以通过以下代码查看接收到的信息:

@KafkaListener(topics = {"herodotus.public.sys_organization"}, groupId = "herodotus.debezium")

public void received(String message) {

log.info("[Herodotus] |- Recived message from Debezium : [{}]", message);

}

5. 运行测试

现在,可以基于以上环境的配置,进行Debezium捕获数据效果的测试。可以进入到Kafka容器中,使用使用Kafka提供的kafka-console-consumer.sh查看Topic接收到的数据。具体命令如下:

bin/kafka-console-consumer.sh --bootstrap-server 192.168.101.10:9092 --topic herodotus.public.sys_organization

5.1 Insert 测试

在数据库sys_organization 表中插入一条数据

Kafka的消费者命令行工具收到了来自Debezium发布的数据变更消息:

格式化后的消息体如下,其中schema字段在此先忽略,重点放payload.before,payload.after及payload.op字段上:

{

"schema": {

...

},

"payload": {

"before": null,

"after": {

"organization_id": "4",

"create_time": null,

"ranking": null,

"update_time": null,

"description": null,

"is_reserved": null,

"reversion": null,

"status": 1,

"a4_biz_org_id": null,

"biz_org_code": null,

"biz_org_desc": null,

"biz_org_id": null,

"biz_org_name": null,

"biz_org_type": null,

"organization_name": "AAAAA",

"parent_id": null,

"partition_code": null,

"short_name": null

},

"source": {

"version": "1.6.0.Final",

"connector": "postgresql",

"name": "herodotus",

"ts_ms": 1626594964405,

"snapshot": "false",

"db": "athena",

"sequence": "[\"63461608\",\"63461608\"]",

"schema": "public",

"table": "sys_organization",

"txId": 2460,

"lsn": 63461896,

"xmin": null

},

"op": "c",

"ts_ms": 1626594964846,

"transaction": null

}

}

由于是insert操作,所以op为c (create),before为null,after为我们插入的数据。

5.2 Update 测试

在数据库sys_organization表中修改一条数据

Kafka的消费者命令行工具收到了来自Debezium发布的数据变更消息:

格式化后的消息体如下:

{

"schema": {

...

},

"payload": {

"before": null,

"after": {

"organization_id": "4",

"create_time": null,

"ranking": null,

"update_time": null,

"description": null,

"is_reserved": null,

"reversion": null,

"status": 1,

"a4_biz_org_id": null,

"biz_org_code": null,

"biz_org_desc": null,

"biz_org_id": null,

"biz_org_name": null,

"biz_org_type": null,

"organization_name": "BBBBB",

"parent_id": null,

"partition_code": null,

"short_name": null

},

"source": {

"version": "1.6.0.Final",

"connector": "postgresql",

"name": "herodotus",

"ts_ms": 1626595173601,

"snapshot": "false",

"db": "athena",

"sequence": "[\"63466888\",\"63466888\"]",

"schema": "public",

"table": "sys_organization",

"txId": 2461,

"lsn": 63467176,

"xmin": null

},

"op": "u",

"ts_ms": 1626595173825,

"transaction": null

}

}

进行更新产品信息的操作后,consumer将收到一条op为u (update)的信息,after为修改后的数据。

5.3 Delete测试

在数据库sys_organization表中删除一条数据

Kafka的消费者命令行工具收到了来自Debezium发布的数据变更消息:

格式化后的消息体如下:

{

"schema": {

...

},

"payload": {

"before": {

"organization_id": "3",

"create_time": null,

"ranking": null,

"update_time": null,

"description": null,

"is_reserved": null,

"reversion": null,

"status": null,

"a4_biz_org_id": null,

"biz_org_code": null,

"biz_org_desc": null,

"biz_org_id": null,

"biz_org_name": null,

"biz_org_type": null,

"organization_name": null,

"parent_id": null,

"partition_code": null,

"short_name": null

},

"after": null,

"source": {

"version": "1.6.0.Final",

"connector": "postgresql",

"name": "herodotus",

"ts_ms": 1626594566933,

"snapshot": "false",

"db": "athena",

"sequence": "[\"63461120\",\"63461120\"]",

"schema": "public",

"table": "sys_organization",

"txId": 2458,

"lsn": 63461176,

"xmin": null

},

"op": "d",

"ts_ms": 1626594567136,

"transaction": null

}

}

进行删除产品信息的操作后,consumer将收到一条op为d (delete)的信息,before为删除前的数据,after为null。

6.总结

通过Debezimu进行数据同步,不仅解决了传统ETL时效性不高的问题,还解决了基于消息队列需要两端编写代码的工程量,而且基于容器的方式更适合微服务架构的使用,使用Kafka进行消费端的整合,使得整合方式更加灵活便捷、终端类型更加丰富。

示例代码地址:

[Gitee](https://gitee.com/herodotus/eurynome-cloud)

[Github](https://github.com/herodotus-cloud/eurynome-cloud)

搜索

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容