详解SpringCloud微服务技术栈:深入ElasticSearch(3)——数据同步(酒店管理项目)

‍作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
上期文章:详解SpringCloud微服务技术栈:深入ElasticSearch(2)——自动补全、拼音搜索
订阅专栏:微服务技术全家桶
希望文章对你们有所帮助

数据同步,可以说是非常重要的,我看到的很多面经里面就有关于数据同步的问题。
在之前从MySQL中批量导入了酒店数据到ElasticSearch,因此当MySQL数据发生改变时,ElasticSearch也必须跟着改变,这就是ES与MySQL之间的数据同步。而之前在学习Redis的时候,这就是一个很重要的问题,即双写一致性问题。

数据同步

  • 同步方案分析
  • 导入酒店管理项目
  • 声明队列和交换机
  • 发送MQ消息
  • 监听MQ消息
  • 测试同步功能

同步方案分析

如果是一个单体项目,那么MySQL数据怎么懂,ElasticSearch也相应做变化就好了,但是现在我们使用的是微服务架构,酒店管理(MySQL)的业务和搜索酒店(ElasticSearch)的业务很可能都不在同一个微服务下,数据同步就没办法直接实现。有三种实现方案:

方案一:同步调用
详解SpringCloud微服务技术栈:深入ElasticSearch(3)——数据同步(酒店管理项目)_第1张图片
但是这种方式,admin调用了demo,业务耦合度太高了,影响性能。
方案二:异步通知
当有新增的数据时,依旧先写MySQL,写入后是将消息放入到消息队列中,至于谁需要这个消息,就与admin业务没有关系了,有效解除了耦合,是比较推荐的方案,但是这种方案比较依赖MQ的可靠性,实现复杂度会上升。
详解SpringCloud微服务技术栈:深入ElasticSearch(3)——数据同步(酒店管理项目)_第2张图片
方案三:监听binlog
学习MySQL主从同步的时候有接触过,MySQL中的binlog默认关闭,一旦开启,每当MySQL在做修改的时候,都会将相应的操作记录在binlog中,那么就可以使用canal这种中间件来监听binlog,并通知demo做更新。
详解SpringCloud微服务技术栈:深入ElasticSearch(3)——数据同步(酒店管理项目)_第3张图片
耦合度确实很低,但是会对MySQL产生一定的压力。

在这里将会使用方案二。

导入酒店管理项目

之前一直在做hotel-demo,现在要导入hotel-admin来作为酒店管理的微服务,当数据发生增删改的时候,要对ElasticSearch中的数据完成相同操作。
hotel-admin项目从网盘中下载并导入工程:

链接:https://pan.baidu.com/s/19gdxEBRxpAXdaozyw3uJGQ?pwd=rzbg
提取码:rzbg

修改配置后即可启动自行测试。
详解SpringCloud微服务技术栈:深入ElasticSearch(3)——数据同步(酒店管理项目)_第4张图片

声明队列和交换机

之前学习MQ的时候,要发送消息,就需要指定好交换机exchange、队列queue以及RoutingKey。
ES增和改的时候,实际上都是一样的语句(id不存在就新增,id存在就修改),所以在ES操作中,增和改可以算成是一个业务。
因此消息队列只有2个,一个为hotel.insert.queue,一个为hotel.delete.queue,且每一个队列都有一个唯一的RoutingKey,交换机为hotel.topic。

声明队列和交换机一般都是在消费者中进行的。
1、在hotel-demo、hotel-admin中引入依赖:

	
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-amqpartifactId>
    dependency>

2、在hotel-demo和hotel-admin配置中配置一下AMQP的地址:
详解SpringCloud微服务技术栈:深入ElasticSearch(3)——数据同步(酒店管理项目)_第5张图片
3、hotel-demo与hotel-admin中定义一个常量类:

public class MqConstants {
    //交换机
    public final static String HOTEL_EXCHANGE = "hotel.topic";
    //监听新增或修改的队列
    public final static String HOTEL_INSERT_QUEUE = "hotel.insert.queue";
    //监听删除的队列
    public final static String HOTEL_DELETE_QUEUE = "hotel.delete.queue";
    //新增或修改的RoutingKey
    public final static String HOTEL_INSERT_KEY = "hotel.insert";
    //删除的RoutingKey
    public final static String HOTEL_DELETE_KEY = "hotel.delete";
}

4、在hotel-demo中基于bean的方式声明并绑定关系:

@Configuration
public class MqConfig {
    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange(MqConstants.HOTEL_EXCHANGE, true, false);
    }

    @Bean
    public Queue insertQueue(){
        return new Queue(MqConstants.HOTEL_INSERT_QUEUE, true);
    }

    @Bean
    public Queue deleteQueue(){
        return new Queue(MqConstants.HOTEL_DELETE_QUEUE, true);
    }

    @Bean
    public Binding insertQueueBinding() {
        return BindingBuilder.bind(insertQueue()).to(topicExchange()).with(MqConstants.HOTEL_INSERT_KEY);
    }

    @Bean
    public Binding deleteQueueBinding() {
        return BindingBuilder.bind(deleteQueue()).to(topicExchange()).with(MqConstants.HOTEL_DELETE_KEY);
    }
}

发送MQ消息

从hotel-admin中的增删改业务中增加消息发送到MQ的环节。原先这个项目导入的环节,业务逻辑都放在controller里面实现了,这种肯定是不规范的,业务逻辑应该全部放在service层中进行。我新增了Service层:

HotelController:

	@PostMapping
    public void saveHotel(@RequestBody Hotel hotel){
        hotelService.saveHotel(hotel);
    }

    @PutMapping()
    public void updateHotelById(@RequestBody Hotel hotel){
        hotelService.updateHotelById(hotel);
    }

    @DeleteMapping("/{id}")
    public void deleteHotelById(@PathVariable("id") Long id) {
        hotelService.deleteHotelById(id);
    }

IHotelService:

public interface IHotelService extends IService<Hotel> {
    void saveHotel(Hotel hotel);
    void updateHotelById(Hotel hotel);
    void deleteHotelById(Long id);
}

实现类HotelService:

@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Override
    public void saveHotel(Hotel hotel) { //新增酒店并发送到消息队列
        save(hotel);
        rabbitTemplate.convertAndSend(MqConstants.HOTEL_EXCHANGE, MqConstants.HOTEL_INSERT_KEY, hotel.getId());
    }

    @Override
    public void updateHotelById(Hotel hotel) { //修改酒店并发送到消息队列
        if (hotel.getId() == null) {
            throw new InvalidParameterException("id不能为空");
        }
        updateById(hotel);
        rabbitTemplate.convertAndSend(MqConstants.HOTEL_EXCHANGE, MqConstants.HOTEL_INSERT_KEY, hotel.getId());
    }

    @Override
    public void deleteHotelById(Long id) { //删除酒店并发送到消息队列
        removeById(id);
        rabbitTemplate.convertAndSend(MqConstants.HOTEL_EXCHANGE, MqConstants.HOTEL_DELETE_KEY, id);
    }
}

监听MQ消息

消费者hotel-demo需要完成消息的监听并且更新ElasticSearch中的数据,而消息的监听都是通过编写实体类来实现的。

@Component
public class HotelListener {

    @Resource
    IHotelService hotelService;

    /**
     * 监听酒店新增或修改的业务
     * @param id 酒店id
     */
    @RabbitListener(queues = MqConstants.HOTEL_INSERT_QUEUE)
    public void listenHotelInsertOrUpdate(Long id) {
        hotelService.insertById(id);
    }

    /**
     * 监听酒店删除的业务
     * @param id 酒店id
     */
    @RabbitListener(queues = MqConstants.HOTEL_DELETE_QUEUE)
    public void listenHotelDelete(Long id) {
        hotelService.deleteById(id);
    }
}

之前在HotelService中没有实现着两个方法,所以需要在实现类中用RestClient实现:

	@Override
    public void deleteById(Long id) {
        try {
            //准备request
            DeleteRequest request = new DeleteRequest("hotel").id(id.toString());
            //发送请求
            client.delete(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void insertById(Long id) {
        try {
            //根据id查询酒店数据
            Hotel hotel = getById(id);
            //转换为文档类型
            HotelDoc hotelDoc = new HotelDoc(hotel);
            //准备request
            IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
            //准备DSL
            request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
            //发送请求
            client.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

测试同步功能

重启hotel-demo和hotel-admin,查看MQ后台的交换机、消息队列以及他们是否绑定成功:
详解SpringCloud微服务技术栈:深入ElasticSearch(3)——数据同步(酒店管理项目)_第6张图片
在酒店管理系统中修改上海希尔顿酒店的价格为2687,消息队列中存入了消息,且直接被消费者监听和消费掉了:
详解SpringCloud微服务技术栈:深入ElasticSearch(3)——数据同步(酒店管理项目)_第7张图片

详解SpringCloud微服务技术栈:深入ElasticSearch(3)——数据同步(酒店管理项目)_第8张图片
至此,数据同步成功完成,把业务也分离的很好。

你可能感兴趣的:(微服务技术全家桶,spring,cloud,微服务,elasticsearch,RestClient,DSL,java)