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的查询语句就可以了。

  1. duckula以kafka等消息中间件为中心,用消息来进行削峰填谷的作用,消息可以做为消息驱动微服务的驱动器,也可以入ES等分布式搜索引擎 来实现实时搜索。也可以用来驱动其它的业务。
  2. duckula不是完全依赖kafka等消息中间件,它可以直接更新Redis/Mongodb等nosql存储介质,因为不需要业务方支持,便于统一管理,但 要做到这点,需要规范好数据的使用格式,也失去了灵活性。
  3. duckula也可以做为异地双活/多活的数据同步方案,因为它的性能足够的强,在测试环境(0.1ms网络延迟)下能达到近2W tps。
  4. duckula支持自定义插件的方式使用,它的序列化和发送者 可以做到自定义。

模块定义

功能模块

对应于上面的4大功能,对应有四大模块提供支撑:

  1. binlog在线监听
    实时监听mysql的binlog,并解析后推送kafka等存储中间件(也有es和redis等,但kafka适用最
    广泛,测试也最多)

  2. binlog离线解析
    离线补数据用

  3. kafka消费监听
    消费模块1推过来的数据,并把这些数据存放到ES/mysql等。(ES是重点功能,支持也最好)

  4. 数据库全量导入
    把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/dockertiller(k8s)
服务器需要,通过duckula支持初始化不需要
Haduckula自己做watch监听做HAk8s的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监听的task配置示例
这个就是普通的binlog监听规则,注意点就是需要同时监听user_info和user_addr的2张表:
binlog_test_dbuser_info|user_addr{‘topic’:‘demo_join’}

用于kafka的consumer的监听规则

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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

偏锋书生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值