数据的异构实战(一) 基于canal进行日志的订阅和转换
什么是数据的异构处理。简单说就是为了满足我们业务的扩展性,将数据从某种特定的格式转换到新的数据格式中来。
为什么会有这种需求出现呢?
传统的企业中,主要都是将数据存储在了关系型数据库中,例如说MySQL这种数据库,但是为了满足需求的扩展,查询的维度会不断地增加,那么这个时候我们就需要做数据的异构处理了。
常见的数据异构有哪些?
例如MySQL数据转储到Redis,MySQL数据转储到es等等,也是因为这种数据异构的场景开始出现,陆陆续续有了很多中间件在市场中冒出,例如说rocketMq,kafka,canal这种组件。
下边有一张通俗易懂的数据异构过程图:
在这里插入图片描述
canal进行数据同步
首先,我们需要正确地打开canal服务器去订阅binlog日志。
关于binlog日志查看常用的几条命令如下:
- #是否启用了日志
- mysql>show variables like 'log_bin';
- #怎样知道当前的日志
- mysql> show master status;
- #查看mysql binlog模式
- show variables like 'binlog_format';
- #获取binlog文件列表
- show binary logs;
- #查看当前正在写入的binlog文件
- show master statusG
- #查看指定binlog文件的内容
- show binlog events in 'mysql-bin.000002';
注意binlog日志格式要求为row格式:
ROW格式日志的特点
记录sql语句和每个字段变动的前后情况,能够清楚每行数据的变化历史,占用较多的空间,不会记录对数据没有影响的sql,例如说select语句就不会记录。可以使mysqlbinlog工具去查看内部信息。
STATEMENT模式的日志内容
STATEMENT格式的日志就和它本身的命名有点类似,只是单独地记录了sql的内容,但是没有记录上下文信息,在数据会UI福的时候可能会导致数据丢失。
MIX模式模式的日志内容
这种模式的日志内容比较灵活,当遇到了表结构变更的时候,就会记录为statement模式,如果遇到了数据修改的话就会变为row模式。
如何配置canal的相关信息?
比较简单,首先通过下载好canal的安装包,然后我们需要在canal的配置文件上边做一些手脚:
- canal的example文件夹下边的properties文件
- canal.instance.master.address=**.***.***.**:3306
- # 日志的文件名称
- canal.instance.master.journal.name=master-96-bin.000009
- canal.instance.dbUsername=****
- canal.instance.dbPassword=****
启动我们的canal程序,然后查看日志,如果显示下边这些内容就表示启动成功了:
- 2019-10-13 16:00:30.072 [main] ERROR com.alibaba.druid.pool.DruidDataSource - testWhileIdle is true, validationQuery not set
- 2019-10-13 16:00:30.734 [main] INFO c.a.otter.canal.instance.spring.CanalInstanceWithSpring - start CannalInstance for 1-example
- 2019-10-13 16:00:30.783 [main] INFO c.a.otter.canal.instance.core.AbstractCanalInstance - start successful....
ps:关于canal入门安装的教程网上有很多,这里我就不做过多的阐述了。
canal服务器搭建起来之后,我们便进入了java端的程序编码部分:
接着再来查看我们的客户端代码,客户端中我们需要通过java程序获取canal服务器的连接,然后进入监听binlog日志的状态。
可以参考下边的程序代码:
- package com.sise.client.simple;
- import com.alibaba.otter.canal.client.CanalConnector;
- import com.alibaba.otter.canal.client.CanalConnectors;
- import com.alibaba.otter.canal.protocol.CanalEntry;
- import com.alibaba.otter.canal.protocol.Message;
- import com.google.protobuf.InvalidProtocolBufferException;
- import com.sise.common.dto.TypeDTO;
- import com.sise.common.handle.CanalDataHandler;
- import java.net.InetSocketAddress;
- import java.util.List;
- import java.util.stream.Collectors;
- /**
- * 简单版本的canal监听客户端
- *
- * @author idea
- * @date 2019/10/12
- */
- public class SImpleCanalClient {
- private static String SERVER_ADDRESS = "127.0.0.1";
- private static Integer PORT = 11111;
- private static String DESTINATION = "example";
- private static String USERNAME = "";
- private static String PASSWORD = "";
- public static void main(String[] args) throws InterruptedException {
- CanalConnector canalConnector = CanalConnectors.newSingleConnector(
- new InetSocketAddress(SERVER_ADDRESS, PORT), DESTINATION, USERNAME, PASSWORD);
- canalConnector.connect();
- canalConnector.subscribe(".*\..*");
- canalConnector.rollback();
- for (; ; ) {
- Message message = canalConnector.getWithoutAck(100);
- long batchId = message.getId();
- if(batchId!=-1){
- // System.out.println(message.getEntries());
- System.out.println(batchId);
- printEntity(message.getEntries());
- }
- }
- }
- public static void printEntity(List<CanalEntry.Entry> entries){
- for (CanalEntry.Entry entry : entries) {
- if (entry.getEntryType()!=CanalEntry.EntryType.ROWDATA){
- continue;
- }
- try {
- CanalEntry.RowChange rowChange=CanalEntry.RowChange.parseFrom(entry.getStoreValue());
- for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
- System.out.println(rowChange.getEventType());
- switch (rowChange.getEventType()){
- //如果希望监听多种事件,可以手动增加case
- case INSERT:
- String tableName = entry.getHeader().getTableName();
- //测试选用t_type这张表进行映射处理
- if ("t_type".equals(tableName)) {
- TypeDTO typeDTO = CanalDataHandler.convertToBean(rowData.getAfterColumnsList(), TypeDTO.class);
- System.out.println(typeDTO);
- }
- System.out.println("this is INSERT");
- break;
- default:
- break;
- }
- }
- } catch (InvalidProtocolBufferException e) {
- e.printStackTrace();
- }
- }
- }
- /**
- * 打印内容
- *
- * @param columns
- */
- private static void printColums(List<CanalEntry.Column> columns){
- String line=columns.stream().map(column -> column.getName()+"="+column.getValue())
- .collect(Collectors.joining(","));
- System.out.println(line);
- }
- }
本地监听到了canal的example文件夹中配置的监听的日志信息之后,就会自动将该日志里面记录的数据进行打印读取。
那么这个时候我们还需要做多一步处理,那就是将坚听到的数据转换为可识别的对象,然后进行对象转移处理。
其实光是链接获取到canal的binlog日志并不困难,接着我们还需要将binlog日志进行统一的封装处理,需要编写一个特定的处理器将日志的内容转换为我们常用的DTO类:
下边这个工具类可以借鉴一下:
- package com.sise.common.handle;
- import com.alibaba.otter.canal.protocol.CanalEntry;
- import com.sise.common.dto.CourseDetailDTO;
- import lombok.extern.slf4j.Slf4j;
- import java.lang.reflect.Field;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- /**
- * 基于canal的数据处理器
- *
- * @author idea
- * @data 2019/10/13
- */
- @Slf4j
- public class CanalDataHandler extends TypeConvertHandler {
- /**
- * 将binlog的记录解析为一个bean对象
- *
- * @param columnList
- * @param clazz
- * @param <T>
- * @return
- */
- public static <T> T convertToBean(List<CanalEntry.Column> columnList, Class<T> clazz) {
- T bean = null;
- try {
- bean = clazz.newInstance();
- Field[] fields = clazz.getDeclaredFields();
- Field.setAccessible(fields, true);
- Map<String, Field> fieldMap = new HashMap<>(fields.length);
- for (Field field : fields) {
- fieldMap.put(field.getName().toLowerCase(), field);
- }
- if (fieldMap.containsKey("serialVersionUID")) {
- fieldMap.remove("serialVersionUID".toLowerCase());
- }
- System.out.println(fieldMap.toString());
- for (CanalEntry.Column column : columnList) {
- String columnName = column.getName();
- String columnValue = column.getValue();
- System.out.println(columnName);
- if (fieldMap.containsKey(columnName)) {
- //基础类型转换不了
- Field field = fieldMap.get(columnName);
- Class<?> type = field.getType();
- if(BEAN_FIELD_TYPE.containsKey(type)){
- switch (BEAN_FIELD_TYPE.get(type)) {
- case "Integer":
- field.set(bean, parseToInteger(columnValue));
- break;
- case "Long":
- field.set(bean, parseToLong(columnValue));
- break;
- case "Double":
- field.set(bean, parseToDouble(columnValue));
- break;
- case "String":
- field.set(bean, columnValue);
- break;
- case "java.handle.Date":
- field.set(bean, parseToDate(columnValue));
- break;
- case "java.sql.Date":
- field.set(bean, parseToSqlDate(columnValue));
- break;
- case "java.sql.Timestamp":
- field.set(bean, parseToTimestamp(columnValue));
- break;
- case "java.sql.Time":
- field.set(bean, parseToSqlTime(columnValue));
- break;
- }
- }else{
- field.set(bean, parseObj(columnValue));
- }
- }
- }
- } catch (InstantiationException | IllegalAccessException e) {
- log.error("[CanalDataHandler]convertToBean,初始化对象出现异常,对象无法被实例化,异常为{}", e);
- }
- return bean;
- }
- public static void main(String[] args) throws IllegalAccessException {
- CourseDetailDTO courseDetailDTO = new CourseDetailDTO();
- Class clazz = courseDetailDTO.getClass();
- Field[] fields = clazz.getDeclaredFields();
- Field.setAccessible(fields, true);
- System.out.println(courseDetailDTO);
- for (Field field : fields) {
- if ("java.lang.String".equals(field.getType().getName())) {
- field.set(courseDetailDTO, "name");
- }
- }
- System.out.println(courseDetailDTO);
- }
- /**
- * 其他类型自定义处理
- *
- * @param source
- * @return
- */
- public static Object parseObj(String source){
- return null;
- }
- }
接着是canal的核心处理器,主要的目的是将binlog转换为我们所希望的实体类对象,该类目前主要考虑兼容的数据类型为目前8种,比较有限,如果读者后续在实际开发中还遇到某些特殊的数据类型可以手动添加到map中。
- package com.sise.common.handle;
- import java.text.ParseException;
- import java.text.SimpleDateFormat;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.Map;
- /**
- * 类型转换器
- *
- * @author idea
- * @data 2019/10/13
- */
- public class TypeConvertHandler {
- public static final Map<Class, String> BEAN_FIELD_TYPE;
- static {
- BEAN_FIELD_TYPE = new HashMap<>(8);
- BEAN_FIELD_TYPE.put(Integer.class, "Integer");
- BEAN_FIELD_TYPE.put(Long.class, "Long");
- BEAN_FIELD_TYPE.put(Double.class, "Double");
- BEAN_FIELD_TYPE.put(String.class, "String");
- BEAN_FIELD_TYPE.put(Date.class, "java.handle.Date");
- BEAN_FIELD_TYPE.put(java.sql.Date.class, "java.sql.Date");
- BEAN_FIELD_TYPE.put(java.sql.Timestamp.class, "java.sql.Timestamp");
- BEAN_FIELD_TYPE.put(java.sql.Time.class, "java.sql.Time");
- }
- protected static final Integer parseToInteger(String source) {
- if (isSourceNull(source)) {
- return null;
- }
- return Integer.valueOf(source);
- }
- protected static final Long parseToLong(String source) {
- if (isSourceNull(source)) {
- return null;
- }
- return Long.valueOf(source);
- }
- protected static final Double parseToDouble(String source) {
- if (isSourceNull(source)) {
- return null;
- }
- return Double.valueOf(source);
- }
- protected static final Date parseToDate(String source) {
- if (isSourceNull(source)) {
- return null;
- }
- if (source.length() == 10) {
- source = source + " 00:00:00";
- }
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- Date date;
- try {
- date = sdf.parse(source);
- } catch (ParseException e) {
- return null;
- }
- return date;
- }
- protected static final java.sql.Date parseToSqlDate(String source) {
- if (isSourceNull(source)) {
- return null;
- }
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
- java.sql.Date sqlDate;
- Date utilDate;
- try {
- utilDate = sdf.parse(source);
- } catch (ParseException e) {
- return null;
- }
- sqlDate = new java.sql.Date(utilDate.getTime());
- return sqlDate;
- }
- protected static final java.sql.Timestamp parseToTimestamp(String source) {
- if (isSourceNull(source)) {
- return null;
- }
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- Date date;
- java.sql.Timestamp timestamp;
- try {
- date = sdf.parse(source);
- } catch (ParseException e) {
- return null;
- }
- timestamp = new java.sql.Timestamp(date.getTime());
- return timestamp;
- }
- protected static final java.sql.Time parseToSqlTime(String source) {
- if (isSourceNull(source)) {
- return null;
- }
- SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
- Date date;
- java.sql.Time time;
- try {
- date = sdf.parse(source);
- } catch (ParseException e) {
- return null;
- }
- time = new java.sql.Time(date.getTime());
- return time;
- }
- private static boolean isSourceNull(String source) {
- if (source == "" || source == null) {
- return true;
- }
- return false;
- }
- }
ps: t_type表是一张我们用于做测试时候使用的表,这里我们可以根据自己实际的业务需要定制不同的实体类对象
现在我们已经可以通过binlog转换为实体类了,那么接下来就是如何将实体类做额外的传输和处理了。数据的传输我们通常会借助mq这类型的中间件来进行操作,关于这部分的内容我会在后续的文章中做详细的输出。
推荐阅读(点击即可跳转阅读)
2.面试题内容聚合
3.设计模式内容聚合
5.多线程内容聚合
数据的异构实战(一) 基于canal进行日志的订阅和转换的更多相关文章
- ASP.NET Core 实战:基于 Dapper 扩展你的数据访问方法
一.前言 在非静态页面的项目开发中,必定会涉及到对于数据库的访问,最开始呢,我们使用 Ado.Net,通过编写 SQL 帮助类帮我们实现对于数据库的快速访问,后来,ORM(Object Relatio ...
- 开源基于Canal的开源增量数据订阅&消费中间件
CanalSync canal 是阿里巴巴开源的一款基于数据库增量日志解析,提供增量数据订阅&消费,目前主要支持了MySQL(也支持mariaDB). 我开发的这个CanalSync项目 ht ...
- 基于canal的client-adapter数据同步必读指南
本文将介绍canal项目中client-adapter的使用,以及落地生产中需要考虑的可靠性.高可用与监控报警.(基于canal 1.1.4版本) canal作为mysql的实时数据订阅组件,实现了对 ...
- 从SQL Server到MySQL,近百亿数据量迁移实战
从SQL Server到MySQL,近百亿数据量迁移实战 狄敬超(3D) 2018-05-29 10:52:48 212 沪江成立于 2001 年,作为较早期的教育学习网站,当时技术选型范围并不大:J ...
- Java生鲜电商平台-SpringCloud微服务开发中的数据架构设计实战精讲
Java生鲜电商平台-SpringCloud微服务开发中的数据架构设计实战精讲 Java生鲜电商平台: 微服务是当前非常流行的技术框架,通过服务的小型化.原子化以及分布式架构的弹性伸缩和高可用性, ...
- 基于Canal和Kafka实现MySQL的Binlog近实时同步
前提 近段时间,业务系统架构基本完备,数据层面的建设比较薄弱,因为笔者目前工作重心在于搭建一个小型的数据平台.优先级比较高的一个任务就是需要近实时同步业务系统的数据(包括保存.更新或者软删除)到一个另 ...
- 大数据存储:MongoDB实战指南——常见问题解答
锁粒度与并发性能怎么样? 数据库的读写并发性能与锁的粒度息息相关,不管是读操作还是写操作开始运行时,都会请求相应的锁资源,如果请求不到,操作就会被阻塞.读操作请求的是读锁,能够与其它读操作共享,但是当 ...
- 《Wireshark数据包分析实战》 - http背后,tcp/ip抓包分析
作为网络开发人员,使用fiddler无疑是最好的选择,方便易用功能强. 但是什么作为爱学习的同学,是不应该止步于http协议的,学习wireshark则可以满足这方面的需求.wireshark作为抓取 ...
- 【Todo】【读书笔记】大数据Spark企业级实战版 & Scala学习
下了这本<大数据Spark企业级实战版>, 另外还有一本<Spark大数据处理:技术.应用与性能优化(全)> 先看前一篇. 根据书里的前言里面,对于阅读顺序的建议.先看最后的S ...
随机推荐
- Python初步接触与学习
Python的发展史与特点 诞生与发展史 1989,为了度过圣诞假期,Guido开始编写Python语言编译器.Python这个名字来自Guido的喜爱的电视连续剧<蒙蒂蟒蛇的飞行马戏团> ...
- mybatis拦截器实现通用权限字段添加
实现效果 日常sql中直接使用权限字段实现权限内数据筛选,无需入参,直接使用,使用形式为:select * from crh_snp.channelinfo where short_code in ( ...
- Android 开发系列教程之(一)Android基础知识
什么是Android Android一词最早是出现在法国作家维里耶德利尔·亚当1986年发表的<未来夏娃>这部科幻小说中,作者利尔·亚当将外表像人类的机器起名为Android,这就是And ...
- .Net基础篇_学习笔记_第四天_switch-case
swith-case 用来处理多条件的定值的判断. 语法: switch(变量或者表达式的值) { case 值1:要执行的代码: break: case 值2:要执行的代码: break: case ...
- Cabloy全栈JS框架微创新之一:不一样的“移动优先 PC适配”
前言 目前流行的前端UI组件库都支持移动设备优先的响应式布局特性.但基于Mobile和PC两个场景的不同用户体验,也往往会实现Mobile和PC两个版本. PC场景下的Web工程,如大量的后台前端管理 ...
- Day4 总结
- mysql集群基于docker 在centos上
新博客https://blog.koreyoshi.work/ mysql集群(PXC)基于docker 在centos上 常用设计方案 Replication(复制) 速度快 弱一致性 低价值 场景 ...
- linux双网卡绑定为逻辑网卡
网卡bond是通过多张网卡绑定为一个逻辑网卡,实现本地网卡的冗余,带宽扩容和负载均衡,在生产场景中是一种常用的技术. 生产环境服务器为:DELL 网卡为:光纤 bond需要修改涉及的网卡配置文件 /e ...
- CocosCreator实现动物同化
获取源码 关注微信公众号『一枚小工 』,发送『动物同化 』获取完整游戏源码. 游戏玩法 游戏目标是将游戏区域的动物全部同化成同一种动物.游戏从左上角开始,从右边点击需要变成的目标动物头像,如果被同化动 ...
- 全文搜索引擎 Elasticsearch
写在前面 最近在学Elasticsearch , 我相信只要是接触过开发的都会听过Elasticsearch或ELK这么一个技术. 主要用来做全文检索或大数据分析等,之前一直处理了解状态. 所以打算系 ...