现实生产中使用Canal+kafka做数据同步的一次记录

业务场景

需求:实时同步数据库(Mysql)数据到第三方公司、另一个数据库
在这里插入图片描述

方案

一、 数据同步操作嵌入业务代码块
在这里插入图片描述

优点:实现简单。
缺点:业务代码整体耦合性变高。如果同步到第三方公司的数据是有筛选的条件的,还会影响本身业务系统的性能。

二、 多搞一个数据库,读写分离,专门用做数据同步。
在这里插入图片描述
优点:较于方案一耦合性降低。不影响本身业务系统的正常运转
缺点:如果本身没有读写分离的需求,为了同步数据给第三方而增加一台机器。增加了成本且有点浪费资源。实时性得不到保证

三、 主从同步加消息队列实现同步
在这里插入图片描述
第三种方案是本文主要讨论的思路,主要技术栈是Canal+Kafka。这里面隐含了一个zookeeper,这里先不做展开研究。
优点:耦合低、实时性也很强。且数据量很大时,单位时间内的吞吐量也能得到保证。
缺点:技术栈要求高,业务量大时要搭建集群。


以下是围绕方案三,以问题的方式和现实生产中遇到的问题做记录和基础概念分享。

整体思路梳理

一次数据同步完整的流程中,首先是应用系统的写操作产生一条mysql的Binlog,Canal伪造成Slave(从库)拿到从Master(主库)产生的Binlog,Canal可以设置一定的过滤规则,只要某个特定的数据库或者表的Binlog。然后再把它丢到Kafka指定的Topic中。这时候,kafka产生一条消息,每条消息有自己的offset(类似id).然后,再开发一个消费者,通过指定group id(消费组)拿到消息消费。其实就是处理Binlog然后拼接参数,按第三方公司的接口给它传数据。

什么是Binglog?

一条Binlog具体长这样:

{"db": "test_kafka", "table": "user", "event_typ:": 1, "data": {"after": {"Id": "01","name":"测试用户"}}}

db:数据库名
table:数据表
event_type:事件,1-INSERT 2-UPDATE 3-DELETE
data:发生变化的字段
这里是插入事件,所以只有after。如果是修改事件或者删除事件、会有Before数据,可以做数据的前后比较。

什么是Canal?

canal [kə’næl],译意为水道/管道/沟渠,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费

图片来自官方
工作原理:
1、canal伪装成slave(从库),向master(主库)发送dump请求。
2、master发送binlgo到canal
3、canal拿到binlog后存到自己的relay log中,然后重放转变为自身的数据。
整个流程通俗的讲就是:电影院(canal)从电影出版方(mysql master)拿到片源(binlog),用的时候重放一遍。
Canal官方Github

Canal如何设置过滤规则?

可自行跳转另一篇docker搭建环境的介绍docker-compose 搭建Canal+Kafka+Kafka-eagle(监控)

Kafka是干嘛的?

kafak是一个分布式发布-订阅模式的消息服务工具

可以先简单理解为就是一个消息队列。这里可以从Kafka官方文档里截取的一段话中去理解

Producers are those client applications that publish (write) events to Kafka, and consumers are those that subscribe to (read and process) these events

消息发布——生成者
消息订阅——消费者

Kafka的Topic具体是什么?

Topic可以理解为对数据进行分类,或者说它就是 一个文件夹。生成者按Topic进行发布消息,消费者也是按Topic进行消费读取消息。
Topic会有多个分区,每个分区可以说就是一个消息队列
Topic分区图
每个分区上的消息都有独自的唯一标识Offset(偏移量),这里可以理解为mysql中的自增主键。消费者从0开始消费,消费消息后会根据消费组group id提交offset。保证同一个消费组下的消费者不会重复消费,消息被消费后不会马上被删除,为了保证队列的完整性而是被刷进磁盘。可以配置清除策略定时或者一定量的大小清除消息。
以上都是本次实践中部分基础概念的个人理解和记录。


现实生产环境中遇到的问题

一、 Canal获取不到Mysql Binlog

1、场景:新搭建的Canal环境
详细搭建过程可以跳转docker-compose 搭建Canal+Kafka+Kafka-eagle(监控)
a、账号未授予权限: REPLICATION SLAVE, REPLICATION
b、数据库配置未开启Binlog,或者Binlog非ROW

2、场景:本来Canal工作正常。修改配置,重启Canal

报错:canal Could not find first log file name in binary log index file

[destination = example , address = / , EventParser] ERROR com.alibaba.otter.canal.common.alarm.LogAlarmHandler - destination:example[java.io.IOException: Received error packet: errno = 1236, sqlstate = HY000 errmsg = Could not find first log file name in binary log index file
	at com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher.fetch(DirectLogFetcher.java:95)
	at com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection.dump(MysqlConnection.java:113)
	at com.alibaba.otter.canal.parse.inbound.AbstractEventParser$3.run(AbstractEventParser.java:209)
	at java.lang.Thread.run(Thread.java:745)

由于Mysql Binlog一直在产生,存储Binlog的文件名发生变化或者id一直在增长。不恰当的方式重启Canal。例如:docker搭建的情况下,修改配置文件后直接docker restart。这样重启相当于机器断电了一瞬间。但是当时Canal里面记录的Binlog文件位置没有跟Mysql同步。
解决方案:
进入路径:/admin/canal-server/conf/example/(初始的destination为example)
修改里面的meta.data。

{"clientDatas":[{"clientIdentity":{"clientId":1001,"destination":"example","filter":""},"cursor":{"identity":{"slaveId":-1,"sourceAddress":{"address":"127.0.0.1","port":3306}},"postion":{"included":false,"journalName":"mysql-bin.000033","position":5988,"timestamp":1429621093000}}}],"destination":"example"}

journalName跟现在Mysql Binlog文件名对应。然后再重启Canal,如果还是不行,这里需要关闭mysql的Binlog重新开一次。

二、 Kafka 消息明明已经被消费了,Lag却一直在增加

原因:未指定Group id,kafak默认的配置是自动提交偏移量。但是如果未指定消费者,这个配置就会失效。相当于每次的消费都没有提交offset。这里会有重复消费的可能
解决方案(这里以python的消费者为例)

from kafka import KafkaConsumer
import json
consumer = KafkaConsumer('example', bootstrap_servers=['127.0.0.1:9092'], auto_offset_reset='latest',value_deserializer=json.loads,enable_auto_commit=True,group_id='test-consumer-group')

指定一下group_id,设置自动提交偏移量enable_auto_commit=True

三、 消费者速度很慢,远跟不上生产者的速度

细节:上面说的topic的每一个分区就是一个消息队列。
所以多少个分区就对应多少个消费者。可以适当的增加分区,然后部署更多的消费者去消费。如果消费者的数量大于分区数,就会有部分消费者读取不到消息,无法消费。

四、 消费者(Django)运行一段时间后自动挂了“Mysql server has gone away”

在这里插入图片描述
场景:整合django的消费者白天业务期运行正常,但是半夜突然自动挂了。
原因排查:
1、数据库服务是否运行正常
2、 django能否正常连接Mysql

本次遇到的问题都不是上面列出来的,而是django与mysql连接的复用机制导致的。
在这里插入图片描述

django数据库连接中有个配置 CONN_MAX_AGE:连接最大存活时间

在每次创建完数据库连接之后,把连接放到一个Theard.local的实例中。在request请求开始结束的时候,打算关闭连接时会判断是否超过CONN_MAX_AGE设置这个有效期。这是关闭。每次进行数据库请求的时候其实只是判断local中有没有已存在的连接,有则复用。

查看mysql连接超时时间: show global variables like 'wait_timeout’
在这里插入图片描述
原因:django设置的连接存活时间 > mysql设置的连接超时时间。在django服务中,每次请求都会去检测连接有无失效,但是我们的消费者是通过自定义django Command去实现的。里面又涉及到Model的交互,当半夜业务数据停止产出了,就会长时间没有与mysql交互。
在mysql中连接已经超时了,但是django的连接池中还在复用它。所以会报连不上Mysql

解决方案:手动调用关闭连接的方法 close_old_connections()
在这里插入图片描述
该方法在 django.db.connections 下,用的时候直接复制出来就行

from django.db import connections
def close_old_connections(**kwargs):
    for conn in connections.all():
        conn.close_if_unusable_or_obsolete()

try:
	# your code
	pass
except:
	close_old_connections()

连接关闭之后,下次与mysql交互时会建立新的连接。


待续。。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值