1、异步处理
2、应用解耦 比如订单服务
3、流量控制 比如秒杀系统
RabbitMQ操作流程:
Exchange类型:
引入依赖
org.springframework.boot
spring-boot-starter-amqp
配置文件
spring.rabbitmq.host=192.168.80.133
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/
# confirmCallback(\u5F00\u542F\u53D1\u9001\u7AEF\u786E\u8BA4)
spring.rabbitmq.publisher-confirm-type=correlated
# returnCallback(\u5F00\u542F\u53D1\u9001\u7AEF\u6D88\u606F\u62B5\u8FBE\u961F\u5217\u7684\u786E\u8BA4)
spring.rabbitmq.publisher-returns=true
# \u53EA\u8981\u62B5\u8FBE\u961F\u5217\uFF0C\u4EE5\u5F02\u6B65\u53D1\u9001\u4F18\u5148\u56DE\u8C03\u6211\u4EEC\u8FD9\u4E2Areturnfirm
spring.rabbitmq.template.mandatory=true
# \u624B\u52A8ack\u6D88\u606F
spring.rabbitmq.listener.simple.acknowledge-mode=manual
测试发送消息:
import com.xhh.greymall.order.entity.OrderEntity;
import com.xhh.greymall.order.entity.OrderReturnReasonEntity;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Date;
import java.util.UUID;
@Slf4j
@SpringBootTest
class GreymallOrderApplicationTests {
@Autowired
AmqpAdmin amqpAdmin;
@Autowired
RabbitTemplate rabbitTemplate;
@Test
void sendMessageTest() {
// OrderReturnReasonEntity reasonEntity = new OrderReturnReasonEntity();
// reasonEntity.setId(1L);
// reasonEntity.setCreateTime(new Date());
// reasonEntity.setName("哈哈");
//1、发送消息,如果发送的消息是个对象,我们会使用序列化机制,将对象写出去。Serializable
// String msg = "hello world";
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
OrderReturnReasonEntity reasonEntity = new OrderReturnReasonEntity();
reasonEntity.setId(1L);
reasonEntity.setCreateTime(new Date());
reasonEntity.setName("哈哈-" + i);
//2、发送的对象类型
rabbitTemplate.convertAndSend("hello-java-exchange", "hello.java", reasonEntity);
} else {
OrderEntity orderEntity = new OrderEntity();
orderEntity.setOrderSn(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend("hello-java-exchange", "hello.java", orderEntity);
}
log.info("消息发送完成");
}
// //2、发送的对象类型
// rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",reasonEntity);
// log.info("消息发送完成{}",reasonEntity);
}
/**
* String name, boolean durable, boolean autoDelete, Map arguments
*/
@Test
void createExchange() {
//amqpAdmin
//Exchange
DirectExchange directExchange =
new DirectExchange("hello-java-exchange", true, false);
amqpAdmin.declareExchange(directExchange);
log.info("Exchange[{}]创建成功", "hello-java-exchange");
}
/**
* String name, boolean durable, boolean exclusive, boolean autoDelete,
*
* @Nullable Map arguments
*/
@Test
void createQueue() {
Queue queue = new Queue("hello-java-queue", true, false, false);
amqpAdmin.declareQueue(queue);
log.info("Queue[{}]创建成功", "hello-java-queue");
}
/**
* String destination, 【目的地】
* DestinationType destinationType,【目的地类型】
* String exchange, 【交换机】
* String routingKey,【路由键】
*
* @Nullable Map arguments 【自定义参数】
*/
@Test
void createBinding() {
Binding binding = new Binding(
"hello-java-queue",
Binding.DestinationType.QUEUE,
"hello-java-exchange",
"hello.java",
null);
amqpAdmin.declareBinding(binding);
log.info("Binding[{}]创建成功", "hello-java-binding");
}
}
测试接收消息:
/**
* queues:声明需要监听的所有队列
*
* org.springframework.amqp.core.Message
*
* 参数可以写以下类型
* 1、Message message:原生消息详细详细。头+体
* 2、T<发送的消息的类型> OrderReturnReasonEntity content;
* 3、Channel channel:当前传输数据的通道
*
* Queue:可以很多人都来监听。只要收到消息,队列删除消息,而且只能有一个收到此消息
*
* 1、订单服务启动多个:同一个消息,只能有一个客户端能收到
* 2、只有一个消息完全处理完,方法运行结束,我们就可以接收下一个消息
*/
// @RabbitListener(queues = {"hello-java-queue"})
@RabbitHandler
public void receiveMessage(Message message,
OrderReturnReasonEntity content,
Channel channel){
//消息头属性信息
System.out.println("接收到消息" + content);
MessageProperties messageProperties = message.getMessageProperties();
System.out.println("消息处理完成-》" + content.getName());
//channel内顺序自增的
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.println("deliveryTag==>" + deliveryTag);
//签收货物,非批量模式
try {
if(deliveryTag %2 == 0){
//收货
channel.basicAck(deliveryTag,false);
System.out.println("签收了货物..." + deliveryTag);
} else {
//退货 requeue=false 丢弃, requeue=true 发回服务器,服务器重新入队
channel.basicNack(deliveryTag,false,true);
// channel.basicReject();
System.out.println("没有签收了货物..." + deliveryTag);
}
} catch (IOException e) {
//网络中断
e.printStackTrace();
}
}
@RabbitHandler
public void receiveMessage2(OrderEntity content){
//消息头属性信息
System.out.println("接收到消息" + content);
}
1、可靠投递发送端确认:
/**
* 定制RabbitTemplate
* 1、服务收到消息就回调
* 1、spring.rabbitmq.publisher-confirm-type=correlated
* 2、设置确认回调
* 2、消息正确抵达队列进行回调
* 1、spring.rabbitmq.publisher-returns=true
* spring.rabbitmq.template.mandatory=true
* 2、设置确认回调
*
* 3、消费端确认(保证每个消息被正确消费,此时才可以broker删除这个消息)
* 1、默认是自动确认的,只要消息接收到,客户端会自动确认,服务端就会移除这个消息
*
* @PostConstruct:构造器创建完成后执行这个方法
*/
// @PostConstruct
public void initRabbitTemplate(){
//设置确认回调
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
*1、只要消息抵达Broker就ack=true
* @param correlationData 当前消息的唯一关联数据(这个是消息的唯一id)
* @param ack 消息是否成功收到
* @param cause 失败的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
/**
* 1.做好消息确认机制
* 2.每一个发送的消息都在数据库做好记录,定期将失败的消息再次发送
*/
//RabbitMQ服务器收到了
System.out.println("confirm...correlationData[" + correlationData + "]=>ack[" + ack + "]=>cause[" + cause + "]");
}
});
//设置消息抵达队列的确认回调
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
* 只要消息没有投递给指定的队列,就触发这个失败回调
* @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("Fail Message[" + message + "]=>replyCode[" + replyCode + "]=>replyText[" + replyText + "]=>exchange[" + "]=>" + exchange + "]=>routingKey" + routingKey);
}
});
}
可靠投递消费端确认:
消费端确认(保证每个消息被正确消费,此时才可以broker删除这个消息)
* 1、默认是自动确认的,只要消息接收到,客户端会自动确认,服务端就会移除这个消息
* 问题:收到很多消息,自动回复给服务器ack,只有一个消息处理成功然后宕机了发生消息丢失
* 手动确认
修改配置:
代码:
建议采用给队列设置过期时间,因为Rabbit MQ是惰性检查机制
升级版:
参照以上场景:
创建队列和交换机:
//@Bean Binding,Queue,Exchange
/**
* 容器中的Binding,Queue,Exchange都会自动创建(RabbitMQ没有的情况)
* @return
*/
@Bean
public Queue orderDelayQueue(){
Map arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange","order-event-exchange");
arguments.put("x-dead-letter-routing-key","order.release.order");
arguments.put("x-message-ttl",60000);
return new Queue("order.delay.queue", true, false, false,arguments);
}
@Bean
public Queue orderReleaseQueue(){
return new Queue("order.release.order.queue", true, false, false);
}
@Bean
public Exchange orderEventExchange(){
return new TopicExchange("order-event-exchange",true,false);
}
@Bean
public Binding orderCreateOrderBinding(){
return new Binding("order.delay.queue",
Binding.DestinationType.QUEUE,
"order-event-exchange",
"order.create.order",
null);
}
@Bean
public Binding orderReleaseOrderBinding(){
return new Binding("order.release.order.queue",
Binding.DestinationType.QUEUE,
"order-event-exchange",
"order.release.order",
null);
}
测试:
@RabbitListener(queues = "order.release.order.queue")
public void listener(OrderEntity entity,Channel channel,Message message) throws IOException {
System.out.println("收到过期消息,准备关闭订单"+entity.getOrderSn());
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
@ResponseBody
@GetMapping("/test/createOrder")
public String createOrderTest(){
//订单下单成功
OrderEntity entity = new OrderEntity();
entity.setOrderSn(UUID.randomUUID().toString());
entity.setModifyTime(new Date());
// System.out.println(entity);
//给MQ发消息
rabbitTemplate.convertAndSend("order-event-exchange","order.create.order",entity);
return "OK";
}
效果:
在库存服务选编写Rabbit MQ配置:
@Configuration
public class MyRabbitConfig {
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
// @RabbitListener(queues = "stock.release.stock.queue")
// public void handle(Message message){
//
// }
@Bean
public Exchange stockEventExchange(){
return new TopicExchange("stock-event-exchange",true,false);
}
@Bean
public Queue stockReleaseStockQueue(){
return new Queue("stock.release.stock.queue",true,false,false);
}
@Bean
public Queue stockDelayQueue(){
Map arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange","stock-event-exchange");
arguments.put("x-dead-letter-routing-key","stock.release");
arguments.put("x-message-ttl",120000); //2分钟
return new Queue("stock.delay.queue",true,false,false,arguments);
}
@Bean
public Binding stockReleaseBinding(){
return new Binding("stock.release.stock.queue",
Binding.DestinationType.QUEUE,
"stock-event-exchange",
"stock.release.#",
null);
}
@Bean
public Binding stockLockedBinding(){
return new Binding("stock.delay.queue",
Binding.DestinationType.QUEUE,
"stock-event-exchange",
"stock.locked",
null);
}
}
监听库存解锁:
库存锁定:
for (SkuWareHasStock hasStock : collect) {
boolean skuStocked = false;
Long skuId = hasStock.getSkuId();
List wareIds = hasStock.getWareId();
if (wareIds == null || wareIds.size() == 0) {
//没有任何仓库有这个商品的库存
throw new NoStockException(skuId);
}
//1、如果每个商品都锁定成功,将当前商品锁定了几件的工作单记录发送给MQ
//2、如果锁定失败,前面保存的工作的回滚,发送出去的消息,即使要解锁记录,由于去数据库查不到id,所以就不用解锁
//
for (Long wareId : wareIds) {
//成功就返回1,否则为0
Long count = wareSkuDao.lockSkuStock(skuId,wareId,hasStock.getNum());
if(count == 1){
skuStocked = true;
// TODO 告诉MQ库存锁定成功
WareOrderTaskDetailEntity entity =
new WareOrderTaskDetailEntity(null, skuId, null, hasStock.getNum(), taskEntity.getId(), wareId, 1);
wareOrderTaskDetailService.save(entity);
StockLockedTo lockedTo = new StockLockedTo();
lockedTo.setId(taskEntity.getId());
StockDetailTo stockDetailTo = new StockDetailTo();
BeanUtils.copyProperties(entity,stockDetailTo);
//只发id不行,防止回滚以后找不到数据
lockedTo.setDetail(stockDetailTo);
rabbitTemplate.convertAndSend("stock-event-exchange","stock.locked",lockedTo);
break;
} else {
//当前仓库锁失败,重试下一个仓库
}
}
if(skuStocked == false){
//当前商品所有仓库都没有锁住
throw new NoStockException(skuId);
}
}
解锁库存:
@RabbitHandler
public void handleStockLockedRelease(StockLockedTo to, Message message, Channel channel) throws IOException {
System.out.println("收解锁库存的消息");
try {
wareSkuService.unlockStock(to);
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
} catch (Exception e) {
channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
}
}
@Override
public void unlockStock(StockLockedTo to) {
StockDetailTo detail = to.getDetail();
Long detailId = detail.getId();
//解锁
//1、查询数据库关于这个订单的锁定库存信息
//有:证明库存锁定成功
// 解锁:订单情况:1.没有这个订单,必须解锁
// 2.有这个订单。不是解锁库存。
// 订单状态:已取消:解锁库存
// 没取消:不能解锁
//没有:库存锁定失败,库存回滚了,这种情况无需解锁
WareOrderTaskDetailEntity byId = wareOrderTaskDetailService.getById(detailId);
if(byId != null){
//解锁
Long id = to.getId();
WareOrderTaskEntity taskEntity = wareOrderTaskService.getById(id);
String orderSn = taskEntity.getOrderSn();//根据订单号查询订单状态
R r = orderFeignService.getOrderStatus(orderSn);
if(r.getCode() == 0){
//订单数据返回成功
OrderVo data = r.getData(new TypeReference() {
});
if(data == null || data.getStatus() == 4){
//订单不存在,订单已经被取消了,才能解锁库存
//detailId
if(byId.getLockStatus() == 1){
//当前库存工作单详情,状态1 已锁定但是未解锁才可以解锁
unLockStock(detail.getSkuId(),detail.getWareId(),detail.getSkuNum(),detailId);
}
}
} else {
//消息拒绝以后重新放到队列里面,让别人继续消费解锁
throw new RuntimeException("远程服务失败");
}
} else {
//无需解锁
}
}
private void unLockStock(Long skuId, Long wareId, Integer num, Long taskDetailId){
//库存解锁
wareSkuDao.unLockStock(skuId,wareId,num);
//更新库存工作单的状态
WareOrderTaskDetailEntity entity = new WareOrderTaskDetailEntity();
entity.setId(taskDetailId);
entity.setLockStatus(2); //变为已解锁
wareOrderTaskDetailService.updateById(entity);
}
定时关单:
关单监听器:
@RabbitListener(queues = "order.release.order.queue")
@Service
public class OrderCloseListener {
@Autowired
OrderService orderService;
@RabbitHandler
public void listener(OrderEntity entity, Channel channel, Message message) throws IOException {
System.out.println("收到过期的订单消息,准备关闭订单:" + entity.getOrderSn());
try {
orderService.closeOrder(entity);
//手动接消息
//手动调用支付宝收单
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
} catch (Exception e) {
channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
}
}
}
关闭订单:
@RabbitHandler
public void handleOrderCloseRelease(OrderTo orderTo, Message message, Channel channel) throws IOException {
System.out.println("订单关闭,准备解锁库存...");
try {
wareSkuService.unlockStock(orderTo);
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
} catch (Exception e) {
channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
}
}
@Override
public void closeOrder(OrderEntity entity) {
//查询当前这个订单的最新状态
OrderEntity orderEntity = this.getById(entity.getId());
if(orderEntity.getStatus() == OrderStatusEnum.CREATE_NEW.getCode()){
//关单
OrderEntity update = new OrderEntity();
update.setId(entity.getId());
update.setStatus(OrderStatusEnum.CANCEL.getCode());
this.updateById(update);
OrderTo orderTo = new OrderTo();
BeanUtils.copyProperties(orderEntity,orderTo);
//发给MQ一个
try {
//TODO 保证消息一定会发送出去,每一个消息都可以做好日志记录(给数据库保存每个消息的详细消息)
rabbitTemplate.convertAndSend("order-event-exchange","order.release.other",orderTo);
} catch (Exception e) {
//TODO 将没发送的消息进行重试发送。
//while
}
}
}
//防止订单服务卡顿,导致订单状态消息一直改不了,库存消息优先到期,查订单状态新建状态,
//导致卡顿的订单,永远不能解锁库存
@Transactional
@Override
public void unlockStock(OrderTo orderTo) {
String orderSn = orderTo.getOrderSn();
//查一下最新库存的状态,防止重复解锁库存
WareOrderTaskEntity taskEntity = wareOrderTaskService.getOrderTaskByOrderSn(orderSn);
Long id = taskEntity.getId();
//按照工作单找到所有没有解锁的库存,进行解锁
List entities = wareOrderTaskDetailService.list(new QueryWrapper()
.eq("task_id", id)
.eq("lock_status", 1));
for (WareOrderTaskDetailEntity entity : entities) {
unLockStock(entity.getSkuId(),entity.getWareId(),entity.getSkuNum(),entity.getId());
}
}
日志记录可以在数据库创建一张消息表:
然后定期扫描数据库,将没发出去的消息在重新发一次