SpringCloud对使用者透明的数据同步组件
一、背景
云端使用Spring Cloud实现,A服务有一些数据,B和C服务也需要A服务的这些数据,但是系统上面只有A服务有数据操作的入口,B和C服务只能从A服务处同步数据到自己的表里面。
解决方案是A服务对数据进行增删改操作之后,将数据操作发送给消息服务,B和C服务从消息服务拉取数据同步消息,然后修改自己的数据。
本文以kafka作为消息服务组件。
二、需求和目标
开发一个组件,A服务引用此组件,配置kafka连接和topic信息,注入生产者,即可向指定的topic发送数据同步消息。
B和C服务引用此组件,配置kafka连接和topic信息,编写核心的数据同步业务代码,即可实现同步数据的消费和入库。
有以下几点要求:
对使用者透明,使用者不需要了解具体的消息服务组件,只需要使用数据同步组件的API即可实现数据的发送和消费;
扩展方便,如果云端的消息服务不使用kafka而是其余的比如rabbitmq或rocketmq之类的消息服务,使用者不需要修改代码,组件库添加支持rabbitmq或rocketmq的生产者和消费者监听,使用者修改一下配置即可。
三、组件概述
将具体的消息组件(如kafka、rabbitmq或rocketmq等)封装在底层,提供给数据同步的生产者和消费者相对友好、与底层消息组件无关的API和接口。
在生产者这边,在业务层中注入生产者即可使用producer来发送需要同步的数据消息。
在消费者这边,只要编写类实现业务消费者接口,并且将其放入spring中管理即可消费到同步数据消息做业务处理。
四、组件开发 - 基础
1. 消息实体类
生产者接口的实现类需要将数据同步消息封装成DataSyncMessage的对象,序列化之后进行发送。
消费者监听会将消息反序列化为DataSyncMessage对象,从里面获取到操作类型和数据类型,以及同步的真实数据,然后调用具体的业务消费者实现的相应的方法来做业务处理。
1 /**
2 * 数据同步消息封装
3 */
4 @Data
5 public class DataSyncMessage {
6
7 private Integer operationType;
8
9 private Integer dataType;
10
11 private Object data;
12 }
2. 操作类型枚举
OperationType枚举封装的操作类型,增、删、改等。
1 /**
2 * 操作类型枚举
3 */
4 public enum OperationType {
5
6 /**
7 * 新增
8 */
9 INSERT(1, "insert"),
10
11 /**
12 * 修改
13 */
14 UPDATE(2, "update"),
15
16 /**
17 * 删除
18 */
19 DELETE(3, "delete");
20
21 private Integer value;
22 private String key;
23
24 OperationType(Integer value, String key) {
25 this.value = value;
26 this.key = key;
27 }
28
29 public String getKey() {
30 return key;
31 }
32
33 public Integer getValue() {
34 return value;
35 }
36
37 public static OperationType getEnum(Integer value) {
38 return Stream.of(OperationType.values())
39 .filter(e -> e.getValue().equals(value))
40 .findFirst()
41 .orElse(null);
42 }
43 }
3. 同步数据类型枚举
1 /**
2 * 数据类型枚举
3 */
4 public enum DataType {
5
6 /**
7 * 用户
8 */
9 USER(1, "USER"),
10
11 /**
12 * 订单
13 */
14 ORDER(2, "ORDER");
15
16 private Integer value;
17 private String key;
18
19 DataType(Integer value, String key) {
20 this.value = value;
21 this.key = key;
22 }
23
24 public String getKey() {
25 return key;
26 }
27
28 public Integer getValue() {
29 return value;
30 }
31
32 public static DataType getEnum(Integer value) {
33 return Stream.of(DataType.values())
34 .filter(e -> e.getValue().equals(value))
35 .findFirst()
36 .orElse(null);
37 }
38 }
五、组件开发 - 生产者
1. 生产者接口
提供给数据同步的生产者使用,对于生产者来说,具体的底层消息组件是透明的。
1 /**
2 * 数据同步生产者接口
3 */
4 public interface DataSyncProducer {
5
6 /**
7 * 发送同步消息
8 *
9 * @param operationType 操作类型,增删改等
10 * @param dataType 数据类型,用户、订单等
11 * @param data 同步的数据
12 */
13 void sendSyncData(OperationType operationType, DataType dataType, Object data);
14 }
组件当前内置了kafka的生产者实现和自动注入配置类,如果将来的技术选型是rocketmq或者其他消息中间件,编写一个相对应的生产者实现类和配置类即可使用,数据同步的生产者不需要修改业务代码。
2. 生产者Kafka实现
内置的kafka生产者实现,应该根据不同的数据同步消息中间件选型编写不同的生产者实现类和配置类。
1 /**
2 * 数据同步生产者 - kafka实现
3 */
4 @Slf4j
5 public class KafkaDataSyncProducer implements DataSyncProducer {
6
7 private final KafkaTemplate<String, String> kafkaTemplate;
8
9 private final String topic;
10
11 public KafkaDataSyncProducer(KafkaTemplate<String, String> kafkaTemplate, String topic) {
12 this.kafkaTemplate = kafkaTemplate;
13 this.topic = topic;
14 }
15
16 @Override
17 public void sendSyncData(OperationType operationType, DataType dataType, Object data) {
18
19 DataSyncMessage message = new DataSyncMessage();
20 message.setOperationType(operationType.getValue());
21 message.setDataType(dataType.getValue());
22 message.setData(data);
23
24 String jsonMessage = JSON.toJSONString(message);
25
26 kafkaTemplate.send(topic, jsonMessage);
27 }
28 }
3. 生产者kafka配置类
kafka数据同步生产者配置类,只有在data-sync.producer.data-sync-type配置的值为kafka时才自动装配kafka生产者。
1 @Configuration
2 @ConditionalOnProperty(prefix = "data-sync.producer", name = "data-sync-type", havingValue = "kafka")
3 public class KafkaDataSyncConfig {
4
5 @Value("${data-sync.producer.data-sync-topic}")
6 private String dataSyncTopic;
7
8 @Resource
9 private KafkaTemplate<String, String> kafkaTemplate;
10
11 @Bean
12 public DataSyncProducer dataSyncProducer() {
13 return new KafkaDataSyncProducer(kafkaTemplate, dataSyncTopic);
14 }
15 }
@ConditionalOnProperty注解表示,当data-sync.producer.data-sync-type配置的值为kafka时才会装配该configuration类。
六、组件开发 - 消费者
1. 业务层需实现的接口
数据同步消费者需要编写DataSyncConsumer接口的实现类并将其注入到spring中,另外实现类需要使用@DataSyncListener标注,并指定数据类型。
1 /**
2 * 数据同步监听器接口
3 */
4 public interface DataSyncConsumer<T> {
5
6 /**
7 * 当发生插入操作时触发
8 */
9 void onInsert(T data);
10
11 /**
12 * 当发生修改操作时触发
13 */
14 void onUpdate(T data);
15
16 /**
17 * 当发生删除操作时触发
18 */
19 void onDelete(T data);
20 }
2. 消费扫描处理器
DataSyncListenerAnnotationBeanPostProcessor处理器会扫描到容器中被@DataSyncListener标注、实现了DataSyncConsumer接口的所有bean对象,从@DataSyncListener注解获取数据类型,然后将此bean对象交给DataSyncConsumerHolder管理。
DataSyncConsumerHolder管理着所有的数据同步消费者,内部使用Map结构,key是同步消费者所处理的数据类型,value是消费者对象本身。
BeanPostProcessor接口的实现会在bean对象被初始化之后调用,此处不做过多解释。

1 @Slf4j
2 @Component
3 public class DataSyncListenerAnnotationBeanPostProcessor implements BeanPostProcessor {
4
5 @Override
6 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
7
8 DataSyncListener annotation = bean.getClass().getAnnotation(DataSyncListener.class);
9
10 if (annotation != null) {
11
12 Class<?>[] interfaces = bean.getClass().getInterfaces();
13
14 boolean isDataSyncConsumer = false;
15
16 for (Class<?> i : interfaces) {
17 if (i == DataSyncConsumer.class) {
18 isDataSyncConsumer = true;
19 break;
20 }
21 }
22
23 if (isDataSyncConsumer) {
24
25 DataSyncConsumer dataSyncConsumer = (DataSyncConsumer) bean;
26
27 DataType dataType = annotation.dataType();
28
29 Class clazz = annotation.dataClass();
30
31 DataSyncDataTypeAndConsumer typeAndConsumer = new DataSyncDataTypeAndConsumer();
32 typeAndConsumer.setDataClass(clazz);
33 typeAndConsumer.setConsumer(dataSyncConsumer);
34
35 DataSyncConsumerHolder.add(dataType, typeAndConsumer);
36
37 DataSyncConsumerHolder.print();
38 }
39 }
40
41 return bean;
42 }
43 }
@DataSyncListener注解:
1 /**
2 * 标注在数据同步实现类上
3 */
4 @Retention(RetentionPolicy.RUNTIME)
5 @Target({ElementType.TYPE})
6 @Documented
7 public @interface DataSyncListener {
8
9 DataType dataType();
10
11 Class dataClass();
12 }
3. Kafka消费者监听
kafka数据同步消费者监听器,只有在data-sync.consumer.data-sync-type配置的值为kafka时才自动装配kafka监听。

1 @Slf4j
2 @Service
3 @ConditionalOnProperty(prefix = "data-sync.consumer", name = "data-sync-type", havingValue = "kafka")
4 public class KafkaDataSyncService {
5
6 @KafkaListener(topics = {"${data-sync.consumer.data-sync-topic}"})
7 public void handleDataSyncMessage(ConsumerRecord<?, ?> record) {
8 Optional<?> kafkaMessage = Optional.ofNullable(record.value());
9 if (!kafkaMessage.isPresent()) {
10 return;
11 }
12 String message = kafkaMessage.get().toString();
13
14 log.info("topic:{},message:{}", record.topic(), message);
15
16 // 业务处理
17 dataSync(message);
18 }
19
20 private void dataSync(String message) {
21
22 try {
23
24 DataSyncMessage dataSyncMessage = JSON.parseObject(message, DataSyncMessage.class);
25
26 Integer dataType = dataSyncMessage.getDataType();
27 OperationType operationType = OperationType.getEnum(dataSyncMessage.getOperationType());
28
29 DataType type = DataType.getEnum(dataType);
30
31 // debug
32 DataSyncConsumerHolder.print();
33
34 DataSyncDataTypeAndConsumer consumer = DataSyncConsumerHolder.get(type);
35
36 if (consumer == null) {
37 log.warn("Consumer not found for data type: {}", type);
38 return;
39 }
40
41 Class clazz = consumer.getDataClass();
42
43 DataSyncConsumer realConsumer = consumer.getConsumer();
44
45 Object data = dataSyncMessage.getData();
46
47 // debug
48 log.info("data = " + data);
49
50 Object o;
51
52 if (clazz == Map.class) {
53 o = data;
54 } else {
55 o = clazz.newInstance();
56 BeanUtils.populate(o, (Map<String, ? extends Object>) data);
57 }
58
59 switch (operationType) {
60 case INSERT:
61 realConsumer.onInsert(o);
62 break;
63 case DELETE:
64 realConsumer.onDelete(o);
65 break;
66 case UPDATE:
67 realConsumer.onUpdate(o);
68 break;
69 default:
70 log.warn("Error operation: " + operationType);
71 }
72
73 } catch (Exception e) {
74 log.error(e.getLocalizedMessage(), e);
75 log.error("An exception occurred during data synchronization: ", message);
76 }
77 }
78 }
七、生产者配置及使用
1. 配置
1 data-sync:
2 producer:
3 data-sync-topic: data-sync-topic
4 data-sync-type: kafka
2. 注入生产者
@Resource
private DataSyncProducer dataSyncProducer;
3. 发送消息
1 User user = new User();
2 user.setUsername("admin");
3
4 dataSyncProducer.sendSyncData(OperationType.INSERT, DataType.USER, user);
八、消费者配置及使用
1. 配置
1 data-sync:
2 consumer:
3 data-sync-topic: data-sync-topic
4 data-sync-type: kafka
2. 实现业务层消费者接口
实现该接口的onInsert、onUpdate、onDelete等方法,如下:
1 @DataSyncListener(dataType = DataType.USER, dataClass = User.class)
2 @Service("UserDataSyncConsumer")
3 public class UserDataSyncConsumer implements DataSyncConsumer<User> {
4
5 @Override
6 public void onInsert(User data) {}
7
8 @Override
9 public void onUpdate(User data) {}
10
11 @Override
12 public void onDelete(User data) {}
13 }
接口泛型是同步数据的数据类型,如果写Map<String, Object>,组件不会对同步数据做解析和封装,直接将原始Map传递给接口方法。
@DataSyncListener注解的dataType参数指定同步数据的数据类型,具体见上面的DataType枚举介绍。dataClass 参数作用同接口泛型。
@Service注解尽量传一个唯一的bean名称。
另一个实现类的例子:
1 @DataSyncListener(dataType = DataType.USER, dataClass = Map.class)
2 @Service("UserDataSyncConsumer")
3 public class UserDataSyncConsumer implements DataSyncConsumer<Map<String, Object>> {
4
5 @Override
6 public void onInsert(Map<String, Object> data) {}
7
8 @Override
9 public void onUpdate(Map<String, Object> data) {}
10
11 @Override
12 public void onDelete(Map<String, Object> data) {}
13 }
九、扩展说明
1. 扩展同步数据类型
为DataType枚举添加新的类型即可。
暂时这样扩展,后续可以优化为更加友好的扩展方式。
2. 扩展支持新的消息服务
比如使用rabbitmq作为消息服务:
- 编写支持rabbitmq的DataSyncProducer实现类;
- 编写rabbitmq生产者配置类,data-sync.producer.data-sync-type配置的值为rabbitmq时才自动装配rabbitmq生产者;
- 编写支持rabbitmq的消息监听器,data-sync.consumer.data-sync-type配置的值为rabbitmq时才自动装配监听器;
- 数据同步生产者和消费者引入组件库,做相应的配置。
十、 参考源码
https://gitee.com/xuguofeng2020/net5ijy-mall/tree/master/component-datasync
SpringCloud对使用者透明的数据同步组件的更多相关文章
- 测试 ClownFish、CYQ、Entity Framework、Moon、MySoft、NHibernate、PDF、XCode数据访问组件性能
下期预告: 由于很多园友反馈,有的组件不应该缺席.测试复杂度不够.测试还缺乏一定的公平. 因此考虑在下一个版本中,确保在更加公平的前提下进行更高复杂度的测试 . 同时将分为2组测试,纯SQL组件及纯O ...
- 开源数据同步神器——canal
前言 如今大型的IT系统中,都会使用分布式的方式,同时会有非常多的中间件,如redis.消息队列.大数据存储等,但是实际核心的数据存储依然是存储在数据库,作为使用最广泛的数据库,如何将mysql的数据 ...
- 【NIFI】 实现数据库到数据库之间数据同步
本里需要基础知识:[NIFI] Apache NiFI 安装及简单的使用 数据同步 界面如下: 具体流程: 1.使用ExecuteSQL连接mysql数据库,通过写sql查询所需要的数据 2.nifi ...
- TiDB 部署及数据同步
简介 TiDB 是 PingCAP 公司受 Google Spanner / F1 论文启发而设计的开源分布式 HTAP (Hybrid Transactional and Analytical Pr ...
- 微服务之数据同步Porter
Porter是一款数据同步中间件,主要用于解决同构/异构数据库之间的表级别数据同步问题. 背景 在微服务架构模式下深刻的影响了应用和数据库之间的关系,不像传统多个服务共享一个数据库,微服务架构下每个服 ...
- WinCE数据通讯之SqlCE数据同步篇
上一篇总结了WinCE通过WebService进行数据通讯的交互方式,今天整理个SqlCE数据同步方式的内容.先说下软件环境:终端平台使用WinCE5.0+SqlCE2.0,服务器使用Windows ...
- 基于ActiveMQ的Topic的数据同步——初步实现
一.背景介绍 公司自成立以来,一直以做项目为主,算是经累经验吧,自去年以来,我们部门准备将以前的项目做成产品,大概细分了几个小的产品,部们下面又分了几个团队,分别负责产品的研发,而我们属于平台团队,负 ...
- 增量数据同步中间件DataLink分享(已开源)
项目介绍 名称: DataLink['deitə liŋk]译意: 数据链路,数据(自动)传输器语言: 纯java开发(JDK1.8+)定位: 满足各种异构数据源之间的实时增量同步,一个分布式.可扩展 ...
- vue2.X props 数据传递 实现组件内数据与组件外的数据的双向绑定
vue2.0 禁止 子组件修改父组件数据 在Vue2中组件的props的数据流动改为了只能单向流动,即只能由组件外(调用组件方)通过组件的DOM属性attribute传递props给组件内,组件内只能 ...
- SQLite与MySQL、SQLServer等异构数据库之间的数据同步
SQLite DBSync是开源嵌入式数据库SQLite的数据同步引擎,实现了SQLite与SQLite数据库之间以及SQLite与异构数据库(Oracle.MySQL.SQLServer)之间的增量 ...
随机推荐
- Kernel Memory 入门系列: RAG 简介
Kernel Memory 入门系列: RAG 简介 开一个新坑,Semantic Kernel系列会在 Release 1.0 之后陆续更新. 当我们有了一定的产品资料或者知识内容之后,自然想着提供 ...
- Oracle密码文件、警告日志文件、跟踪文件
密码文件 oracle用于验证sysdba权限的二进制文件. 警告日志文件 警告日志文件是用来记录oracle数据库系统在运行期间的各种信息: oracle实例打开和关闭.建立表空间.增加数据文件等记 ...
- windows 和 Linux 下 git status 结果不一致
解决该问题 运行一下命令即可 git config core.autocrlf true 解释 git config core.autocrlf true 这个命令是在任何支持的操作系统上都可以运行的 ...
- select/poll/epoll 优缺点比较
校招应届生简历中,有一个 C++ 项目出现的频率非常高... select 1024 fd 限制(可修改,麻烦) 需要遍历才知道哪些 fd ready 副作用:修改传入 fd_set,每次需要重新赋值 ...
- JavaImprove--Lesson04--LocalDateTime,ZoneId,Instant,DateTimeFormatter
一.LocalDateTime LocalDateTime是JDK8的新时间特性,它解决了Date类和Calender类的很多不足,如使用不方便,线程不安全,以及获取时间戳只能拿到毫秒而不能拿到纳秒等 ...
- 文心一言 VS 讯飞星火 VS chatgpt (22)-- 算法导论4.2 2题
二.为Strassen算法编写伪代码. 文心一言: Strassen算法是一种用于矩阵乘法的算法,它的核心思想是通过不断将原始矩阵分割成较小的子矩阵来实现高效的乘法运算.以下是Strassen算法的伪 ...
- Flutter Getx 中的Dialog 以及改变主题
设置应用程序入口 当我们导入依赖后,在应用程序顶层把 GetMaterialApp 作为顶层,如下所示 import 'package:flutter/material.dart'; import ' ...
- 云图说丨初识华为云OrgID:轻松实现统一帐号、统一授权
本文分享自华为云社区<[云图说]第282期 初识华为云OrgID:轻松实现统一帐号.统一授权>,作者: 阅识风云 . 组织成员帐号 OrgID是面向企业提供组织管理.企业成员帐号管理以及S ...
- 几款Java开发者必备常用的工具,准点下班不在话下
摘要:一问一答的形式轻松学习掌握java工具. 以一问一答的形式学习java工具 Q:检查内存泄露的工具有?A: jmap生成dump转储文件,jhat可视化查看. Q:某进程CPU使用率一直占满,用 ...
- 字节跳动基于DataLeap的DataOps实践
更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 本文根据 ArchSummit 全球架构师峰会(深圳站)来自抖音数据研发负责人王洋的现场分享实录整理而成(有删减) ...