ElasticSearch - 在 微服务项目 中基于 RabbitMQ 实现 ES 和 MySQL 数据异步同步(考点)

目录

一、数据同步

1.1、什么是数据同步

1.2、解决数据同步面临的问题

1.3、解决办法

1.3.1、同步调用

1.3.2、异步通知(推荐)

[1.3.3、监听 binlog](#1.3.3、监听 binlog)

[1.3、基于 RabbitMQ 实现数据同步](#1.3、基于 RabbitMQ 实现数据同步)

1.3.1、需求

[1.3.2、在"酒店搜索服务"中 声明 exchange、queue、routingKey,同时开启监听](#1.3.2、在“酒店搜索服务”中 声明 exchange、queue、routingKey,同时开启监听)

1.3.3、在"酒店管理服务"中发布消息

1.3.4、启动微服务并测试


一、数据同步


1.1、什么是数据同步

我们知道 elasticsearch 的数据是来源于 数据库(比如 mysql). 当我们在写了代码将 mysql 中的数据导入 es 中,那么这次导入之后 mysql 的数据并不会一成不变,将来我们的业务中会有 crud,数据库中的数据就和有新增、修改、删除,那么 mysql 数据一定那发生变化, es 如果不跟着变化,就会出现问题.

比如在一个商城系统中,到了 双11 ,数据库中的商品的价格下降,而 es 还是老价格,那么用户搜索时看到的商品的价格还是没有变化,用户可能就得考虑换软件了~

因此,我们要保证 mysql 数据变化的时候 es 也能跟着同步变化,这就是数据同步.

Ps:实际上不光是 es 存在数据同步问题,凡是涉及到数据库双写的情况,比如 redis 和 mysql,都会存在数据同步问题.

1.2、解决数据同步面临的问题

如果你现在是一个单体式的项目,所有业务都写在一个项目中,那就比较好办,无非就是在及逆行新增、修改、删除业务的时候,同时把 es 也一起更新就 ok.

但是如果我们是一个 微服务 架构的项目上,不同的业务往往会在不同的微服务上,比如 "商品数据管理业务" 和 "商品数据搜索业务" 肯定会在两个不同的微服务上,那么跨微服务的项目就没办法直接操作了.

1.3、解决办法

1.3.1、同步调用

假设我们现在有两个微服务,一个是 酒店数据管理服务,另一个是酒店数据搜索服务. 假设这两个服务之间互相不能访问对方的数据库,也就是说,酒店管理服务只能访问 mysql,而 酒店搜索服务 只能访问 es,这也符合 微服务 里的标准和规范.

这里有一种办法是同步调用,步骤如下:

1.比如用户做新增操作时,首先把数据写到数据库里.

  1. 数据库写完了以后紧接着调用 酒店搜索服务中的 "更新索引库" 的接口.

  2. 更新 es.

最后更新完了 es 就把响应反馈给搜索服务,然后搜索服务才会把把响应反馈给 管理服务,然后再反馈给用户. 这整个过程依次执行,因此也叫同步调用.

缺陷:

**1. 数据耦合,业务耦合:**原本只是写数据库,写完就结束了,然后现在还需要再写完数据库的代码后面再加上 调用 "更新索引库" 接口的代码,而且这调用 这个接口的业务跟我新增业务显然没有关系啊,现在业务耦合在一起,将来必然也会影响性能.

**2. 影响性能(耦合带来的问题):**原本数据库写完了,比如耗时 50ms,但是现在写完数据库,你还得等待后台调用这个接口返回的响应,而这个接口又要等待 es 这里的响应,假如这里也耗时 50ms,那么总的耗时不就是这个三个步骤相加的,达到 100 ms.

**3. 牵一发而动全身(耦合带来的问题):**如果 步骤 2 和 步骤 3 任意一个位置出现了异常,就会导致整个业务也出现崩溃.

同步调用这么多问题,那么就需要考虑别的方案了.

1.3.2、异步通知(推荐)

这里就需要使用到 mq 来实现了,步骤如下:

  1. 当有人做新增操作时,先去写数据库.

  2. 写完之后,不调用任何服务的接口,而是向 mq 发送一个消息,通知一下 其他服务:"我这里数据新增了啊~",整个步骤到这里结束.

那么至于谁来监听这个消息,监听了以后做什么,跟我有关系吗?没关系,这样一来,业务的耦合就解除了; 至于其他服务耗时多少秒,跟我也没关系,我写完数据库,发完消息就结束了,因此性能也提升了;再者,就算其他服务出现异常了,跟我这里也没关系.

缺陷:

  1. 这样一来,比较依赖 mq 消息的可靠性.

  2. 引入新的中间件,实现的复杂度也会有一定的上升.

不过这些缺陷,跟同步调用比起来,算不了什么,因此这也是比较推荐的方案.

1.3.3、监听 binlog

mysql 默认情况下 binlog 时关闭的,一旦开启,那么每次 mysql 在做增删改的时候,都会记录相应的操作到 binlog 中.

那么可以使用类似于 canal 这样的中间件来监听 binlog,一旦发现变化,立马通知对应的微服务,这个时候就知道数据发生变更,就可以进行更新了.

优势:

这种方案他既不给任何中间件发消息,也不去调用任何接口,因此耦合度是最低的.

劣势:

1.开启 binlog,对 mysql 的压力就增加了.

2.引入新的中间件.

1.3、基于 RabbitMQ 实现数据同步

1.3.1、需求

现在有两个微服务:"酒店管理服务" 和 "酒店搜索服务"

用户操作 "酒店管理服务" 进行增删改数据、要求对 "酒店搜索服务" 中的 es 的数据也要完成相同的数据更改操作.

这里使用 MQ 的异步方式实现数据同步.

1.3.2、在"酒店搜索服务"中 声明 exchange、queue、routingKey,同时开启监听

在 "酒店搜索服务" 中去声明一个 exchange,用来接收 增删改 的消息,接着声明两个队列即可,一个队列用来增改(这两用一个队列是因为在 es 中,增改可以使用同一个 DSL 语句实现),另一个用来删除(这里我是以 Bean 的方式注入到容器中了).

java 复制代码
public class MqConstants {

    //主题交换机
    public static final String EXCHANGE_TOPIC = "hotel.topic";
    //增加 or 修改酒店 队列
    public static final String INSERT_QUEUE = "hotel.insert.queue";
    //删除酒店 队列
    public static final String DELETE_QUEUE = "hotel.delete.queue";
    //增加 or 修改酒店 routingKey
    public static final String INSERT_KEY = "hotel.insert.key";
    //删除酒店 routingKey
    public static final String DELETE_KEY = "hotel.delete.key";

}
java 复制代码
@Configuration
public class MqConfig {

    @Bean
    public TopicExchange hotelTopicExchange() {
        return new TopicExchange(MqConstants.EXCHANGE_TOPIC, true, false);
    }

    @Bean
    public Queue hotelInsertQueue() {
        return new Queue(MqConstants.INSERT_QUEUE, true);
    }

    @Bean
    public Queue hotelDeleteQueue() {
        return new Queue(MqConstants.DELETE_QUEUE, true);
    }

    @Bean
    public Binding hotelInsertBinding() {
        return BindingBuilder.bind(hotelInsertQueue()).to(hotelTopicExchange()).with(MqConstants.INSERT_KEY);
    }

    @Bean
    public Binding hotelDeleteBinding() {
        return BindingBuilder.bind(hotelDeleteQueue()).to(hotelTopicExchange()).with(MqConstants.DELETE_KEY);
    }

}

Ps:此类(MqConfig)的包必须与启动类同级,否则声明交换机和队列失败.

最后就可以使用 @RabbitListener 监听 队列 了.

java 复制代码
@Component
public class MqListener {

    @Autowired
    private IHotelService hotelService;

    @RabbitListener(queues = MqConstants.INSERT_QUEUE)
    public void HotelInsertOrUpdateListener(Long id) {
        hotelService.insertHotelById(id);
    }

    @RabbitListener(queues = MqConstants.DELETE_QUEUE)
    public void HotelDeleteListener(Long id) {
        hotelService.deleteHotelById(id);
    }

}

Ps:此类(MyListener,监听者类)上必须要有 @Component 注解(交由给 Spring 来管理),否则声明的交换机和队列无效.

1.3.3、在"酒店管理服务"中发布消息

酒店管理服务中,一旦用户进行酒店的 增删改,就会对数据库信息进行修改,然后将增删改的消息发布到 MQ 中.

Ps:这里不要发送 hotel 整体数据,太大可能会导致占满队列(Mq 是基于内存存储的,因此会设定队列上限),因此这里发送 id 即可. es 这边拿到 id,就可进行相应的增删改.

java 复制代码
    @PostMapping
    public void saveHotel(@RequestBody Hotel hotel){
        // 新增酒店
        hotelService.save(hotel);
        rabbitTemplate.convertAndSend(MqConstants.EXCHANGE_TOPIC, MqConstants.INSERT_KEY, hotel.getId());
    }

    @PutMapping()
    public void updateById(@RequestBody Hotel hotel){
        //修改酒店信息
        if (hotel.getId() == null) {
            throw new InvalidParameterException("id不能为空");
        }
        hotelService.updateById(hotel);
        rabbitTemplate.convertAndSend(MqConstants.EXCHANGE_TOPIC, MqConstants.INSERT_KEY, hotel.getId());
    }

    @DeleteMapping("/{id}")
    public void deleteById(@PathVariable("id") Long id) {
        //删除酒店信息
        hotelService.removeById(id);
        rabbitTemplate.convertAndSend(MqConstants.EXCHANGE_TOPIC, MqConstants.DELETE_KEY, id);
    }

1.3.4、启动微服务并测试

a)在酒店管理页面中,修改 "7天连锁酒店(上海莘庄地铁站店)" 为 "7天连锁酒店(此处正在施工,请谨慎前往)",如下.

b)在酒店搜索页面中,搜索 "施工" 关键词,就可以看到在 "酒店管理服务" 中更新的信息,已经同步到了 "酒店搜索服务" 中.

相关推荐
WeeJot嵌入式21 分钟前
大数据治理:确保数据的可持续性和价值
大数据
晨欣37 分钟前
Elasticsearch和Lucene之间是什么关系?(ChatGPT回答)
elasticsearch·chatgpt·lucene
zmd-zk1 小时前
kafka+zookeeper的搭建
大数据·分布式·zookeeper·中间件·kafka
激流丶1 小时前
【Kafka 实战】如何解决Kafka Topic数量过多带来的性能问题?
java·大数据·kafka·topic
测试界的酸菜鱼2 小时前
Python 大数据展示屏实例
大数据·开发语言·python
时差9532 小时前
【面试题】Hive 查询:如何查找用户连续三天登录的记录
大数据·数据库·hive·sql·面试·database
Mephisto.java2 小时前
【大数据学习 | kafka高级部分】kafka中的选举机制
大数据·学习·kafka
Mephisto.java2 小时前
【大数据学习 | kafka高级部分】kafka的优化参数整理
大数据·sql·oracle·kafka·json·database
道可云2 小时前
道可云人工智能&元宇宙每日资讯|2024国际虚拟现实创新大会将在青岛举办
大数据·人工智能·3d·机器人·ar·vr
成都古河云2 小时前
智慧场馆:安全、节能与智能化管理的未来
大数据·运维·人工智能·安全·智慧城市