duckula介绍
分布式binlog监听中间件duckula能像吸血鬼一样从各mysql实例中得到变化的数据,又能自动复活(HA).支持插件化数据接收者和序列化.非常灵 活.,可在自定义接收者,项目内置了kafka和redis接收者,也可以自定义序列化格式,项目也内置了protobuf2/protobuf3序列化.通过插件的形式嵌入到 duckula. 具有丰富的界面操作,可以通过页面操作进行任务的创建,任务的启动
(一) 前言
互联网体系架构具有可控性差、 数据量大、 架构复杂等特点,错综复杂的各业务模块需要解耦,各异构数据需要同步,双活/多活的容灾方案需要高实时性 等,在各种场合都需要一套可靠的数据实时推送方案。mysql已成为互联网项目存储的主力,围绕着它的各外围模块急需实时地获取它的数据,binlog监听是解决此实时同步问题的不二之选。duckula就是为了满足此需求而设计与开发出来的中间件。
(二)duckula简介
分布式binlog监听中间件duckula能像吸血鬼一样从各mysql实例中得到变化的数据,又能自动复活(HA).支持插件化数据接收者和序列化.非常灵 活.,可在自定义接收者,项目内置了kafka和redis接收者,也可以自定义序列化格式,项目也内置了protobuf2/protobuf3序列化.通过插件的形式嵌入到 duckula. 具有丰富的界面操作,可以通过页面操作进行任务的创建,任务的启动与停止.合适的调度策略,当某个task由于某种原因"自杀"后,系统会自动 选择其它占用资源较少的服务器来运行此task.支持GTID和文件名+位置的2种形式的起动监听方式,且可以选择以前的任何位点进行监听(只要能起得 来).支持docker和k8s运行.支持metrics的计数(现只是打好日志,后续会加入界面查看)
功能概述
duckula的重头戏是binlog监听,但它又不止于binlog监听,它的作用范围有了进一步的延伸,如:把监听的消息实时入ES,通过配置完成mysql到ES的快速全量导入等,这样就有了一个从mysql到ES的闭环。有了duckula后,业务开发人员就不用理会mysql到es的同步问题,只需跟据业务需求开发ES的查询语句就可以了。
- duckula以kafka等消息中间件为中心,用消息来进行削峰填谷的作用,消息可以做为消息驱动微服务的驱动器,也可以入ES等分布式搜索引擎 来实现实时搜索。也可以用来驱动其它的业务。
- duckula不是完全依赖kafka等消息中间件,它可以直接更新Redis/Mongodb等nosql存储介质,因为不需要业务方支持,便于统一管理,但 要做到这点,需要规范好数据的使用格式,也失去了灵活性。
- duckula也可以做为异地双活/多活的数据同步方案,因为它的性能足够的强,在测试环境(0.1ms网络延迟)下能达到近2W tps。
- duckula支持自定义插件的方式使用,它的序列化和发送者 可以做到自定义。
模块定义
功能模块
对应于上面的4大功能,对应有四大模块提供支撑:
-
binlog在线监听
实时监听mysql的binlog,并解析后推送kafka等存储中间件(也有es和redis等,但kafka适用最
广泛,测试也最多) -
binlog离线解析
离线补数据用 -
kafka消费监听
消费模块1推过来的数据,并把这些数据存放到ES/mysql等。(ES是重点功能,支持也最好) -
数据库全量导入
把mysql的表数据全量导入到ES(暂时只支持ES,后续也会做插件化)
代码模块
代码模块又会为2大块,一块是运行时代码块,另一块是开发者代码块。
基础工具类代码模块
是整个duckula项目的基础,它提供一些工具类辅助完成duckula的代码开发,有了它们,duckula至少减小了三分之一的代码量。 它是独立于duckula的,也适用于所有的java项目。这些模块是平时开发时提炼出来的“精品”,由于经过许多的项目锤炼,我们可以认为它是较稳定的。它的源码在
parent(父pom): https://gitee.com/rjzjh/parent
common(工具类):https://gitee.com/rjzjh/common
tams(ops用到的tapestry组件库): https://gitee.com/rjzjh/tams
开发者代码模块
用于支持开发人员做duckula插件开发,duckula的核心的插件也会对它有依赖,开发人员可以用它来做插件的定制开发,开发完的插件需求放到“运行时代码模块”中进行部署。它的代码在:https://github.com/rjzjh/duckula-dev 它又可以会为两大类:
duckula-dev-plugin(插件开发)
用于各种插件的开发的依赖,它们是:
- duckula-dev-plugin-serializer…序列化插件接口(发送到kafka等消息中间件前的序列化)
- duckula-dev-plugin-receiver … 接收者插件接口(kafka/redis/es/自定义 等接收者开发)
- duckula-dev-plugin-busi…业务处理插件接口(解析完binlog后,发送给接收者前的业务处理)
- duckula-dev-plugin-common…各接口用到的一些公用Bean定义
- duckula-dev-plugin-consumer…消费者插件接口
duckula-dev-client(客户端反序列化)
用于业务接收kafka等消息中间件的binlog数据(可能做过序列化了)
- duckula-dev-client-protobuf3…probuf3的反序列化(推荐使用)
- duckula-dev-client-protobuf2…probuf2的反序列化(较少使用)
- duckula-dev-client-thrift…thrift的反序列化(未测试)
运行时代码模块
它供duckula执行任务使用,是用来运行duckula的必要模块,它对“开发者代码块”中的模块会有依赖,甚至直接使用其提供的核心插件来支持duckula的任务执行。
-
duckula-plugin-redis …redis发送者插件
-
duckula-serializer-protobuf3 …pb3序列化插件
-
duckula-serializer-protobuf2 …pb2序列化插件
-
duckula-serializer-thrift …thrift序列化插件
-
duckula-common …公共的引用模块
-
duckula-task …binlog监听模块
-
duckula-plugin-kafka …发送kafka发送者插件
-
duckula-plugin-elasticsearch …直接发送ES的插件
-
duckula-busi-filter …过虑器插件(满足不同场景的过虑要求)
-
duckula-dump-elasticsearch …全量导入ES的模块
-
duckula-kafka-consumer …消息kafka数据的模块
-
duckula-ops Tapestry 5 Application …ops控制台模块
运维支持模块
它用于打docker镜像及k8s的helmchart包用,它主要集中放到
- duckula-install …打包安装模块
它有的目录有:
-
bin …各功能模块的运行脚本
-
conf-***(env环境) …各开发环境的配置信息
-
docker …各功能模块的Dockerfile文件及入口脚本
-
k8s …helm的chart包
-
logs…各功能模块运行时的日志及离线binlog和位点历史等
依赖版本(在开发时使用了下面版本,更多的版本未做测试)
-
JDK8
-
zookeeper 3.5.3-beta
-
kafka 1.0.2
-
es 6.3.2
-
mysql: 5.6
-
k8s 1.10.11
-
tiller 2.11.0
-
docker 18.09.02
(三) 安装
安装模式
duckula支持“process/docker”和"tiller(k8s)" 二种方案的布署方案,下面是它的一些对比:
对比项 | process/docker | tiller(k8s) |
---|---|---|
服务器 | 需要,通过duckula支持初始化 | 不需要 |
Ha | duckula自己做watch监听做HA | k8s的pod自动重启机制 |
分布式锁 | zookeeper的分布式锁 | k8s的命名冲突及zk分布式锁 |
存储 | 分散在每个服务器 | k8s的分布式存储(storageClass支持),task共享 |
网络 | 服务器主机的网络 | k8s网络 |
布署方式 | duckula通过SSH初始化服务器 | helm的chart包布署 |
不同的布署方案,是指task及consumer任务的运行方式,对ops布署不受影响,为了管理方便,可以把ops布署在k8s集群外面,需要配置不同的布署方案,只需要修改配置文件 "duckula-ops.properties"的配置项:
"duckula.ops.starttask.pattern"即可,它可提供的值有:
-
process process/docker模式,可以 以进程方式也可以 以docker容器的方式启动 task和consumer
-
tiller tiller(k8s) 模式,只能通过tiller启动task和consumer,要求k8s必须安装tiller
-
k8s 原生的k8s模式,这种方式还没有做支持。也许以后会废弃,请不要设置这个值
需要说明
task和docker是推荐的运行方式,具体是使用process进程还是使用docker容器来启监听任务,这个是由服务器决定的,在duckula的“服务器管理”页面,每个服务器都会有“是否docker”选项。在初始化服务器时,如果此服务器安装了docker并启动了 daemon,duckula会自动走docker模式来初始化服务器,那么此选项应该会被设置为“是”。那么,在以后的task和consumer调度中,只要选择了此服务,则会自动启用docker模式来运行监听任务。 如果一台非docker的服务器初始化后,哪怕后面又装了docker,duckula也不会走docker模式来部署监听任务的。
前缀配置
duckula依赖其它中间件,要很好地运行duckula,会有一些前缀配置要求
开启binlog日志
[mysqld]
log_bin=mysql-bin
binlog_format=ROW
server_id = 1
开启gtid
[mysqld]
gtid-mode=on
enforce-gtid-consistency=1
log-slave-updates=1
源码安装
duckula由于特殊配置原因,没有提供realse包进行安装,需要由源码执行maven命令得到realse包进行安装。
打包前配置
- 打包环境确认,打包者首要要明确的是这次打的包将运行于哪套环境,duckula支持4套环境:dev/test/pre/prd,分别对应于duckula-install\conf-[环境]目录,duckula现在放了2个目录,conf-dev 存放的是配置模板。 conf-test存放的是duckula开发时用的配置,里面的中间件都是内网配置。打包者可以随便复制一份,或是直接修改相应的配置。修改后需要修改文件"duckula-install\pom.xml"的env参数:
<env>
test
</env>
- rootPwd: 是指打包机的root密码,如果需要连带docker也打或是push也做了就需要提供
- isDocker: 需要同时打docker需要设置为true
- needpush: 需要推送镜像时里要设置为true
- 如果要推镜像还需要修改:./duckula-install/bin/docker-build.sh 相关镜像仓库的配置
- 如果需要用docker模式或k8s。还需要修改:duckula-install\conf-[环境]\duckula-ops.properties文件的两个参数:
duckula.task.image.name=rjzjh/duckula
duckula.task.image.tag=task.2.0.0
打安装包
duckula的安装非常简单,只需在duckula的根目录执行
mvn clean package -Dmaven.test.skip=true -Dmaven.javadoc.skip=true
PowerShell上命令稍有些不同
mvn clean package -DskipTests "-Dmaven.javadoc.skip=true"
执行上面的命令后将会在 duckula-install\target 目录下生成3个文件
- duckula-ops.war ops项目包,它需要一个tomcat之类的容器布署
- duckula.tar duckula的运行主体程序,有task/consumer/dump等程序
- duckula-data.tar 配置信息、log日志目录、插件配置、离线binlog下载目录等信息
然后把它们按各自需求分别布署就完了。
ops控制台安装
-
配置好环境变量"DUCKULA_HOME"和"DUCKULA_DATA"用于放duckula的配置文件,
eg: DUCKULA_HOME=D:\opt\duckula DUCKULA_DATA=D:\data\duckula-data
-
下载一个较新版本的tomcat,我测试部署用的是apache-tomcat-9.0.6,开发时用的是jetty 6 。把duckula-ops.war放到webapps目录下,启动tomcat就可以了
tiller和k8s安装
tiller和k8s的模式开发较为新,一些功能也没来得及做,如健康检查等,可以推荐在测试环境上运行。以下是这种模式应该注意点:
独立模式
独立模式是指在k8s运行的时候不采用分布式存储的方案,也就是说k8s每个task运行自己的配置,duckula设置卷存储为hostPath路径/data/duckula-data,保证了同一个k8s节点的任务用的是相同的配置。 这种模式不确定性较大,如果配置不相同将导致监听任务的表现也不是一致的,那么做HA也没有任何意义,所以,这种模式只能用于开发调式环境,比如:我在我的win10机器上建了一个只有一个节点的k8s集群,因为它只有一个节点,所以不存在配置不一致的问题。生产环境一定要把它设置为false
claimname
当非独立模式时,duckula会强制设置claimname,它要求布署人员在布署duckula前要创建好一个分布式磁盘卷,且这个卷允许多个容器读写的accessMode: ReadWriteMany。然后把卷名配置到这个参数: claimname
配置
独立模式和claimname的配置在“./duckula-install/conf-test/duckula-ops.properties”文件的
#是否独立启动,true:是独立启动,配置和日志都在本机,重启后将丢失 false:需要设置claimname
duckula.ops.starttask.standalone=true
#使用的磁盘PVC
duckula.ops.starttask.claimname=null
(四) 原理结构
task内部结构
duckula大概由以下几个部分组成:
- ops: 调度中心,相当于duckula的大脑,负责任务、数据库实例、服务器信息等配置。通过分布式锁控制一个任务只能有一个进程运行,当进程挂掉时,用合适的调度策略完成HA的功能。
- zookeeper: duckula状态、配置、运行时数据的存储。分布式锁
- 序列化: 这是指示duckula监听的数据以何种状态进入存储中间件。现支持 protobuf3 ,protobuf2(大数据许多的中间件还停留在这种格式), json(没有序列化)等状态格式,可以以插件的形式自定义序列化状态。
- 接收者(Kafka): 指示duckula如何处理数据,就是说要放入什么样的存储中间件由接收者来决定,内置 kafka,redis等接收者,可以以插件的形式自定义接收者。
- 存储中间件(ES):就是duckula处理的数据将最终流入的目的地。duckula是打通mysql到ES的全流程实时监听中间件,所以数据通过binlog到达Kafka后,可以配置 consumer监听来实时到达ES集群,业务方只管到ES做查询就OK了
- Dump全量导入:由binlog监听、consumer监听只能完成增量部分的数据推送到ES,这样数据是不完整的,那全量部分的导入就由Dump全量导入功能来完成。这样整个方案就完整了
task层次模块
duckula实现了在线监听与离线解析功能,适应于不同场景。
(五) 操作手册(待)
(六) join支持
使用场景
duckula可以做到mysql到ES的增量和全量的同步,但如果仅限制于做一张表对一个索引的同步,那它的使用场景就大大的限制了,有很多的场景都需要有2表关联及多表关联,这就需要duckula可以做到有关联关系时也能跟据规则进行增量和全量的同步。
ES父子关系模式的选择
在ES6.0以后,索引的type只能有一个,使得父子结构变的不那么清晰,毕竟对于java开发者来说,index->db,type->table的结构比较容易理解。
虽然明确不支持父子关系了,但是可以有2种方式达到我们所说的父子关系模型 :nested类型和join类型。nested类型就是采用一个大json存放父表字段,里面可以含有许多的小json存放子表的数据,join类型则更像我们数据库存放的数据形态,在ES里面会有多种数据,其中每条记录会有一个附加的join类型字段,指示它是父表的数据还是子表的数据,它的值可以是一个json,其中有parent字段表示它中哪条记录的子表,这样一级级的指示父子关系,最后就是一棵父子关系树。
网上会有许多的文章来说明2者关系的特点,我从自身理解的方向来说明一下duckula为什么选择join类型:
- 1、join类型更像之前老版本ES用type来做父子关系,对于旧系统的改造会有优势
- 2、join类型所做查询是细粒度的,比如: 1父10000子,如果其中只有1个子记录命中查询,join类型只会查询这一条子记录,但nested类型是大的json,不管你如何查,只要一1条命中了,也会把整个大json给查出来。这对网络和ES也带来了不小的开销。
- 3、对于频繁做新增修改的场景,join类型也是细粒度的,只变更有变化的父或子数据就可以了,而nested类型不论父还是子数据有变更都需要修改整个大的记录,代价非常大。
- 4、使用ES来做查询都是千万级以上的数据,甚至亿级,对于上亿的主表,采用nested类型时,需主表每条记录又要到上亿甚至10亿数量的子表去查询,最后再组装为一个大的json,可想而知对于ES的全量dump导入会是什么样的性能。而对于join类型它可以快速的batch插入,它的全量导入的速度是nested类型全量导入的速度快几个数量级一点不为过。注意:全量的dump导入不是一次性的动作,在以后漫长的维护过程中,不管由于什么原因大批量的丢数据了,修改字段类型, 甚至es与mysql的数据不一致了,都需要再次做全量dump操作。如果不能快速的完成全量dump操作,将对业务造成很大的困扰。
duckula对join模式的支持
由于不是项目形式的,做完能用就算了,duckula是以产品的模式来设计对join类型的支持,只要有类似的需求,运维只需要在duckula的ops上做相应的配置,duckula收集到配置后才能做全量与增量的导入。那么duckula必需要设计一套规则来满足可配置的需求。
index的mapping设计
duckula设计附加的join类型的mapping定义为(mapping片断):
"tams_relations": {
"type": "join",
"eager_global_ordinals": true,
"relations": {
"user_info": "user_addr:user_id"
}
}
字段“tams_relations”是固定的。
eager_global_ordinals:字段预先加载,全局序数会在一个新的段可进行搜索之前进行构建。
relations:定义父子关系。
“user_info”: “user_addr:user_id” 表示user_info为父表user_addr为子表关联字段是user_addr表的user_id字段
ES的数据示例
{
"_index": "demo_join",
"_type": "_doc",
"_id": "6",
"_score": 1,
"_source": {
"birthday": 1531238400000,
"update_time": 1552181688000,
"money": 6678000,
"name": "fff6644",
"id": 6,
"tams_relations": "user_info",
"age": 567666
}
},
{
"_index": "demo_join",
"_type": "_doc",
"_id": "user_addr:6",
"_score": 1,
"_routing": "6",
"_source": {
"postNo": "366528",
"user_id": 6,
"id": 2,
"tams_relations": {
"parent": "6",
"name": "user_addr:user_id"
},
"addr": "aaaa"
}
}
第一条记录是表示主表记录:它的tams_relations值 为join类型的前半部分 ,它的id是原样id不做任何处理。
第二条记录是子表记录,它的tams_relations的name值为mapping定义的join类型的后半部分:user_addr:user_id,注意,它的_id值是加了子表表名做前缀“user_addr:”的,目的为了避免父子表同一个id的记录互相覆盖。tams_relations字段还有一个部分是"parent",表示它的父记录的id,ES的routing也是跟据它来做路由的,这样就保证了存在父子关系的数据一定落到同一个分区中。
binlog用的task监听规则
这个就是普通的binlog监听规则,注意点就是需要同时监听user_info和user_addr的2张表:
binlog_test_dbuser_info|user_addr
{‘topic’:‘demo_join’}
用于kafka的consumer的监听规则
其它配置与普通的consumer没有什么变化 ,主要变化点在它的规则:binlog_test_dbuser_info|user_addr
{‘key’:‘id’,‘relakey’:‘user_id’ ,‘index’:‘demo_join’,‘middleware’:‘dev’}
其它部分没变,也是 库名表名
{规则item},在“规则item”里需要加一个配置:‘relakey’:‘user_id’,表示这个子表的哪个字段用来关系父表id。
索引管理创建索引
与一般的索引创建的区别是它需要填写从表的配置:“库名-表名(从)-关联字段”。上图中的“内容”部分的json是duckula跟据数据库的字段和预行定制好的字段映射关系自动生成的。用户一定不要修改字段名,字段类型可以跟据需求做调整,duckula会试图匹配你调整的类型,但如果转换不成功会抛出异常,需要重新配置或是加强duckula的转换规则,以后可以考虑使用插件来解决此类类型转换问题。
用于全量dump的配置
由于历史原因,dump的配置与之前的配置没有变化,只能一张表一张表的导入,所以主表和子表需要分2次dump,如果资源允许,主表和子表的全量dump可以以2个进程的方式同时进行。
更多推荐
所有评论(0)