040 RabbitMq及数据同步02
1.Spring AMQP
(1)简介
Spring有很多不同的项目,其中就有对AMQP的支持:
Spring AMQP的页面:http://spring.io/projects/spring-amqp
注意:Spring-amqp是对AMQP协议的抽象实现,而spring-rabbit 是对协议的具体实现,也是目前的唯一实现。底层使用的就是RabbitMQ。
(2)依赖和配置
添加AMQP的启动器:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
在application.yml
中添加RabbitMQ地址:
spring:
rabbitmq:
host: 127.0.0.1
username: guest
password: guest
virtual-host: /
(3)监听者
在SpringAmqp中,对消息的消费者进行了封装和抽象,一个普通的JavaBean中的普通方法,只要通过简单的注解,就可以成为一个消费者。
@Component
public class Listener { @RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "spring.test.queue", durable = "true"),
exchange = @Exchange(
value = "spring.test.exchange",
ignoreDeclarationExceptions = "true",
type = ExchangeTypes.TOPIC
),
key = {"#.#"}))
public void listen(String msg){
System.out.println("接收到消息:" + msg);
}
}
@Componet
:类上的注解,注册到Spring容器@RabbitListener
:方法上的注解,声明这个方法是一个消费者方法,需要指定下面的属性:bindings
:指定绑定关系,可以有多个。值是@QueueBinding
的数组。@QueueBinding
包含下面属性:value
:这个消费者关联的队列。值是@Queue
,代表一个队列exchange
:队列所绑定的交换机,值是@Exchange
类型key
:队列和交换机绑定的RoutingKey
类似listen这样的方法在一个类中可以写多个,就代表多个消费者。
(4)AmqpTemplate
Spring最擅长的事情就是封装,把他人的框架进行封装和整合。
Spring为AMQP提供了统一的消息处理模板:AmqpTemplate,非常方便的发送消息,其发送方法:
红框圈起来的是比较常用的3个方法,分别是:
指定交换机、RoutingKey和消息体
指定消息
指定RoutingKey和消息,会向默认的交换机发送消息
(5)测试代码
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class MqDemo { @Autowired
private AmqpTemplate amqpTemplate; @Test
public void testSend() throws InterruptedException {
String msg = "hello, Spring boot amqp";
this.amqpTemplate.convertAndSend("spring.test.exchange","a.b", msg);
// 等待10秒后再结束
Thread.sleep(10000);
}
}
运行后查看日志:
2.搜索服务、商品静态页的数据同步
(1)思路分析
<1>发送方:商品微服务
什么时候发?
当商品服务对商品进行写操作:增、删、改的时候,需要发送一条消息,通知其它服务。
发送什么内容?
对商品的增删改时其它服务可能需要新的商品数据,但是如果消息内容中包含全部商品信息,数据量太大,而且并不是每个服务都需要全部的信息。因此我们只发送商品id,其它服务可以根据id查询自己需要的信息。
<2>接收方:搜索微服务、静态页微服务
接收消息后如何处理?
搜索微服务:
增/改:添加新的数据到索引库
删:删除索引库数据
静态页微服务:
增/改:创建新的静态页
删:删除原来的静态页
(2)商品服务发送消息
我们先在商品微服务leyou-item-service
中实现发送消息
<1>引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<2>配置文件
我们在application.yml中添加一些有关RabbitMQ的配置:
spring:
rabbitmq:
host: 127.0.0.1
username: guest
password: guest
virtual-host: /
template:
exchange: leyou.item.exchange
publisher-confirms: true
template:有关
AmqpTemplate
的配置exchange:缺省的交换机名称,此处配置后,发送消息如果不指定交换机就会使用这个
publisher-confirms:生产者确认机制,确保消息会正确发送,如果发送失败会有错误回执,从而触发重试
<3>改造GoodsService
在GoodsService中封装一个发送消息到mq的方法:(需要注入AmqpTemplate模板)
@Autowired
private AmqpTemplate amqpTemplate;
/**
* 利用rabbitmq发送消息
* @param id
* @param type
*/
private void sendMessage(Long id, String type){
// 发送消息
try {
this.amqpTemplate.convertAndSend("item." + type, id);
} catch (Exception e) {
//logger.error("{}商品消息发送异常,商品id:{}", type, id, e);
}
}
这里没有指定交换机,因此默认发送到了配置中的:leyou.item.exchange
注意:这里要把所有异常都try起来,不能让消息的发送影响到正常的业务逻辑
然后在新增的时候调用:
/**
* 新增商品
* @param spuBo
*/
@Override
@Transactional //添加事务
public void saveGoods(SpuBo spuBo) {
// 01 新增spu
// 设置默认字段
spuBo.setId(null);
spuBo.setSaleable(true); //设置是否可售
spuBo.setValid(true);
spuBo.setCreateTime(new Date()); //设置创建时间
spuBo.setLastUpdateTime(spuBo.getCreateTime()); //设置更新时间
this.spuMapper.insertSelective(spuBo); // 02 新增spuDetail
SpuDetail spuDetail = spuBo.getSpuDetail();
spuDetail.setSpuId(spuBo.getId());
this.spuDetailMapper.insertSelective(spuDetail); saveSkuAndStock(spuBo); //发送rabbitmq消息
sendMessage(spuBo.getId(),"insert");
}
修改的时候调用:
/**
* 更新商品
* @param spu
*/
@Override
@Transactional
public void updateGoods(SpuBo spu) {
// 查询以前sku
Sku sku=new Sku();
sku.setSpuId(spu.getId());
List<Sku> skus = this.skuMapper.select(sku); // 如果以前存在,则删除
if(!CollectionUtils.isEmpty(skus)) {
List<Long> ids = skus.stream().map(s -> s.getId()).collect(Collectors.toList());
// 删除以前库存
Example example = new Example(Stock.class);
example.createCriteria().andIn("skuId", ids);
this.stockMapper.deleteByExample(example); // 删除以前的sku
Sku record = new Sku();
record.setSpuId(spu.getId());
this.skuMapper.delete(record); }
// 新增sku和库存
saveSkuAndStock(spu); // 更新spu
spu.setLastUpdateTime(new Date());
spu.setCreateTime(null); //不能更新的内容,设置为null
spu.setValid(null);
spu.setSaleable(null);
this.spuMapper.updateByPrimaryKeySelective(spu); // 更新spu详情
this.spuDetailMapper.updateByPrimaryKeySelective(spu.getSpuDetail()); //发送rabbitmq消息
sendMessage(spu.getId(),"insert");
}
(3)搜索服务接收消息
搜索服务接收到消息后要做的事情:
增:添加新的数据到索引库
删:删除索引库数据
改:修改索引库数据
因为索引库的新增和修改方法是合二为一的,因此我们可以将这两类消息一同处理,删除另外处理。
<1>引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<2>添加配置
spring:
rabbitmq:
host: 127.0.0.1
username: guest
password: guest
virtual-host: /
这里只是接收消息而不发送,所以不用配置template相关内容。
<3>编写监听器
package lucky.leyou.listener; import lucky.leyou.service.SearchService;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; @Component
public class GoodsListener { @Autowired
private SearchService searchService; /**
* 处理insert和update的消息
*
* @param id
* @throws Exception
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "leyou.create.index.queue", durable = "true"),
exchange = @Exchange(
value = "leyou.item.exchange",
ignoreDeclarationExceptions = "true",
type = ExchangeTypes.TOPIC),
key = {"item.insert", "item.update"}))
public void listenCreate(Long id) throws Exception {
if (id == null) {
return;
}
// 创建或更新索引
this.searchService.createIndex(id);
} /**
* 处理delete的消息
*
* @param id
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "leyou.delete.index.queue", durable = "true"),
exchange = @Exchange(
value = "leyou.item.exchange",
ignoreDeclarationExceptions = "true",
type = ExchangeTypes.TOPIC),
key = "item.delete"))
public void listenDelete(Long id) {
if (id == null) {
return;
}
// 删除索引
this.searchService.deleteIndex(id);
}
}
<4>编写创建和删除索引方法
这里因为要创建和删除索引,我们需要在SearchService中拓展两个方法,创建和删除索引:
public void createIndex(Long id) throws IOException { Spu spu = this.goodsClient.querySpuById(id);
// 构建商品
Goods goods = this.buildGoods(spu); // 保存数据到索引库
this.goodsRepository.save(goods);
} public void deleteIndex(Long id) {
this.goodsRepository.deleteById(id);
}
创建索引的方法可以从之前导入数据的测试类中拷贝和改造。
040 RabbitMq及数据同步02的更多相关文章
- 039 RabbitMq及数据同步01
1.RabbitMq (1)问题引出 目前我们已经完成了商品详情和搜索系统的开发.我们思考一下,是否存在问题? 商品的原始数据保存在数据库中,增删改查都在数据库中完成. 搜索服务数据来源是索引库,如果 ...
- RabbitMQ数据同步一致性解决方案
1.概述 我们知道在使用RabbitMQ时,生产者将消息发布出去之后,消息是否顺利到达broker代理服务器呢?默认情况下发布操作没有任何信息返回给生产者,也就是生产者是不知道消息有没有顺利到达bro ...
- C#.NET 大型企业信息化系统集成快速开发平台 4.2 版本 - 大型软件系统客户端数据同步的问题解决
作为一个完整的整体信息化解决方案需要有足够强大的各种功能,这些功能相对独立,又互相依存.当有需要这样的功能时可以随时拿出来用,适当修改一下就可以满足要求.只有这样才能快速开发各种信息化系统,才能满足各 ...
- SqlServer调用外部程序实现数据同步
首先创建两个数据库:SyncA是数据源,SyncB是对SyncA进行同步的数据库. 在SyncA和SyncB中分别创建Source表和Target表,实际业务中,两张表的结构大多不相同. 然后 ...
- Kettle ETL 来进行mysql 数据同步——试验环境搭建(表中无索引,无约束,无外键连接的情况)
今天试验了如何在Kettle的图形界面(Spoon)下面来整合来mysql 数据库中位于不同数据库中的数据表中的数据. 试验用的数据表是customers: 第三方的数据集下载地址是:http://w ...
- OGG "Loading data from file to Replicat"table静态数据同步配置过程
OGG "Loading data from file to Replicat"table静态数据同步配置过程 一个.mgr过程 GGSCI (lei1) 3> view p ...
- Linux实战教学笔记21:Rsync数据同步工具
第二十一节 Rsync数据同步工具 标签(空格分隔): Linux实战教学笔记-陈思齐 ---本教学笔记是本人学习和工作生涯中的摘记整理而成,此为初稿(尚有诸多不完善之处),为原创作品,允许转载,转载 ...
- 搭建中小规模集群之rsync数据同步备份
NFS重要问题 1.有关NFS客户端普通用户写NFS的问题. 1)为什么要普通用户写NFS. 2)exports加all_squash. Rsync介绍 什么是Rsync? Rsync是一款开源的.快 ...
- 转载:MySQL和Redis 数据同步解决方案整理
from: http://blog.csdn.net/langzi7758521/article/details/52611910 最近在做一个Redis箱格信息数据同步到数据库Mysql的功能. 自 ...
随机推荐
- 实验吧——忘记密码了(vim备份文件,临时文件(交换文件))
题目地址:http://ctf5.shiyanbar.com/10/upload/step1.php 前些天突然发现个游戏,于是浪费了好多时间,终于还是忍住了,现在专心学习,从今天开始正式写些学习笔记 ...
- 第二篇Scrum冲刺博客
第二篇Scrum冲刺博客 一.站立式会议 提供当天站立式会议照片一张 二.每个人的工作 成员 已完成工作 明天计划完成的工作 遇到的困难 林剑峰 初步学习小程序的编写.博客园的撰写 初步完成用户界面 ...
- Node.js官方文档:到底什么是阻塞(Blocking)与非阻塞(Non-Blocking)?
译者按: Node.js文档阅读系列之一. 原文: Overview of Blocking vs Non-Blocking 译者: Fundebug 为了保证可读性,本文采用意译而非直译. 这篇博客 ...
- 是否注意过isEmpty 和 isBlank 区别?
isEmpty 和 isBlank 区别 org.apache.commons.lang.StringUtils 类提供了 String 的常用操作,最为常用的判空有如下两种 isEmpty(Stri ...
- Linux操作系统的日志管理之rsyslog实战案例
Linux操作系统的日志管理之rsyslog实战案例 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.日志介绍 1>.什么是日志 历史事件: 时间,地点,人物,事件 日志级 ...
- Springboot测试类之@RunWith注解
@runWith注解作用: --@RunWith就是一个运行器 --@RunWith(JUnit4.class)就是指用JUnit4来运行 --@RunWith(SpringJUnit4ClassRu ...
- NiFi使用总结 一 hive到hive的PutHiveStreaming processor和SelectHiveQL
我说实话,NiFi的坑真的挺多的... 1.PutHiveStreaming processor的使用 具体配置可参考:https://community.hortonworks.com/articl ...
- 终极 Shell——ZSH
https://zhuanlan.zhihu.com/p/19556676 在开始今天的 MacTalk 之前,先问两个问题吧: 1.相对于其他系统,Mac 的主要优势是什么?2.你们平时用哪种 Sh ...
- 2.8/4/6/8mm/12mm焦距的镜头分别能监控多大范围?
2.8/4/6/8mm/12mm焦距的镜头分别能监控多大范围? 相关介绍 一.焦距和监控距离的关系 我司IPC镜头焦距有2.8/4mm/6mm/8mm等多种选择,可以满足室内外各种环境的拍摄需求.IP ...
- Pandas | 07 函数应用
要将自定义或其他库的函数应用于Pandas对象,有三个重要的方法,下面来讨论如何使用这些方法.使用适当的方法取决于函数应用于哪个层面(DataFrame,行或列或元素). 表合理函数应用:pipe() ...