SpringCloud(十)ES 进阶 -- 数据同步

Demo案例,两个微服务项目,一个操作MySql,一个操作ES

hotel-admin :酒店管理微服务demo,实现对酒店信息的增、删、改(操作MySql)

hotel-demo:ES demo,实现了对索引库、文档的操作,以及高亮显示、搜索自动补全功能(操作ES)

Demo源码下载地址(两个微服务在一起):链接:https://pan.baidu.com/s/1nPTCnLwM2AyH5id8NyyUZg 提取码:wpnh

 

使用注意事项:

1、修改yml中的mysql地址和rabbitmq地址

2、hotel-demo启动类以及测试单元文件中有ES的连接地址,要改成自己的。

 

 

本节实现目标:当酒店数据发生增、删、改时,要求对elasticsearch中的数据也要完成相同操作。

 

数据同步问题分析

同步调用优点:实现简单。 

同步调用缺陷:耦合性太强,影响性能。只要这三步有任意一步出现问题,整个业务就会受到影响。

 

MQ方案优点:为比较推荐的方案,低耦合,实现难度一般。

MQ方案缺点:比较依赖MQ的可靠性。

 

监听binlog方案优点:完全解除了耦合性。

监听binlog方案缺点:需要开启MySQL的binlog。对MySQL压力会很大。还需要引入新的中间件。实现起来很复杂。

 

 

利用MQ实现mysql与elasticsearch数据同步

 

因为在ES中文档的修改操作原理是:校验文档存不存在如果不存在直接新增,如果存在先删除再新增。

所以ES中的新增和修改归属于同一种操作。

因此消息类型只需两种 增 和 删 

 

消费者(hotel-demo 操作ES)

1、hotel-demo 项目引入mq的依赖

<!--amqp-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

2、yml文件中配置mq的地址

  rabbitmq:
    host: 192.168.223.129
    port: 5672
    username: mymq
    password: 123456
    virtual-host: /

 3、声明队列、交换机 。(这里声明成常量)

public class HotelMqConstants {
    public static final String EXCHANGE_NAME = "hotel.topic";
    public static final String INSERT_QUEUE_NAME = "hotel.insert.queue";
    public static final String DELETE_QUEUE_NAME = "hotel.delete.queue";
    public static final String INSERT_KEY = "hotel.insert";
    public static final String DELETE_KEY = "hotel.delete";
}

 4、创建config.MQConfig类文件 创建交换机、消息队列。绑定交换机和消息队列  

注意注解 @Configuration  会将MQConfig变为配置类,将里面的@Bean方法注入到Spring的IOC容器内,这样在启动项目时会自动执行

第4步很重要,如果这步有问题项目会跑不起来,会报找不到交换机和消息队列问题的错误。

@Configuration
public class MQConfig {
    //创建交换机
    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange(HotelMqConstants.EXCHANGE_NAME);
    }
    //创建新增、修改消息队列
    @Bean
    public Queue insertQueue(){
        return new Queue(HotelMqConstants.INSERT_QUEUE_NAME);
    }
    //创建删除消息队列
    @Bean
    public Queue deleteQueue(){
        return new Queue(HotelMqConstants.DELETE_QUEUE_NAME);
    }
    //绑定新增、修改消息队列到交换机
    @Bean
    public Binding insertQueueBinding(){
        return BindingBuilder.bind(insertQueue()).to(topicExchange()).with(HotelMqConstants.INSERT_KEY);
    }
    //绑定删除消息队列到交换机
    @Bean
    public Binding deleteQueueBinding(){
        return BindingBuilder.bind(deleteQueue()).to(topicExchange()).with(HotelMqConstants.DELETE_KEY);
    }

}

5、监听发送者

新建 mq.HotelListener 监听消息队列

@Component
public class HotelListener {

    @Autowired
    private IHotelService hotelService;

    @RabbitListener(queues = HotelMqConstants.INSERT_QUEUE_NAME)
    public void listenHotelInsert(Long hotelId){
        // 新增
        hotelService.saveById(hotelId);
    }

    @RabbitListener(queues = HotelMqConstants.DELETE_QUEUE_NAME)
    public void listenHotelDelete(Long hotelId){
        // 删除
        hotelService.deleteById(hotelId);
    }

}

alt+enter 生成 IService ,实现Service方法

Service新增、修改文档 ,删除文档 参考  SpringCloud(七.3)ES(elasticsearch)-- RestClient操作索引库、文档 中的文档操作部分

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

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Override
    public void deleteById(Long hotelId) {
        try {
            // 1.创建request
            DeleteRequest request = new DeleteRequest("hotel", hotelId.toString());
            // 2.发送请求
            restHighLevelClient.delete(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException("删除酒店数据失败", e);
        }
    }

    @Override
    public void saveById(Long hotelId) {
        try {
            // 查询酒店数据,应该基于Feign远程调用hotel-admin,根据id查询酒店数据(现在直接去数据库查)
            Hotel hotel = getById(hotelId);
            // 转换
            HotelDoc hotelDoc = new HotelDoc(hotel);

            // 1.创建Request
            IndexRequest request = new IndexRequest("hotel").id(hotelId.toString());
            // 2.准备参数
            request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
            // 3.发送请求
            restHighLevelClient.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException("新增酒店数据失败", e);
        }
    }

}

 

 

 

 


发送者(hotel-admin 操作MySql)

1、hotel-admin 项目引入mq的依赖

<!--amqp-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

2、yml文件中配置mq的地址

  rabbitmq:
    host: 192.168.223.129
    port: 5672
    username: mymq
    password: 123456
    virtual-host: /

 3、声明队列、交换机 。(这里声明成常量) ---- 因为发送者消息费定义声明的交换机、消息队列名称要一致,直接复制一份消费者的类文件。

public class HotelMqConstants {
    public static final String EXCHANGE_NAME = "hotel.topic";
    public static final String INSERT_QUEUE_NAME = "hotel.insert.queue";
    public static final String DELETE_QUEUE_NAME = "hotel.delete.queue";
    public static final String INSERT_KEY = "hotel.insert";
    public static final String DELETE_KEY = "hotel.delete";
}

4、消息发送(正常业务操作应该放在Service中进行操作,这里为了方便查看效果在Controller中操作了)

备注:发送的消息在MQ是要保存的,如果发送整个对象是比较消耗内存的,所以这里只发送酒店的ID过去。消费者可以通过ID去查询酒店数据再做同步(增、删、改)操作。

@RestController
@RequestMapping("hotel")
public class HotelController {

    @Autowired
    private IHotelService hotelService;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/{id}")
    public Hotel queryById(@PathVariable("id") Long id){
        return hotelService.getById(id);
    }

    @GetMapping("/list")
    public PageResult hotelList(
            @RequestParam(value = "page", defaultValue = "1") Integer page,
            @RequestParam(value = "size", defaultValue = "1") Integer size
    ){
        Page<Hotel> result = hotelService.page(new Page<>(page, size));

        return new PageResult(result.getTotal(), result.getRecords());
    }

    @PostMapping
    public void saveHotel(@RequestBody Hotel hotel){
        // 新增酒店
        hotelService.save(hotel);
// 发送MQ消息 rabbitTemplate.convertAndSend(HotelMqConstants.EXCHANGE_NAME, HotelMqConstants.INSERT_KEY, hotel.getId()); } @PutMapping() public void updateById(@RequestBody Hotel hotel){ if (hotel.getId() == null) { throw new InvalidParameterException("id不能为空"); } hotelService.updateById(hotel); // 发送MQ消息 rabbitTemplate.convertAndSend(HotelMqConstants.EXCHANGE_NAME, HotelMqConstants.INSERT_KEY, hotel.getId()); } @DeleteMapping("/{id}") public void deleteById(@PathVariable("id") Long id) { hotelService.removeById(id); // 发送MQ消息 rabbitTemplate.convertAndSend(HotelMqConstants.EXCHANGE_NAME, HotelMqConstants.DELETE_KEY, id); } }

 

 

启动测试

1、启动消费者(hotel-demo)

2、启动发送者(hotel-admin)

3、新增、修改、删除测试

注意:这里要先启动消费者(消费者中在Spring的IOC容器中注入了创建交换机、消息队列、绑定关系)

 

启动消费者hotel-demo后,登录mq的管理端  部署mq的服务器或虚拟机:15672(我这里是http://192.168.223.129:15672/ ),登录以后点击交换机Exchanges,会发现交换机hotel.topic已创建,如图:

再来看一眼消息队列 Queues,也已经创建好了。

 接下来启动发送者(hotel-admin),登录 8099 端口(http://localhost:8099/)

我们来做一个修改操作测试一下效果:

我们进到MQ的消息队列中查看一下,发现并没有消息(因为消费者处于启动状态,已经消费掉了),我们通过mq的message可以看出确实是产出过消息,如图:

 

我们去8089(http://localhost:8089/)去看下页面效果,看值是否已改变。

点击速8 查找刚刚修改的数据,可以看到数据已经同步至ES。

 

再来测试一下删除,还是这条数据。

 MQ状态

 

再来8089搜索一下这条速8酒店,可以看到没有那条文档信息了,总条数也由15条变为了14条。

 

posted @ 2024-04-23 17:40  一介桃白白  阅读(109)  评论(0编辑  收藏  举报