1>今天主要讲rabbitmq和SpringBoot的整合 的实际需求
>1.1 框架如何搭建
>1.2 接收方消息丢失怎么处理
---------------------> 我们现在模拟这么一个场景
我们下订单之后吧消息扔进MQ中 然后库存减1 这个操作
发送方: order_service
1.pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
2.config
@Configuration
public class RabbitMQConfig {
@Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory conn = new CachingConnectionFactory("192.168.1.6");
conn.setUsername("kavito");
conn.setPassword("123456");
conn.setVirtualHost("/kavito");//虚拟地址
return conn;
}
@Bean
public RabbitTemplate rabbitTemplate() {
//我们通过rabbitTemplate来操作 rabbit
//也就是我们调用rabbitTemplete的api来操作 rabbit
// 类似于redis 我们不是用redisTemplate 操作redis么
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory());
return rabbitTemplate;
}
//Exchange
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange("test_fanoutExchange");
}
//声明一个queue
@Bean
public Queue queue() {
// 这里Queue有很多参数 比方说是否持久化 为了简单,我这里就没写
return new Queue("simple_queue");
}
//将交换机和queue进行绑定
@Bean
Binding binding() {
return BindingBuilder.bind(queue()).to(fanoutExchange());
}
}
3.controller —>此处模拟下订单时候讲消息写进mq
@RestController
public class OrderController {
@Autowired
RabbitMessageSend rabbitMessageSend;
@RequestMapping("order")
public Object order() {
//下单的时候调用库存
rabbitMessageSend.sendMessage();
return "下单成功";
}
}
- RabbitMessageSend// 发送消息
@Component
public class RabbitMessageSend {
@Autowired
RabbitTemplate rabbitTemplate;
public void sendMessage() {
// 这个参数和消息的id绑定 标识该消息是唯一的
CorrelationData correlationData = new CorrelationData("消息的id");
// 交换机名 消息键 消息内容 此处可以传过来 我就写个简单的 消息id
rabbitTemplate.convertAndSend("test_fanoutExchange","","hellow", correlationData);
}
}
其中交换机类型使用的是 FanoutExchange
2.声明 queue -->simple_queue 并且此处绑定这个交换机了 这种交换机不需要声明消息键
-------------->
go;
此处我端口设置的8000
我们看下Mq中有没有消息存入
此时队列中有消息的并且他已经说明了 我们的消息由test_fanoutExchange 这个交换机发送过来的
------------------------------->呢么我们的接收方 也就是我们的库存怎么写
库存 收到 消息就会减库存
>1.1 pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
1.2
Config配置
@Configuration
public class RabbitMQConfig {
@Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory conn = new CachingConnectionFactory("192.168.1.6");
conn.setUsername("kavito");
conn.setPassword("123456");
conn.setVirtualHost("/kavito");//虚拟地址
return conn;
}
@Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory());
return rabbitTemplate;
}
}
此时库存作为监听mq的消息 监听的是queue
@RestController
public class Listener {
@RabbitListener(queues = "simple_queue")
public void get (Message message) throws Exception {
System.out.println(new String(message.getBody(),"utf-8"));
}
}
-------->go 我们看看效果吧
此时就会出现 下订单之后--------->mq-------->库存减1 的操作
=====================================================>
自然讲此处也可以用这种交换机 声明 消息键 我这里用的简单
//Exchange
@Bean
public DirectExchange directExchange() {
return new DirectExchange("test_DirectExchange");
}
//声明一个queue
@Bean
public Queue queue() {
// 这里Queue有很多参数 比方说是否持久化 为了简单,我这里就没写
return new Queue("direct_simple_queue");
}
//将交换机和queue进行绑定
@Bean
Binding binding() {
return BindingBuilder.bind(queue()).to(directExchange()).with("a");
}
---------------------------------->
好的 问题来了
我们现在的逻辑是
订单系统------->mq------------>库存
但是 如果用户成功下单 ,而库存没有收到消息怎么办
比如说消息路由失败,这就会造成消息丢失的情况
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200425201438420.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzY4OTk1Mw==,size_16,color_FFFFFF,t_70)
我们知道我们生产者的消息首先是进入交换机 的 再让交换机路由到属于那个队列当中
如果我们能保证这一路畅通 是不是对于生产者这里就能吧消息 发出去
如果生产者发送的消息没有进入交换机中 或者说从交换机中没有成功路由到队列这种 就算消息发送失败了 可以这样理解吧
针对消息发送,主要有两类问题
1.消息未能发送到交换机
2.消息未能路由到队列
对应着2种情况 ,我们也有2种机制
1.消息未能发送到交换机,主要通过发送方确认回调
2.消息未能路由到队列,主要是通过返回失败回调,绑定备用交换机[此处不是死信交换机]
Coding
1发送方确认回调代码
1.1> 开启发送方确认
1.2>
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
//CorrelationData是业务id的封装类,可以在客户端发送消息时,携带这个信息(可以在发送时,增加这个参数,参加RabbitmqMessageSend),当这里出现问题时,找到这笔消息 简单点就是消息的唯一标识
System.out.println(correlationData);//消息的唯一标识
System.out.println(ack);// 消息是否到达了exchange
System.out.println(cause);//失败原因
}
});
自然讲一般是不会失败的
2.失败回调------------------------>也就是说此时消息已经到达交换机了 在交换机路由的时候失败,比方说 路由键不存在
也是在RabbitTemplate定义时设置
此时我们模拟一下 路由失败的场景
讲解下我们此时声明一个DirectExchange类型的交换机 ,并且吧他和
direct_queue进行绑定 ,消息键为test
1> 声明交换机
//Exchange
@Bean
public DirectExchange directExchange() {
return new DirectExchange("test_directExchange");
}
//声明一个queue
@Bean
public Queue queue() {
// 这里Queue有很多参数 比方说是否持久化 为了简单,我这里就没写
return new Queue("direct_queue");
}
//将交换机和queue进行绑定
@Bean
Binding binding() {
return BindingBuilder.bind(queue()).to(directExchange()).with("test");
}
- 但是此时 我们发送消息的时候路由键是test11,此时就路由不到对应的queue种
3.同样失败回调的代码也在这里设置
rabbitTemplate.setMandatory(true);//开启失败回调机制
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
//message:发送的消息 “hello”+ 消息的配置(消息过期时间、排序权重 rpc contentType contentEncoding)
System.out.println(message);
//返回码
System.out.println(replyCode);
//返回信息
System.out.println(replyText);
//交换机
System.out.println(exchange);
//路由键
System.out.println(routingKey);
}
});
go------->
自然讲成功就路由过去了 当路由失败的时候他会给我们讲明白 这个消息的路由键是什么以及从那个交换机发过来的 还有失败原因的信息
----------------------------->
总结一下 如何确定我这个消息有没有发送到rabbitmq中
1>发送方确认
2.失败回调
--------------------------------------------------------------------------------------->
--------------------------------------------------------------------------------------->
2.今天想说第二点就是
我们每次吧消息发送到mq中是一个string字符串,如果我们想要发送一个map或者说是一个实体对象的话,参照大佬的博客说明
https://blog.csdn.net/east123321/article/details/78900791
我们也可以用fastjson 下的 JSON.toJSONBytes(map) 将map或者实体类转成byte数组进行传输
HashMap<Object, Object> map = new HashMap<>();
map.put("username","1");
map.put("password","2");
rabbitTemplate.convertAndSend("test_directExchange","test",JSON.toJSONBytes(map), correlationData);
我们也可以配置一个消息转换器 来指定我们发送消息的格式 我们在这个消息转换器中定义我们要发送消息内容的格式什么的
rabbitTemplate.setMessageConverter(new MessageConverter() {
//此时对于发送方 必然是toMessage啊
@Override
public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
return new Message( JSON.toJSONBytes(object),messageProperties);
}
@Override
public Object fromMessage(Message message) throws MessageConversionException {
return null;
}
});
--------------------------------------------------------------------------------------->
--------------------------------------------------------------------------------------->
3.说下备用交换机
呢么何为备用交换机 当我们的消息路由失败的时候,会启用备用交换机
备份交换器是为了实现没有路由到队列的消息,声明交换机的时候添加属性alternate-exchange,声明一个备用交换机,一般声明为fanout类型,这样交换机收到路由不到队列的消息就会发送到备用交换机绑定的队列中。
大致是这样一个逻辑 ,我们可以测试下当我们一个交换机声明
alternate-exchange 的时候 在rabbitmq的管理页面也可以看到的
@Bean
public DirectExchange directExchange() {
HashMap<String, Object> map = new HashMap<>();
map.put("alternate-exchange","test_fanoutExchange");
return new DirectExchange("test_directExchange",true,false,map);
}
此时我们可以吧路由键改了 试试
rabbitTemplate.convertAndSend("test_directExchange","test11", "aa", correlationData);
1.>备用交换机需要设置持久化,要不然服务器一关闭就没有了
2.>备用交换机一般为fanout类型
3.这些参数不用记
看啊管理页面不都有么