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条。