{"id":19993184,"url":"https://github.com/liukelin/canal_mysql_nosql_sync","last_synced_at":"2025-05-04T12:31:00.321Z","repository":{"id":50439815,"uuid":"48999610","full_name":"liukelin/canal_mysql_nosql_sync","owner":"liukelin","description":"基于canal 的 mysql 与 redis/memcached/mongodb 的 nosql 数据实时同步方案 案例 demo canal client","archived":false,"fork":false,"pushed_at":"2019-07-10T09:22:46.000Z","size":27705,"stargazers_count":1429,"open_issues_count":18,"forks_count":470,"subscribers_count":101,"default_branch":"master","last_synced_at":"2024-11-13T04:56:04.416Z","etag":null,"topics":["binlog","canal","canal-mysql","mysql","sync"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/liukelin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-01-04T13:47:17.000Z","updated_at":"2024-10-10T03:46:50.000Z","dependencies_parsed_at":"2022-08-12T21:21:00.754Z","dependency_job_id":null,"html_url":"https://github.com/liukelin/canal_mysql_nosql_sync","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liukelin%2Fcanal_mysql_nosql_sync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liukelin%2Fcanal_mysql_nosql_sync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liukelin%2Fcanal_mysql_nosql_sync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liukelin%2Fcanal_mysql_nosql_sync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/liukelin","download_url":"https://codeload.github.com/liukelin/canal_mysql_nosql_sync/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252334218,"owners_count":21731362,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["binlog","canal","canal-mysql","mysql","sync"],"created_at":"2024-11-13T04:52:30.338Z","updated_at":"2025-05-04T12:30:55.287Z","avatar_url":"https://github.com/liukelin.png","language":"Java","funding_links":[],"categories":["Java","数据库中间件"],"sub_categories":[],"readme":"\n\n\n下图是最基本的web服务器的结构图。\n![image](https://github.com/liukelin/canal_mysql_nosql_sync/raw/master/img/system-image.png)\n\n基于 Canal 的 MySql RabbitMQ Redis/memcached/mongodb 的nosql同步 （多读、nosql延时不严格 需求）\n\n\t1.mysql主从配置\n\n\t2.对mysql binlog(row) parser 这一步交给canal\n\n\t3.MQ对解析后binlog增量数据的推送\n\n\t4.对MQ数据的消费（接收+数据解析，考虑消费速度，MQ队列的阻塞）\n\n\t5.数据写入/修改到nosql （redis的主从/hash分片）\n\n\t6.保证对应关系的简单性：一个mysql表对应一个 redis实例（redis单线程，多实例保证分流不阻塞），关联关系数据交给接口业务\n\n\t数据：mysql-\u003ebinlog-\u003eMQ-\u003eredis(不过期、关闭RDB、AOF保证读写性能) （nosql数据仅用crontab脚本维护）\n\n\t请求：http-\u003ewebserver-\u003eredis(有数据)-\u003e返回数据 （完全避免用户直接读取mysql）\n\n\t                    -\u003eredis(无数据)-\u003e返回空\n\t\n\t7.可将它视为一个触发器，binlog为记录触发事件，canal的作用是将事件实时通知出来，并将binlog解析成了所有语言可读的工具。\n\t在事件传输的各个环节 提高 可用性 和 扩展性 （加入MQ等方法）最终提高系统的稳定。\n\n\n\n传统 Mysql Redis/memcached nosql的缓存 （业务同步）\n        从cache读取数据-\u003e\n\n\t1.对数据在mysql的hash算法分布(db/table/分区)，每个hash为节点（nosql数据全部失效时候，可保证mysql各节点可支持直接读取的性能）\n\n\t2.mysql主从\n\n\t3.nosql数据的hash算法分布(多实例、DB)，每个hash为节点\n\n\t4.nosql数据震荡处理 （当某节点挂了寻找替代节点算法（多层hash替代节点）。。。）\n\n\t5.恢复节点数据\n\n\t6.请求：http-\u003ewebserver-\u003e【对key计算一致性hash节点】-\u003econnect对应的redis实例\n\t                                                    \n\t                                                    -\u003e1.redis(有数据)-\u003e 返回数据\n\n\t\t\t\t                            -\u003e2.redis(无数据)-\u003e mysql (并写入数据redis) -\u003e 返回数据\n\n\t\t                                            -\u003e3.redis节点挂掉-\u003e 业务寻址hash替代节点 \n\t\t                                                                      -\u003e 3.1 redis(有数据) -\u003e 返回数据\n\n\t\t\t\t\t\t\t\t\t\t      -\u003e 3.2 redis(无数据) -\u003e mysql(并写入数据redis) -\u003e 返回数据\n\n\n ![image](https://github.com/liukelin/canal_mysql_nosql_sync/raw/master/img/canal-mysql-nosql.png)\n \n \n为什么要使用消息队列（MQ）进行binlog传输:\n\t\n\t1.增加缓冲，binlog生产端（canal client）只负责生产而不需要考虑消费端的消费能力, 不等待阻塞。\n\t\n\t2.binlog 消费端: 可实时根据MQ消息的堆积情况，动态 增加/减少 消费端的数量，达到合理的资源利用和消费\t\n\t\n\t\n部署:\n\n\t阿里canal纯java开发，所以要先安装java环境\n\n安装jdk(推荐jdk1.8):\n\t安装过程参考网上资料，（注意环境变量配置）\n\nmysql配置：\n\t1.编辑mysql配置文件\n\t\t$ sudo vim /etc/my.cnf\n\t\t\n\t\t[mysqld]  \n\t\tlog-bin=mysql-bin #binlog文件名（也可以使用绝对路径）\n\t\tbinlog-format=ROW #选择row模式  \n\t\tserver_id=1 \t  #实例唯一ID，不能和canal的slaveId重复\n\t\n\t保存并退出，并重启mysql\n\t\t$ sudo service mysql restart\n\t\n\t2.创建 mysql账号密码（账号密码自定 权限自定）\n\t\n\t\tCREATE USER canal IDENTIFIED BY 'canal';    \n\t\tGRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';  \n\t\t-- GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' ;  \n\t\tFLUSH PRIVILEGES;\n\n canal server 配置启动：\n\t\n\tcanal server 模拟mysql从库并向mysql发送dump命令获取mysql binlog数据。\n\t\n\t1.下载解压项目，这里提供了1.0.22版本:\n\t[canal.deployer-1.0.22.tar.gz](https://github.com/liukelin/canal_mysql_nosql_sync/releases) \n\t可从阿里项目下载最新版本 deployer ：\n\t[https://github.com/alibaba/canal/releases](https://github.com/alibaba/canal/releases)\n\t\n\t2.配置项目：\n\t\t# 公共配置\n\t\t$ sudo vim conf/canal.properties\n\t\t\t\n\t\t\tcanal.port= 11111 # canal server 运行端口，保证该端口为占用状态，或者使用其他未占用端口\n\t\t\n\t\t保存退出。\n\t\t\n\t\t# 实例配置\n\t\t$ sudo vim conf/example/instance.properties\n\t\t\t\n\t\t\t# position info\n\t\t\tcanal.instance.master.address = 127.0.0.1:3306  # mysql连接\n\t\t\t\n\t\t\tcanal.instance.dbUsername = canal  \t\t# mysql账号\n\t\t\tcanal.instance.dbPassword = canal\t\t# 密码\n\t\t\tcanal.instance.defaultDatabaseName = test\t# 需要同步的库名\n\t\t\tcanal.instance.connectionCharset = UTF-8\t# mysql编码\n\t\t\n\t\t保存退出。\n\t\t\n\t\t更多配置查看:\n\t\t[http://agapple.iteye.com/blog/1831873](http://agapple.iteye.com/blog/1831873)\n\t\t\n\t3.启动：\n\t\t$ sh bin/startup.sh\n\t\t\n\t日志文件： $ less logs/canal/canal.log\t # canal server端运行日志\n\t\t  $ less logs/example/example.log   # canal client端连接日志\n\t\t  $ logs/example/meta.log \t    # 实例binlog 读取记录文件（记录变更位置，默认为新增变更(tail)）\n\n canal client 配置启动：\n\t\n\tcanal client将从canal server获取的binlog数据最终以json行格式保存到指定文件(也可省略这步，直接发送到MQ)。\n\t\n\tbinlog生产端和消费端的之间，增加MQ作为缓冲，增加容错度和动态扩展性\n\t\n\t1.下载解压项目，这里自己写了个基于1.0.22版本的项目:\n\t[canal_client1.0.22.zip](https://github.com/liukelin/canal_mysql_nosql_sync/releases)\n\t\n\t源码查看: [canal-client](https://github.com/liukelin/canal_mysql_nosql_sync/tree/master/canal-client)\n\t\t\n\t2.基本配置\n\t\t\n\t\t$vim conf/canal.properties\n\t\t\n\t\t# cancal server host， 上面 canal server的IP\n\t\tcanal.server.host = 127.0.0.1\n\t\t\n\t\t# cancal server port，上面 canal server的启动端口 \n\t\tcanal.server.port = 11111\n\t\t\n\t\t# 数据保存路径 ，自行指定\n\t\tcanal.binlog.dir = db_data\n\t\t\n\t\t# 可选rabbitmq/redis/kafka 作为队列（这里使用 rabbitmq 作为队列传输）\n\t\tcanal.mq = rabbitmq \n\n\t\t###### rabbitmq 基本配置 #####\n\t\trabbitmq.host = 127.0.0.1\n\t\trabbitmq.port = 5672\n\t\trabbitmq.user = test\n\t\trabbitmq.pass = 123456\n\n\t\t\n\t\t保存退出。\n\t\n\t3.启动canal client：\n\t\n\t\t$ sh start_canal_client.sh\n\t\t\n\n修改mysql数据触发。\n\n最终结果：\n\t\t\n\teventType ：操作类型（UPDATE/INSERTDELETE）\n\t\n\tdb：   涉及库\n\t\n\ttable: 涉及表\n\t\n\tbefore:变更前数据\n\t\n\tafter: 变更后数据\n\t\n\ttime:  操作时间\n\t\n\n        $less db_data/binlog_xxxx.log\n \n        {\"binlog\":\"mysql-bin.000009:1235\",\"db\":\"test\",\"table\":\"users\",\"eventType\":\"UPDATE\",\"before\":{\"uid\":\"8\",\"username\":\"duobao153713223\"},\"after\":{\"uid\":\"8\",\"username\":\"duobao153713223\"},\"time\":\"2016-08-22 17:47:25\"}\n\n        {\"binlog\":\"mysql-bin.000009:1533\",\"db\":\"test\",\"table\":\"users\",\"eventType\":\"DELETE\",\"before\":\"\",\"after\":{\"uid\":\"8\",\"username\":\"duobao153713223\"},\"time\":\"2016-08-22 17:48:09\"}\n\n        {\"binlog\":\"mysql-bin.000009:1790\",\"db\":\"test\",\"table\":\"users\",\"eventType\":\"INSERT\",\"before\":\"\",\"after\":{\"uid\":\"9\",\"username\":\"test2\"},\"time\":\"2016-08-22 17:48:45\"}\n\n消费数据demo：（这里使用python3 消费rabbitmq同步到redis 作为案例，实际可根据业务需求，因为此时所需要的数据已是通用的json格式，无限可能）\n        \n\t流程 ：file数据-\u003e MQ -\u003e nosql\n\t\n\tMQ: rabbitMQ\n\t\n\t语言：python3\n\t\n\tNoSql: redis\n\t\n\t\n\t多项目订阅需求，如：client1和client2 需要消费这些数据， 他们得到的数据一样\n\t开始考虑直接用队列：\n\t队列数据： [A, B, C, D] \n\tclient1 ：\n\t           消费进程1：获取AB\n\t           消费进程2：获取CD\n\t\n\tclient2 ：\n\t           消费进程1：获取AB\n\t           消费进程2：获取CD\n\t\n\t这样的话，如果使用rabbitMQ 就必须给每个 client 提供独立的队列。并独立消费\n\t1、使用kafka，利用他的分组group,每个client 为一个组，这样就可保证，数据给每个组一致。\n\t2、对每个项目需求开独立的 canal server instance 和 canal client实例\n\t\n\n\t配置：\n\t   语言：python3\n\t   pip：pika redis\n\t   \n\t   项目代码： python_sync_nosql\n\t   修改配置文件config.py\n\t   \t# 最终存储数据redis\n\t\tredis_host = '127.0.0.1'\n\t\tredis_port = 6379\n\t\t\n\t\t###### rabbitmq 基本配置 #####\n\t\trabbitmq_host = '127.0.0.1'\n\t\trabbitmq_port = 5672\n\t\trabbitmq_user = 'test'    \n\t\trabbitmq_pass = '123456'\n\t\t\n\t\t# 设置对每个table存储使用的key字段\n\t\tredis_cache_map = {\n\t\t\t# db\n\t\t\t'test':{\n\t\t\t\t# table ： kid\n\t\t\t\t'users':'uid', \n\t\t  \t}\n\t\t}\n\t \n\t 运行脚本：\n\t \t$ python3 startup.py\n\t \n\n数据最终存储为Redis 的 hash结构，key为 db_table_id\n![image](https://github.com/liukelin/canal_mysql_nosql_sync/raw/master/img/redis-hash.png)\n\n同步到MongoDB同理\n\t\n\t  这里的demo是将表数据映射到 mongodb 结构\n\t  db    =\u003e db\n\t  table =\u003e 集合\n\t  column=\u003e coll\n![image](https://github.com/liukelin/canal_mysql_nosql_sync/raw/master/img/mongo.png)\n\n\n    \n## 目录结构\n\n~~~\n\n├─canal-client/         封装的canal client客户端 和 消息队列MQ 项目\n│  ├─src/           \t项目代码\n│  ├─lib/           \tjar包依赖\n│  ├─conf/            \t配置文件\n│  ├─canal_client.jar   启动jar\n│  └─start_canal_client.sh     启动文件\n│  \n├─python_sync_nosql/        \t消费MQ binlog数据, 将数据写入到NoSql demo\n│  ├─queue_rabbitmq.py \t\trabbitmq 消费端\n│  ├─sync_redis.py \t\t写入到redis\n│  ├─sync_mongo.py \t\t写入到mongo\n│  ├─config.py \t\t\t配置\n│  └─startup.py         \t启动入口\n└─\n\n~~~\n\n## 总结\n\t\n\t1.使用MQ作为传输，可提高容错度，并且可以起到一个消费速度的缓冲，在程序上加上对队列积压数据的监控，可实时增加或减少MQ消费进程的数量。\n\t\n\t2.为了提高binlog数据的可靠消费，建议使用带有ACK功能的MQ 做为消息队列使用\n\t\n\t3.为了避免多进程对MQ消费速度的时序先后不可控，建议binlog数据只作为触发条件（使用id从mysql获取最新数据）作为数据使用，而不作为具体数据使用。\n\t\n\t3.1.或者弄一个版本号的服务，对每条消息打上一个唯一的版本号，用版本号来确定是否是最新的数据\n\t\n\t4.接下来我会继续完善otter的实际案例 ...\n\t\n\n## 其他\n 这里有个用golang实现的binlog解析的项目: [https://github.com/liukelin/bubod](https://github.com/liukelin/bubod)\n\n\u003ch1\u003e资源下载\u003c/h1\u003e\n\n canal server 服务端deployer： [https://github.com/alibaba/canal/releases/tag/canal-1.0.22](https://github.com/alibaba/canal/releases/tag/canal-1.0.22)\n\n \n canal client 客户端： [https://github.com/liukelin/canal_mysql_nosql_sync/releases/tag/1.0.22.2](https://github.com/liukelin/canal_mysql_nosql_sync/releases/tag/1.0.22.2)\n\n 阿里canal项目原始地址：[https://github.com/alibaba/canal](https://github.com/alibaba/canal)\n \n 数据消费写入nosql例子: python_sync_nosql 这里是消费rabbitmq数据最终同步到redis\n \n 其他说明：[使用canal和canal_mysql_nosql_sync同步mysql数据](https://www.aliyun.com/jiaocheng/1114823.html?spm=5176.100033.2.18.hXjlSb)\n\n使用golang语言开发的版本：[https://github.com/liukelin/bubod](https://github.com/liukelin/bubod)\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliukelin%2Fcanal_mysql_nosql_sync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fliukelin%2Fcanal_mysql_nosql_sync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliukelin%2Fcanal_mysql_nosql_sync/lists"}