你以为数据库到ES的数据同步只是简单的复制粘贴?Too young too simple!
前言:为什么需要数据同步?
各位程序员朋友们,想必大家都经历过这样的场景:数据库中存储了海量数据,但查询速度却让人抓狂。这时候Elasticsearch(ES)就闪亮登场了!它强大的全文搜索和分析能力让我们的查询飞起来。
但是问题来了:如何让数据库中的数据乖乖跑到ES里去呢? 这就是今天我们要讨论的重点!
方案一:同步双写 - 直来直去的"老实人"
实现思路
同步双写,顾名思义就是在写数据库的同时,直接同步写ES。简单粗暴,就像个老实人一样直来直去。
java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@Transactional
public void addUser(User user) {
// 1. 写入数据库
userRepository.save(user);
// 2. 同步写入ES
IndexQuery indexQuery = new IndexQueryBuilder()
.withObject(user)
.build();
elasticsearchTemplate.index(indexQuery);
}
}
优缺点分析
优点:
- 实现简单,代码直观
- 强一致性,数据立即同步
- 不需要引入额外中间件
缺点:
- 性能瓶颈:每次写操作都要等两个系统都完成
- 系统耦合度高:数据库和ES强耦合
- 异常处理复杂:需要处理分布式事务问题
💡 适合场景:数据量不大,对一致性要求极高的场景
方案二:异步双写(MQ) - "聪明人的选择"
实现思路
异步双写通过消息队列(MQ)解耦,先写数据库,然后发送消息到MQ,最后由消费者异步写入ES。
优缺点分析
优点:
- 系统解耦,各司其职
- 性能提升,异步处理不阻塞主流程
- 容错性好,MQ有重试机制
缺点:
- 最终一致性,数据同步有延迟
- 需要维护MQ中间件
- 系统复杂度增加
💡 适合场景:大部分业务场景,特别是对性能要求高的场景
实战:使用RabbitMQ实现数据同步
1. 准备消息转换器
首先配置RabbitMQ的消息转换器,确保消息能够正确序列化和反序列化。
java
@Configuration
public class RabbitMQConfig {
@Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
@Bean
public Queue esQueue() {
return new Queue("es.sync.queue", true);
}
@Bean
public Exchange esExchange() {
return new DirectExchange("es.sync.exchange");
}
@Bean
public Binding binding(Queue esQueue, Exchange esExchange) {
return BindingBuilder.bind(esQueue)
.to(esExchange)
.with("es.sync.routingKey")
.noargs();
}
}
2. 服务发送数据到MQ
在业务服务中,完成数据库操作后发送消息到MQ
java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private RabbitTemplate rabbitTemplate;
@Transactional
public void addUser(User user) {
// 1. 写入数据库
User savedUser = userRepository.save(user);
// 2. 发送消息到MQ
rabbitTemplate.convertAndSend(
"es.sync.exchange",
"es.sync.routingKey",
savedUser);
}
}
3. 监听MQ将数据存储到ES
创建消费者监听MQ消息并写入ES
java
@Component
public class EsDataSyncConsumer {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@RabbitListener(queues = "es.sync.queue")
public void handleMessage(User user) {
try {
IndexQuery indexQuery = new IndexQueryBuilder()
.withObject(user)
.build();
elasticsearchTemplate.index(indexQuery);
System.out.println("数据同步ES成功: " + user.getId());
} catch (Exception e) {
System.err.println("数据同步ES失败: " + e.getMessage());
// 这里可以加入重试机制
throw new AmqpRejectAndDontRequeueException(e);
}
}
}
棘手问题:消息顺序消费
问题描述
在某些场景下,消息的顺序很重要。比如:
- 先新增用户,再更新用户信息
- 先创建订单,再取消订单
如果消息乱序消费,会导致ES中的数据状态错误!
解决方案
方案1:单一消费者模式
最简单的方案是只使用一个消费者,确保消息按顺序处理。
java
@Bean
public SimpleMessageListenerContainer messageListenerContainer(
ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer container =
new SimpleMessageListenerContainer(connectionFactory);
container.setQueues(esQueue());
container.setConcurrentConsumers(1); // 关键:只设置一个消费者
container.setMaxConcurrentConsumers(1);
return container;
}
优缺点:实现简单,但性能受限
方案2:按业务ID分片(推荐)
使用RabbitMQ的consistent hash exchange或者按照业务ID进行路由,确保同一业务ID的消息发送到同一个队列。
java
// 发送消息时,根据业务ID决定routing key
public void sendUserMessage(User user) {
String routingKey = "user." + (user.getId() % 10); // 分成10个队列
rabbitTemplate.convertAndSend("user.sync.exchange", routingKey, user);
}
// 配置多个队列,每个队列一个消费者
@Bean
public Queue userQueue0() { return new Queue("user.queue.0", true); }
@Bean
public Queue userQueue1() { return new Queue("user.queue.1", true); }
// ... 配置更多队列
方案3:版本号控制
在消息体中增加版本号,消费者处理时检查版本号,只处理最新版本的消息。
java
@Data
public class UserMessage {
private User user;
private Long version;
private String operationType; // CREATE, UPDATE, DELETE
}
// 消费者处理逻辑
public void handleMessage(UserMessage message) {
Long currentVersion = getCurrentVersionFromES(message.getUser().getId());
if (message.getVersion() > currentVersion) {
// 处理消息
updateES(message.getUser());
}
}
总结对比
方案 | 一致性 | 性能 | 复杂度 | 适用场景 |
---|---|---|---|---|
同步双写 | 强一致性 | 低 | 低 | 数据量小,要求强一致 |
MQ异步 | 最终一致性 | 高 | 中 | 大多数业务场景 |
写在最后
选择合适的数据同步方案就像选择女朋友一样,没有最好的,只有最合适的!
- 如果你业务简单,数据量小,同步双写是你的"贤惠型女友"
- 如果你追求高性能,能接受短暂延迟,MQ异步就是你的"时尚型女友"
无论选择哪种方案,都要记得:没有银弹!根据业务需求做出最适合的选择才是王道。
互动环节:大家在项目中用过哪种数据同步方案?遇到了哪些坑?欢迎在评论区分享你的"血泪史"!
点赞关注不迷路,下一篇我们将深入探讨「ES数据同步之数据一致性保障」,敬请期待!