消息队列
标题三、消息队列工具 RabbitMQ
4.1.2 发送确认
有时,业务处理成功,消息也发了,但是我们并不知道消息是否成功到达了rabbitmq,如果由于网络等原因导致业务成功而消息发送失败,那么发送方将出现不一致的问题,此时可以使用rabbitmq的发送确认功能,即要求rabbitmq显式告知我们消息是否已成功发送。
4.1.3 手动消费确认
有时,消息被正确投递到消费方,但是消费方处理失败,那么便会出现消费方的不一致问题。比如:订单已创建的消息发送到用户积分子系统中用于增加用户积分,但是积分消费方处理却都失败了,用户就会问:我购买了东西为什么积分并没有增加呢?
要解决这个问题,需要引入消费方确认,即只有消息被成功处理之后才告知rabbitmq以ack,否则告知rabbitmq以nack
4.2.4 封装发送消息确认
步骤
1 MQProducerAckConfig 实现两个接口 RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback
2 public void init() 初始化两个 一个是消息是否正确到达 Exchange 另一个是消息没有正确到达队列时触发回调
package com.atguigu.gmall.common.config;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* 封装发送端消息确认
*/
@Component
public class MQProducerAckConfig implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback {
// 发送消息: RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
// 写一个方法
// 修饰一个非静态的void()方法,在服务器加载Servlet的时候运行,
// 并且只会被服务器执行一次在构造函数之后执行,init()方法之前执行。
@PostConstruct
public void init(){
// 只确认消息是否正确到达 Exchange 中
rabbitTemplate.setConfirmCallback(this);
// 消息没有正确到达队列时触发回调,如果正确到达队列不执行
rabbitTemplate.setReturnCallback(this);
}
/**
* <p>
* 1. 如果消息没有到exchange,则confirm回调,ack=false
* 2. 如果消息到达exchange,则confirm回调,ack=true
* 3. exchange到queue成功,则不回调return
* 4. exchange到queue失败,则回调return
*
*
*/
/**
* 消息成功发送到交换机上
* @param correlationData 数据载体带有Id标识的!
* @param ack 消息是否发送成功
* @param cause 消息发送失败的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if(ack){
// 消息成功发送到了交换机
System.out.println("消息发送成功!");
}else {
System.out.println("消息发送异常");
}
}
/**
* 表示消息如果没有成功发送到队列则会执行当前这个方法!
* @param message 消息主体
* @param replyCode 应答码
* @param replyText 描述
* @param exchange 消息使用的交换器
* @param routingKey 消息使用的路由键
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("消息主体: " + new String(message.getBody()));
System.out.println("应答码: " + replyCode);
System.out.println("描述:" + replyText);
System.out.println("消息使用的交换器 exchange : " + exchange);
System.out.println("消息使用的路由键 routing : " + routingKey);
}
}
4.2.5 封装消息发送工具类 供调用
package com.atguigu.gmall.common.service;
@Service
public class RabbitService {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 发送消息
* @param exchange 交换机
* @param routingKey 路由键
* @param message 消息
*/
public boolean sendMessage(String exchange, String routingKey, Object message) {
rabbitTemplate.convertAndSend(exchange, routingKey, message);
return true;
}
}
4.2.6 测试 发送消息 确认消息
消息发送端
package com.atguigu.gmall.mq.controller;
@RestController
@RequestMapping("/mq")
@Slf4j
public class MqController {
@Autowired
private RabbitService rabbitService;
/**
* 消息发送
*/
//http://cart.gmall.com/8282/mq/sendConfirm
@GetMapping("sendConfirm")
public Result sendConfirm() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
rabbitService.sendMessage("exchange.confirm", "routing.confirm", sdf.format(new Date()));
return Result.ok();
}
}
消息接收端
package com.atguigu.gmall.mq.receiver;
@Component
@Configuration
public class ConfirmReceiver {
@SneakyThrows
@RabbitListener(bindings=@QueueBinding(
value = @Queue(value = "queue.confirm",autoDelete = "false"),
exchange = @Exchange(value = "exchange.confirm",autoDelete = "true"),
key = {
"routing.confirm"}))
public void process(Message message, Channel channel){
System.out.println("RabbitListener:"+new String(message.getBody()));
// 采用手动应答模式, 手动确认应答更为安全稳定
//如果手动确定了,再出异常,mq不会通知;如果没有手动确认,抛异常mq会一直通知
//channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
// false 确认一个消息,true 批量确认
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
}
4.3改造商品搜索上下架
4.3.1 定义商品上下架常量
在rabbit-util模块中导入常量类MqConst。
/**
* 商品上下架
*/
public static final String EXCHANGE_DIRECT_GOODS = "exchange.direct.goods";
public static final String ROUTING_GOODS_UPPER = "goods.upper";
public static final String ROUTING_GOODS_LOWER = "goods.lower";
//队列
public static final String QUEUE_GOODS_UPPER = "queue.goods.upper";
public static final String QUEUE_GOODS_LOWER = "queue.goods.lower";
4.3.3 service-product发送消息
我在商品上架与商品添加时发送消息 就是更新Good 不止更新skuInfoUp.setIsSale(1);
商品上架
原来的