需求:
Kafka实时采集PostgreSQL数据库有关增删改事务(DML)的WAL信息。
使用组件:
debezium-connector-postgres(1.0.3版本以上)
postgresql数据库(10版本以上)
kafka可用集群(2.11-1.1.0版本,3台)
pgoutput(逻辑解码插件,版本数据库自带)
修改postgresql.conf配置文件:
vim $PGDATA/postgresql.conf
wal_level=logical
checkpoint_timeout = 15min[自定义,其它wal相关的,默认配置就好]
重启数据库服务。
安装并配置Debezium:
- 下载:wget https://repo1.maven.org/maven2/io/debezium/debezium-connector-postgres/1.0.3.Final/debezium-connector-postgres-1.0.3.Final-plugin.tar.gz
- 解压:tar -zxvf ???/debezium-connector-postgres-0.8.3.Final-plugin.tar.gz
- 重命名:mv debezium-connector-postgres debezium
- 复制debezium的jar包至kafka的libs目录下(略)
建立Kafka连接器:
请选择任一台kafka集群服务器。
-
修改kafka的配置文件
vim $KAFKA_HOME/config/connect-standalone.properties
bootstrap.servers=[ip]:9092
-
编写连接器信息
vim $KAFKA_HOME/config/postgres.properties
name=[自定义]
slot.name=[自定义]
connector.class=io.debezium.connector.postgresql.PostgresConnector
database.hostname=[数据库ip]
database.port=[数据库端口]
database.user=[用户:需有replication权限]
database.password=[用户密码]
database.dbname=[监视数据库]
database.history.kafka.bootstrap.servers=[kafka服务器ip:9092]
database.server.name=[自定义]
table.whitelist=[监视表:如public.demo]
publication.name=[自定义]
decimal.handling.mode=string
plugin.name=pgoutput
#snapshot.mode=never
#heartbeat.interval.ms=10000
#slot.drop.on.stop=true
#publication.autocreate.mode=filtered
#time.precision.mode=adaptive_time_microseconds
#schema.refresh.mode=columns_diff_exclude_unchanged_toast
具体连接器配置可参阅官方文档
https://debezium.io/documentation/reference/1.0/connectors/postgresql.html
启动kafka连接器:
nohup $KAFKA_HOME/bin/connect-standalone.sh $KAFKA_HOME/config/connect-standalone.properties $KAFKA_HOME/config/postgres.properties >>$KAFKA_HOME/logs-debezium/connect_kafka_postgresql.log 2>&1 &
注意:
刚建立新的kafka连接器,服务器需要导入数据库快照,恢复数据(这里是更新lsn,在kafka服务器生成相应log文件);
快照模式(snapshot.mode)可根据官方文档修改;
若数据库的数据量很大,请确保kafka集群服务器有足够大的磁盘空间;
故需要一段较长的时间,复制槽的active才会更新为true,连接器才可正式使用。
修改Postgresql的发布表:
待数据恢复完成后,即复制槽的active更新为true,可修改发布表。
debezium的发布状态默认为alltables(发布所有表),生产环境尽量只发布相应表。
update pg_publication set puballtables=false where pubname is not null;
alter publication debezium_kafka_postgresql add table [发布表];
select * from pg_publication_tables;
加入心跳机制(可忽略):
当数据库执行大事务操作时(如reindexdb,vacuumdb等),复制槽中的订阅端无法消费这些数据,若发布端数据库在该时段内没有变更发布数据表操作,很大概率就会造成pg_replication_slots的<restart_lsn>一直停滞不前,
导致wal空间不断增大且无法自动清理,影响数据库性能,故可加入心跳机制。
(本人认为)心跳机制可分为两种。
-
Debezium的心跳机制
连接器信息加入:
heartbeat.interval.ms=1000
具体连接器配置可参阅官方文档
https://debezium.io/documentation/reference/1.0/connectors/postgresql.html
我一阵捣鼓后发现效果不佳,故放弃了。如果有人应用过该配置,可分享下使用经验。
-
Postgresql模拟心跳机制
官方文档有这么一条说明:
WAL磁盘空间消耗
在某些情况下,WAL文件占用的PostgreSQL磁盘空间可能会出现峰值或超出正常比例。有三种可能的原因可以解释这种情况:
-
Debezium定期向数据库确认已处理事件的LSN。这
confirmed_flush_lsn
在pg_replication_slots
插槽表中可见。数据库负责回收磁盘空间,并且可以从restart_lsn
同一表中计算WAL大小。因此,如果confirmed_flush_lsn
定期增加并且restart_lsn
滞后,则数据库确实需要回收空间。磁盘空间通常以批处理块的形式回收,因此这是预期的行为,无需在用户方面采取任何措施。 -
受监视的数据库中有许多更新,但是只有很少量与受监视的表和/或架构有关。通过使用
heartbeat.interval.ms
配置选项启用定期心跳事件,可以轻松解决这种情况。 -
PostgreSQL实例包含多个数据库,其中一个是高流量数据库。与另一个数据库相比,Debezium监视另一个低流量的数据库。然后Debezium无法确认LSN,因为复制插槽针对每个数据库工作,并且不调用Debezium。由于WAL被所有数据库共享,因此它趋于增长,直到Debezium监视的数据库发出事件为止。
为了克服第三个原因,有必要
-
使用
heartbeat.interval.ms
配置选项启用定期心跳记录生成 -
定期从Debezium跟踪的数据库中发出变更事件
-
对于
wal2json
解码器插件,生成空事件就足够了。例如,这可以通过截断一个空的临时表来实现。 -
对于其他解码器插件,建议创建一个不受Debezium监视的补充表。
-
然后,一个单独的过程将定期更新表(插入新事件或全部更新同一行)。然后,PostgreSQL将调用Debezium,后者将确认最新的LSN,并允许数据库回收WAL空间。
①创建debezium_heartbeat表
CREATE TABLE public.debezium_heartbeat
(
id INT8 NOT NULL,
now_heartbeat_ts TIMESTAMPTZ DEFAULT NOW(),
last_heartbeat_ts TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT debezium_heartbeat_pkey PRIMARY KEY (id)
);
INSERT INTO public.debezium_heartbeat (id,now_heartbeat_ts,last_heartbeat_ts) VALUES (202104,NOW(),NOW());
注意:
创建完后,务必将debezium_heart表添加至kafka连接器信息的table.writelist,并且加入postgresql的发布表pg_publication。
②数据库服务器上编写脚本
vim ???/shells/auto_update_debezium_heartbeat.sh
#!bin/bash
#数据库bin命令存放目录
pg_path="???"
#执行的SQL存放目录
sql_path="???"
db_host='ip'
db_port='端口'
db_username='用户'
db_name='数据库'
#密码可保存至~/.pgpass
#格式:ip:端口:数据库:用户:密码
#chmod 700 ~/.papass
step=10 #间隔的秒数,不能大于60
for (( i = 0; i < 60; i=(i+step) )); do
echo '['$(date "+%Y-%m-%d %H:%M:%S")'] 每10s更新<debezium_heartbeat>>表'
echo '['$(date "+%Y-%m-%d %H:%M:%S")'] 开始更新'
$pg_path/psql -h $db_host -p $db_port -U $db_username --dbname $db_name -e -f $sql_path'update_debezium_heartbeat.sql'
echo '['$(date "+%Y-%m-%d %H:%M:%S")'] 完成更新'
sleep $step
done
exit 0
chmod 755 auto_update_debezium_heartbeat.sh
vim ???/shells/update_debezium_heartbeat.sql
update public.debezium_heartbeat set now_heartbeat_ts=NOW(),last_heartbeat_ts=now_heartbeat_ts where id=202104;
③加入定时任务
vim /etc/crontab
#更新debezium_heartbeat
* 15 * * * root ???/shells/auto_update_debezium_heartbeat.sh >> ???/shells/auto_update_debezium_heartbeat.log 2>&1
测试:
数据库服务执行vacuumdb full(该指令订阅端无法消费):
pg_wal目录最大可增长至10~12G(wal_sender进程跟不上checkpoint进程或受网络波动),
所以data目录得预留20G左右磁盘空间(根据自身数据量)。
期间pg_wal目录根据checkpoint点记录的lsn位置(且是订阅端返回的lsn位置)自动清理wal文件,
最后保留max_wal_size配置(默认1G)定义的wal文件大小。
自动清理成功!
vacuumdb full期间也执行一些DDL操作,
订阅端也能正常接收(可通过$KAFKA_HOME/logs查看或更下游消费端测试);
结论:
加入心跳机制,实质上是推动复制槽的<restart_lsn>。
请根据实际情况选择是否加入心跳机制!
后续:
使用PostgreSQL的Debezium连接器,并相应解决了一些可能会出现的问题,
Kafka成功实时采集到Postgresql数据库相应的DML(增删改)操作。
如何消费Kafka订阅信息并作如何处理,目前我还没有一个完整完善的流程操作。
计划流程:postgresql -> debezium -> kafka -> spark streaming(spark sql) -> mysql(postgresql/hive) ->superset
待整个(伪)实时流程建立好后,再抽空编写后面操作的博文。
如有任何不足、错误或可补充的地方,欢迎各位一起讨论,一起学习!
附1:停止连接器并删除复制槽
kill -9 [ConnectStandalone进程pid]
select * from pg_replication_slots;
select pg_drop_replication_slot('debezium_kafka_postgresql');
delete from pg_publication where pubname='debezium_kafka_postgresql';
附2:相关Kafka指令
#查看topic
kafka-topics.sh --list --zookeeper ???:2181,???:2181,???:2181
#删除topic
kafka-topics.sh --zookeeper ???:2181,???:2181,???:2181 --delete --topic [topic]
附3:手动清除wal文件
#!/bin/bash
##pg_wal_clean.sh
CUR_DATE=`date +"%Y%m%d"`
CUR_TIME=`date +"%Y-%m-%d %H:%M:%S"`
pg_bin_path="???/bin"
pg_data_path="???/data"
pg_wal_path="???/data/pg_wal"
#pg_archive_path="???/archive/archdir"
sql_path="???/shells"
db_host='???'
db_port='???'
db_username='postgres'
db_name='???'
#密码可保存至~/.pgpass
#格式:ip:端口:数据库:用户:密码
#chmod 700 ~/.papass
echo '['$CUR_TIME'] 开始:针对性清除wal文件'
lastwal=`$pg_bin_path/pg_controldata -D $pg_data_path |grep "Latest checkpoint's REDO WAL file" |awk '{print $6}'`
echo '['$CUR_TIME'] [1]Latest checkpoint's REDO WAL file:$lastwal
echo '['$CUR_TIME'] 执行checkpoint'
$pg_bin_path/psql -h $db_host -p $db_port -U $db_username --dbname $db_name -e -f $sql_path'/pg_checkpoint.sql'
lastwal=`$pg_bin_path/pg_controldata -D $pg_data_path |grep "Latest checkpoint's REDO WAL file" |awk '{print $6}'`
echo '['$CUR_TIME'] [2]Latest checkpoint's REDO WAL file:$lastwal
#echo '['$CUR_TIME'] 清除归档目录的wal文件'
#$pg_bin_path/pg_archivecleanup -d $pg_archive_path/$CUR_DATE/ $lastwal
echo '['$CUR_TIME'] 清除pg_wal目录的wal文件'
$pg_bin_path/pg_archivecleanup -d $pg_wal_path/ $lastwal
echo '['$CUR_TIME'] 完成:针对性清除wal文件'
##pg_checkpoint.sql
checkpoint;