一、集成RabbitMQ
1.1 引入依赖
<!-- RabbitMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
1.2 配置参数
spring:
rabbitmq:
host: localhost
port: 5672
username: admin
password: admin
##配置消息发送确认机制
publisher-confirm-type: correlated
#开启手动ack
listener:
simple:
acknowledge-mode: manual
二、创建队列与交换机
2.1架构逻辑图
此例中我们创建如下图所示交换机与队列,消息投递后路由到MessageQueue与PushQueue两个队列,消费失败则投递到ORDER_DEAD_LETTER_EXCHANGE死信交换机。
1、创建MessageQueue与PushQueue两个队列,并将其绑定到Fanout Exchange交换机
2、消息发送者将消息发送给Fanout Exchange,投递到MessageQueue与PushQueue队列
3、监听者监听MessageQueue与PushQueue队列,正常消费消息
4、若消息消费失败,MessageQueue与PushQueue中消息将会投递到绑定的ORDER_DEAD_LETTER_EXCHANGE(死信交换机),路由键为编码设置的YD
5、死信交换机将消息路由到死信队列中
2.2代码实现
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* rabbitMQ队列初始化,若不存在则创建并绑定交换机.
*
* @author liuruojing
* @date 2022/2/7 上午9:26
* @since v1.0
*/
@Configuration
public class RabbitMQConfig {
//短信推送队列,并绑定死信交换机
@Bean
public Queue MessageQueue() {
Map<String, Object> args = new HashMap<>(3);
//声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange", "ORDER_DEAD_LETTER_EXCHANGE");
//声明当前队列的死信路由 key
args.put("x-dead-letter-routing-key", "YD");
return QueueBuilder.durable("order.message").withArguments(args).build();
}
//app推送队列,,并绑定死信交换机
@Bean
public Queue PushQueue() {
Map<String, Object> args = new HashMap<>(3);
//声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange", "ORDER_DEAD_LETTER_EXCHANGE");
//声明当前队列的死信路由 key
args.put("x-dead-letter-routing-key", "YD");
return QueueBuilder.durable("order.push").withArguments(args).build();
}
/**
* 声明死信队列.
*
* @param
* @return
* @auther liuruojing
* @date 2022/2/7 上午10:50
* @sice v1.0
*/
@Bean
public Queue DeathQueue() {
return new Queue("message.death");
}
/**
* 声明一个扇形交换机.
*
* @param
* @return
* @auther liuruojing
* @date 2022/2/7 上午11:42
* @sice v1.0
*/
@Bean
FanoutExchange fanoutExchange() {
return new FanoutExchange("ORDER_EXCHANGE");
}
/**
* 声明一个死信交换机.
*
* @param
* @return
* @auther liuruojing
* @date 2022/2/7 上午10:46
* @sice v1.0
*/
@Bean
DirectExchange deathExchange() {
return new DirectExchange("ORDER_DEAD_LETTER_EXCHANGE");
}
/**
* 绑定MessageQueue到扇形交换机.
*
* @param
* @return
* @auther liuruojing
* @date 2022/2/7 下午2:38
* @sice v1.0
*/
@Bean
Binding bindingExchange(Queue MessageQueue, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(MessageQueue).to(fanoutExchange);
}
/**
* 绑定PushQueue到扇形交换机.
*
* @param
* @return
* @auther liuruojing
* @date 2022/2/7 下午2:38
* @sice v1.0
*/
@Bean
Binding bindingExchangeB(Queue PushQueue, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(PushQueue).to(fanoutExchange);
}
/**
* 绑定DeathQueue到死信交换机,并指定路由key为YD.
*
* @param
* @return
* @auther liuruojing
* @date 2022/2/7 上午11:51
* @sice v1.0
*/
@Bean
public Binding deadLetterBindingQAD(Queue DeathQueue,
DirectExchange deathExchange) {
return BindingBuilder.bind(DeathQueue).to(deathExchange).with("YD");
}
}
三、实现可靠消息发送
1)新建消息发送记录表
-- ----------------------------
-- Table structure for t_message_push_statu
-- ----------------------------
DROP TABLE IF EXISTS `t_message_push_statu`;
CREATE TABLE `t_message_push_statu` (
`message_id` bigint(20) NOT NULL COMMENT '消息id',
`message_context` text COLLATE utf8mb4_croatian_ci COMMENT '消息推送内容',
`time` datetime DEFAULT NULL COMMENT '首次推送时间',
`count` int(11) DEFAULT '0' COMMENT '推送次数',
`statu` tinyint(255) DEFAULT '0' COMMENT '推送状态:0-等待回执;1-推送成功;-1-推送失败',
`reason` text COLLATE utf8mb4_croatian_ci COMMENT '推送失败原因,当statu=-1时有效',
PRIMARY KEY (`message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_croatian_ci;
SET FOREIGN_KEY_CHECKS = 1;
2)代码实现消息推送
消息推送失败记录入库,后续业务逻辑视情况用定时任务自定义处理
/**
* 实现可靠消息发送.
*
* @author liuruojing
* @date 2022/1/14 下午7:58
* @since v1.0
*/
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private OrderEntityMapper orderEntityMapper;
@Autowired
private MessagePushStatuEntityMapper messagePushStatuEntityMapper;
/**
* 前置方法,初始化设置消息发送回调方法.
*
* @param
* @return
* @auther liuruojing
* @date 2022/1/19 下午4:34
* @sice v1.0
*/
@PostConstruct
private void postConstruct() {
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
log.info("CorrelationData:{},ack:{},cause:{}", correlationData, ack, cause);
if (ack) {
log.info("MQ消息发布成功:" + correlationData.getId());
//更新状态
MessagePushStatuEntity mp=new MessagePushStatuEntity();
mp.setMessageId(Long.valueOf(correlationData.getId()));
mp.setStatu((byte) 1);
messagePushStatuEntityMapper.updateByPrimaryKey(mp);
} else {
log.info("MQ消息发布失败,存储至数据库,等待定时任务重新发起或shell脚本定时统计告警。。。");
MessagePushStatuEntityWithBLOBs mp=messagePushStatuEntityMapper.selectByPrimaryKey(Long.valueOf(correlationData.getId()));
mp.setStatu((byte) -1);
mp.setCount(mp.getCount()+1);
mp.setReason(cause);
messagePushStatuEntityMapper.updateByPrimaryKeyWithBLOBs(mp);
}
}
});
}
@Override
@Transactional
public void Order(OrderEntity order) {
//订单入库
int i=orderEntityMapper.insert(order);
if(i>0){
MessageContext ms = new MessageContext<OrderEntity>(order);
MessagePushStatuEntityWithBLOBs messagePushStatuEntityWithBLOBs=new MessagePushStatuEntityWithBLOBs();
messagePushStatuEntityWithBLOBs.setMessageId(ms.getMessageId());
messagePushStatuEntityWithBLOBs.setMessageContext(JSON.toJSONString(ms));
//消息初始化入库
messagePushStatuEntityMapper.insert(messagePushStatuEntityWithBLOBs);
//发送消息
rabbitTemplate.convertAndSend("ORDER_EXCHANGE", null, JSON.toJSONString(ms), new CorrelationData(ms.getMessageId().toString()));
log.info("[x] Sent " + JSON.toJSONString(ms));
}else{
throw new RuntimeException("数据插入失败");
}
}
}
四、实现可靠消息消费
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* 队列消息消费.
*
* @author liuruojing
* @date 2022/1/18 下午8:20
* @since v1.0
*/
@Component
@Slf4j
public class RabbitListener {
@org.springframework.amqp.rabbit.annotation.RabbitListener(queues = "order.message")
public void processMessage(String content, Message message, Channel channel) {
long deliveryTag = -1;
try {
deliveryTag = message.getMessageProperties().getDeliveryTag();
log.info("消费短信发送消息:{},消息内容:{}", deliveryTag, content);
//todosomething
channel.basicAck(deliveryTag, false);
log.info("短信发送消息消费成功:{},消息内容:{}", deliveryTag, content);
} catch (Exception e) {
log.error("短信发送消息消费失败", e);
try {
//返回nack,不重发,丢弃或会进到死信队列
channel.basicNack(deliveryTag, false, false);
} catch (IOException exception) {
log.error("短信发送消息拒签失败", exception);
}
}
}
@org.springframework.amqp.rabbit.annotation.RabbitListener(queues = "order.push")
public void processPushMessage(String content, Message message, Channel channel) {
long deliveryTag = -1;
try {
deliveryTag = message.getMessageProperties().getDeliveryTag();
log.info("消费推送消息:{},消息内容:{}", deliveryTag, content);
//todosomething
channel.basicAck(deliveryTag, false);
log.info("推送消息消费成功:{},消息内容:{}", deliveryTag, content);
} catch (Exception e) {
log.error("推送消息消费失败", e);
try {
//返回nack,不重发,丢弃或会进到死信队列
channel.basicNack(deliveryTag, false, false);
} catch (IOException exception) {
log.error("推送消息拒签失败", exception);
}
}
}
}
监听死信队列逻辑本例中未实现,可根据实际情况实现