一、数据同步方式介绍
1.1 什么是数据同步?
数据同步:当数据库中的数据发生变化,需要保证数据库中间件(Redis和ES)的数据与数据库数据保持一致
1.2 数据库中间件数据同步常用方式
-
Change Data Capture(CDC)
:使用CDC技术从数据库中捕获数据更改,并将更改的数据发送到ES。CDC可以监视数据库的事务日志或使用其他方法来检测数据更改,然后将这些更改同步到ES中。这种方法可以确保ES中的数据与数据库中的数据保持一致。 -
双写
:在进行数据更改时,同时更新数据库和中间件(ES、Redis)。应用程序在更新数据库之后,直接将相同的更改发送到中间件。这种方法确保了在数据更改期间数据库和中间件之间的一致性,但需要增加应用程序的开发和维护复杂性。 -
定期同步:定期将数据库中的数据同步到ES中。可以使用定时任务或批处理作业定期将数据库中的数据导入ES。这种方法可能会导致ES和数据库之间的数据有一定的延迟,但可以确保最终一致性。
-
使用消息队列:将数据更改发送到消息队列,然后消费者应用程序将更改应用于数据库和ES。消息队列可以充当缓冲,以确保数据更改在数据库和ES之间传递,并最终保持一致性。
-
采用事件驱动架构:将数据库中的数据更改表示为事件,并使用事件驱动架构来处理这些事件。当数据更改发生时,触发相应的事件,然后使用事件处理程序将更改应用于数据库和ES。这种方法将业务逻辑与数据更改解耦,并支持更高度的可扩展性和灵活性。
无论选择哪种方法,都需要仔细考虑数据一致性和容错性。此外,必须监测和处理中间件和数据库之间的任何同步错误或故障,以确保数据的完整性和一致性。
二、数据双写/双删
2.1 数据双写同步实现
接口1:查询商品信息,使用redis做缓存
接口2:根据关键字搜索商品信息,使用es搜索引擎
接口3:添加新的商品,通过双写操作同步redis、es
-
整合MyBatis-Plus
- 添加依赖
- 配置数据源
- 启动类添加
@MapperScan
-
配置和es的数据源
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/your_database_name?charactorEncoding=utf-8 username: your_mysql_username password: your_mysql_password redis: host: 192.168.0.1 port: 6379 database: 0 password: your_redis_password elasticsearch: rest: uris: http://192.168.0.1:9200 username: your_elasticsearch_username password: your_elasticsearch_password
-
Spring容器注入ES的客户端对象
@Configuration public class ESConfig { @Value("${spring.elasticsearch.rest.uris}") private String uris; @Value("${spring.elasticsearch.rest.username}") private String username; @Value("${spring.elasticsearch.rest.password}") private String password; @Bean public RestHighLevelClient getRestHighLevelClient(){ System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"+uris); int index1 = uris.indexOf("://"); int index2 = uris.lastIndexOf(":"); String protocol = uris.substring(0, index1); String host = uris.substring(index1+3,index2); String port = uris.substring(index2+1); HttpHost httpHost = new HttpHost(host, Integer.parseInt(port), protocol); RestClientBuilder restClientBuilder = RestClient.builder(httpHost); RestHighLevelClient restHighLevelClient = new RestHighLevelClient(restClientBuilder); return restHighLevelClient; } }
2.2.4 商品添加接口实现
保证redis和数据库数据的一致性:双写
@Override
public ResultVO<Product> saveProduct(Product product) {
try{
int i = productMapper.insert(product);
if(i>0){
String s = objectMapper.writeValueAsString(product);
stringRedisTemplate.boundValueOps("product_" + product.getProductId()).set(s);
}
return new ResultVO<>(0,"添加商品成功",product);
}catch (Exception e){
e.printStackTrace();
}
return new ResultVO<>(1,"添加商品失败",null);
}
2.2 数据双写问题分析
2.2.1 采用双写实现数据同步问题分析
我们采用双写操作——更新数据库的同时更新redis,因为写操作在完成数据库后更新之后再更新redis
问题:如果在更新数据库成功之后和更新redis成功之前,并发的读操作依然会读取到redis中未更新的数据——不能完全保证redis和数据库的强一致性
解决方案:
- 线程隔离(加锁),影响查询性能
- 将双写变为
先删后写
先删后写
2.2.2 采用先删后写实现数据同步问题分析
如果在修改线程删除redis之后写数据库之前,查询线程查询redis并查询数据库同时同步查询的数据到redis,在修改线程写数据库之后就会导致redis缓存数据与数据库数据不一致。
双删操作总结:
使用Redis做查询缓存,数据库更新操作保证Redis一致性:
- 新增:只写数据库
- 修改:删除redis——修改数据库——删除redis
- 删除:删除redis——删除数据库——删除redis
使用ES做搜索引擎,数据库更新操作保存ES一致性:
- 新增:插入数据库——插入ES
- 修改:删除ES——修改数据库——插入ES (可以搜索不到,但是不能搜索到修改之前的数据)
- 删除:删除ES——删除数据库
三、CDC介绍
3.1 CDC概念
CDC,通过CDC工具监听数据库中数据的变化,同步更新到中间件
3.2 CDC实现方式
- 基于查询——通过查询操作对比数据的变化
- 基于binlog日志监听——需要MySQL开启日志监听功能
3.3 常见的CDC框架
-
Canal:https://github.com/alibaba/canal
canal [kə’næl],译意为水道/管道/沟渠,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费
早期阿里巴巴因为杭州和美国双机房部署,存在跨机房同步的业务需求,实现方式主要是基于业务 trigger 获取增量变更。从 2010 年开始,业务逐步尝试数据库日志解析获取增量变更进行同步,由此衍生出了大量的数据库增量订阅和消费业务。 -
Debezium:https://debezium.io/
-
Flink CDC