什么是数据的异构处理。简单说就是为了满足我们业务的扩展性,将数据从某种特定的格式转换到新的数据格式中来。

为什么会有这种需求出现呢?

传统的企业中,主要都是将数据存储在了关系型数据库中,例如说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这类型的中间件来进行操作,关于这部分的内容我会在后续的文章中做详细的输出。

推荐阅读(点击即可跳转阅读)

1.SpringBoot内容聚合

2.面试题内容聚合

3.设计模式内容聚合

4.Mybatis内容聚合

5.多线程内容聚合

数据的异构实战(一) 基于canal进行日志的订阅和转换的更多相关文章

  1. ASP.NET Core 实战:基于 Dapper 扩展你的数据访问方法

    一.前言 在非静态页面的项目开发中,必定会涉及到对于数据库的访问,最开始呢,我们使用 Ado.Net,通过编写 SQL 帮助类帮我们实现对于数据库的快速访问,后来,ORM(Object Relatio ...

  2. 开源基于Canal的开源增量数据订阅&消费中间件

    CanalSync canal 是阿里巴巴开源的一款基于数据库增量日志解析,提供增量数据订阅&消费,目前主要支持了MySQL(也支持mariaDB). 我开发的这个CanalSync项目 ht ...

  3. 基于canal的client-adapter数据同步必读指南

    本文将介绍canal项目中client-adapter的使用,以及落地生产中需要考虑的可靠性.高可用与监控报警.(基于canal 1.1.4版本) canal作为mysql的实时数据订阅组件,实现了对 ...

  4. 从SQL Server到MySQL,近百亿数据量迁移实战

    从SQL Server到MySQL,近百亿数据量迁移实战 狄敬超(3D) 2018-05-29 10:52:48 212 沪江成立于 2001 年,作为较早期的教育学习网站,当时技术选型范围并不大:J ...

  5. Java生鲜电商平台-SpringCloud微服务开发中的数据架构设计实战精讲

    Java生鲜电商平台-SpringCloud微服务开发中的数据架构设计实战精讲 Java生鲜电商平台:   微服务是当前非常流行的技术框架,通过服务的小型化.原子化以及分布式架构的弹性伸缩和高可用性, ...

  6. 基于Canal和Kafka实现MySQL的Binlog近实时同步

    前提 近段时间,业务系统架构基本完备,数据层面的建设比较薄弱,因为笔者目前工作重心在于搭建一个小型的数据平台.优先级比较高的一个任务就是需要近实时同步业务系统的数据(包括保存.更新或者软删除)到一个另 ...

  7. 大数据存储:MongoDB实战指南——常见问题解答

    锁粒度与并发性能怎么样? 数据库的读写并发性能与锁的粒度息息相关,不管是读操作还是写操作开始运行时,都会请求相应的锁资源,如果请求不到,操作就会被阻塞.读操作请求的是读锁,能够与其它读操作共享,但是当 ...

  8. 《Wireshark数据包分析实战》 - http背后,tcp/ip抓包分析

    作为网络开发人员,使用fiddler无疑是最好的选择,方便易用功能强. 但是什么作为爱学习的同学,是不应该止步于http协议的,学习wireshark则可以满足这方面的需求.wireshark作为抓取 ...

  9. 【Todo】【读书笔记】大数据Spark企业级实战版 & Scala学习

    下了这本<大数据Spark企业级实战版>, 另外还有一本<Spark大数据处理:技术.应用与性能优化(全)> 先看前一篇. 根据书里的前言里面,对于阅读顺序的建议.先看最后的S ...

随机推荐

  1. zoj 3261 Connections in Galaxy War(并查集逆向加边)

    题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3261 题意:有很多颗星球,各自有武力值,星球间有一些联系通道,现 ...

  2. 【Offer】[27]【二叉树的镜像】

    题目描述 思路分析 测试用例 Java代码 代码链接 题目描述 请完成一个函数,输入一颗二叉树,该函数输出它的镜像. 二叉树的镜像定义:源二叉树 8 / \ 6 10 / \ / \ 5 7 9 11 ...

  3. Leetcode:合并两个有序链表

    class Solution { public ListNode mergeTwoLists(ListNode l1, ListNode l2) { if (l1 == null) { return ...

  4. 【LeetCode】56-合并区间

    题目描述 给出一个区间的集合,请合并所有重叠的区间. 示例 1: 输入: [[1,3],[2,6],[8,10],[15,18]] 输出: [[1,6],[8,10],[15,18]] 解释: 区间 ...

  5. 005 Python开发环境配置

    目录 一.概述 二.Python语言概述 2.1 Python语言的诞生 2.2 Monty Python组合 三.Python语言系统开发环境配置 四.Python程序编写与运行 4.1 Pytho ...

  6. netcore 基于 DispatchProxy 实现一个简单Rpc远程调用

    前言 netcore 发布以来,一直很关注netcore的进程.目前在公司负责的网站也历经波折的全部有.net framework 4.0 全部切换到netcore 2.2 版本中.虽然过程遇到的坑不 ...

  7. 表达式树练习实践:C# 五类运算符的表达式树表达

    目录 表达式树练习实践:C# 运算符 一,算术运算符 + 与 Add() - 与 Subtract() 乘除.取模 自增自减 二,关系运算符 ==.!=.>.<.>=.<= 三 ...

  8. 数据库(DDL,DML,DQL、DCL)

    1.数据查询语言DQL 数据查询语言DQL基本结构是由SELECT子句,FROM子句,WHERE   子句组成的查询块:   SELECT <字段名表>   FROM <表或视图名& ...

  9. 提供就医帮助的安卓APP

    首先 这是我们团队第一次开发安卓APP,也是我 个人第一个开发项目APP,俗话说:“万事开头难”.所以对于新手的我们来说,做好开发前的准备至关重要.凡事预则立不预则废! 首先我们团队这次开发的提供就医 ...

  10. 微信小程序全局变量的设置、使用、修改

    1. 全局变量的设置 在miniprogram > app.js 文件中设置,globalData对象就是存储全局变量的. App({ globalData: { hasLogin: false ...