社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Elasticsearch

数据库+Elasticsearch同步到底怎么搞?别被双写坑了!

鸭哥聊Java • 3 周前 • 29 次点击  

面试的时候,最怕的不是问你不会的,而是你以为自己会的。

就比如下面这个场景,我敢打赌百分之八十的 Java 后端都掉进去过:

面试官:你项目里用了 Elasticsearch,是怎么同步数据的?
你:我们在写数据库的时候,同时也写了 ES。
面试官:那你怎么保证两个数据源的一致性?比如数据库写成功了,但 ES 写失败了呢?
你:……我还是回去等通知吧。

听起来是不是有点熟悉?别急着笑别人,也可能就是你。

其实,像这种多数据源同步的问题,在业务复杂一点的系统里几乎是标配,双写,是最容易上手的做法,也最容易出问题的做法。

今天咱就唠唠:为啥系统里不推荐双写?

双写,听起来很美,实际上很坑

先说说大家最常见的场景。

一开始,业务简单,只有一个数据库,CRUD 香得不行,部署个 MySQL,插个 Spring Boot,一切都风平浪静。

结果业务做大了——

  • 数据库压力飙升,怎么办?加个 Redis 缓缓存吧。
  • 搜索功能用户抱怨慢?加个 Elasticsearch 做全文检索吧。
  • 老板说要 BI 报表?那搞个数据中台,用 Hadoop、Hive 跑个批处理分析吧。

于是,数据开始被写到多个地方。数据库、缓存、ES、数据仓库……就像你谈了四个对象,长得都不一样,但她们都说你是唯一。

你想了一招简单粗暴的同步方式:

public void saveProduct(Product product) {
    database.save(product);
    elasticsearch.save(product);
}

完美!数据库有了,ES也有了!

可问题也来了。

为什么双写是个坑?

因为它看起来同步,其实不同步

比如你代码执行这两行:

database.save(product); // 成功了
elasticsearch.save(product); // 报错了

恭喜你,你的数据库和 ES 不一致了!

再比如两个请求并发执行:

  • A 请求把商品库存改成了 5
  • B 请求把商品库存改成了 10

两个请求都成功执行了数据库和 ES 的双写,但执行顺序不一致,就有可能一个数据源是 5,另一个是 10。

这不叫同步,这叫事故前兆。

问题主要集中在两块:

1. 一致性问题

数据要是不同步,光靠用户的血汗眼睛是看不出来的。比如用户查的是缓存,缓存没更新,就会看到旧数据;ES 没同步成功,搜索结果查不到最新商品。

这些 bug 特别隐蔽,因为它不会直接爆炸,而是偷偷摸摸地喂你一口屎。

2. 原子性问题

你不能保证两步操作是一个原子动作。

你总不能指望写入数据库和写入 ES 像手牵手一样成功,要么一起成功,要么一起失败。数据库事务确实能 roll back,但人家 ES 不吃你这套。

除非你能把所有系统放进一个分布式事务框架里,什么 XA 协议,什么 TCC 模式,听起来高级,真用了你就知道坑有多深。

而且,ES 和 Redis 这种系统,本身就不是为强一致设计的。

所以问题来了,那该怎么办?总不能放弃同步吧?

双写不好,单写也不行,那就来点巧的

真正靠谱的做法,是走异步解耦 + 顺序消费 + 最终一致性这条路。

说人话就是——你先把数据写到数据库,然后把“写了这条数据”的这个动作,发到一个消息队列里,其他系统(比如 ES、Redis、数据仓库)通过订阅消息队列来被动同步数据。

比如,Kafka、RocketMQ、RabbitMQ 就是干这活的。

架构图就变成了这样:

Client -> Database
       -> Kafka -> ES
                -> Redis
                -> Hadoop

你可能会问:那我写数据库成功了,但消息发 Kafka 失败了呢?

可以这么处理:

  • 把消息发送操作也包在事务里,用本地消息表的思路
  • 或者写库成功后,把变更操作记录到一个“数据变更日志表”,异步定时推送到 Kafka

很多大厂用的就是这种套路。

比如你 insert 了一条产品数据,写入数据库成功后,插入一条变更记录到变更表,内容大概是:

{
  "eventType""INSERT",
  "table""product",
  "data": {
    "id"123,
    "name""MacBook Air",
    "price"8999
  },
  "timestamp""2025-04-18 10:00:00"
}

然后异步服务把这条变更推送到 Kafka,ES 消费这个消息,根据内容更新自己的索引。

这样子,所有数据源的更新都是按顺序来的,也就不会乱。

当然,为了保险起见,每个系统消费 Kafka 的时候还得记录消费位点(offset),如果消费失败了,就从上次失败的地方继续重试。

实战中,我们该怎么做?

别光说理论,咱实打实来点代码感受下。

数据库写入 + 记录变更日志

@Transactional
public void saveProduct(Product product) {
    productRepository.save(product);
    
    ChangeLog log = new ChangeLog();
    log.setEventType("INSERT");
    log.setTableName("product");
    log.setData(JSON.toJSONString(product));
    log.setTimestamp(LocalDateTime.now());

    changeLogRepository.save(log);
}

异步任务:读取变更日志,发消息

@Scheduled(fixedDelay = 1000)
public void pushChangeLog() {
    List logs = changeLogRepository.findUnsentLogs();
    for (ChangeLog log : logs) {
        kafkaTemplate.send("product-changes", log.getData());
        log.setSent(true);
        changeLogRepository.save(log);
    }
}

消费端(ES服务):接收消息,同步数据

@KafkaListener(topics = "product-changes")
public void onMessage(String data) {
    Product product = JSON.parseObject(data, Product.class);
    elasticsearchService.save(product);
}

当然啦,实际系统中你还需要:

  • 消息幂等性处理(重复消费问题)
  • 死信队列(处理消费失败的消息)
  • 灰度开关(控制是否启用同步)
  • 分库分表、数据脱敏等……

但核心逻辑就是这三个字:异步化

用 Canal 做被动监听

如果你不想自己写日志表,还可以用成熟的中间件。

比如 MySQL + Canal,就是典型的 binlog 监听方式。

Canal 能监听数据库的 binlog 日志,把变更数据实时推送到 Kafka,你啥都不用管,ES、Redis 这些服务直接监听 Kafka 消息就能同步数据。

配个图感受一下:

MySQL -> Binlog -> Canal -> Kafka -> Redis/ES/Hadoop

这套东西就跟“监听朋友圈动态”的逻辑一样,别人发了,你自动看到,系统自动同步,省心又安心。

写在最后

面试这种事,说穿了就一个字:稳。

你要说自己系统用了双写,面试官一问原子性和一致性问题,你就露馅了;但如果你能聊到:

  • 用 Kafka 异步解耦
  • 通过变更日志或者 Canal 推送变更消息
  • 如何处理幂等、失败重试、最终一致性

那这个点,立刻就从坑变成了加分项。

你看技术,不光是要能写,还得能解释出个所以然来。

最后,我为大家打造了一份deepseek的入门到精通教程,完全免费:https://www.songshuhezi.com/deepseek


同时,也可以看我写的这篇文章《DeepSeek满血复活,直接起飞!》来进行本地搭建。

对编程、职场感兴趣的同学,可以链接我,微信:yagebug  拉你进入“程序员交流群”。
🔥鸭哥私藏精品 热门推荐🔥

鸭哥作为一名老码农,整理了全网最全《Java高级架构师资料合集》
资料包含了《IDEA视频教程》《最全Java面试题库》、最全项目实战源码及视频》及《毕业设计系统源码》总量高达 650GB 。全部免费领取! 全面满足各个阶段程序员的学习需求。

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/181320
 
29 次点击