保障100%的消息可靠性投递方案落地实现
保障100%消息投递成功设计方案(一)
蓝色区域: 生产者
红色区域: broker(MQ服务)
总共有7个步骤
第一步: 存储业务数据(订单), 存储MQ消息记录(MQ日志)
第二步: 发送确认类型的MQ消息到broker
第三步: 监听该结果, broker收到消息,返回(应答)确认收到
第四步: 操作MQ消息记录表做状态更新(0消息发送中,1消息发送成功,2消息发送失败)
如果没有收到第三步的确认则会有定时任务定时轮询
第五步: 定时任务获取状态为0的消息, 为0走第6步进行消息重新投递
第六步: 消息重投
第七步: 当第五步最大努力重投达到一定次数后状态还是0, 就把消息直接设为失败2
代码实现
生产者
MQ消息记录表
-- 表broker_message_1og消息记录结构
CREATE TABLE IF NOT EXISTS `broker_message_log` (
`message_id` varchar (128) NOT NULL, -- 消息唯一ID
`message` varchar(4000) DEFAULT NULL, -- 消息内容
`try_count` int(4) DEFAULT '0', -- 重试次数
`status` varchar (10) DEFAULT '', -- 消息投递状态:0投递中,1投递成功,2投递失败
`next_retry` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', -- 下一次重试时间 或 超时时间
`create_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', -- 创建时间
`update_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', -- 更新时间
PRIMARY KEY (`message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
发送MQ消息类
@Component
public class RabbitOrderSender {
// 自动注入RabbitTemplate模板类
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private BrokerMessageLogMapper brokerMessageLogMapper;
// 回调函数: confirm确认
final ConfirmCal1back confirmCallback = new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.err.println("correlationData:" + correlationData);
String messageId = correlationData.getId();
if (ack) {
// 如果confirm返回成功则进行更新
brokerMessageLogMapper.changeBrokerMessageLogStatus (messageId, Constants.ORDER_SEND_SUCCESS, new Date());
} else {
// 失败则进行具体的后续操作: 重试或者补偿等手段
System.err.println("异常处理...");
}
}
};
// 发送消息方法调用: 构建自定义对象消息
public void sendOrder(Order order) throws Exception {
rabbitTemplate.setConfirmCallback (confirmCallback) ;
//消息唯一ID
CorrelationData correlationData = new CorrelationData (order.getMessageId());
rabbitTemplate.convertAndSend( "order-exchange", "order.ABC", order, correlationData);
}
}
创建订单并发送MQ
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private BrokerMessageLogMapper brokerMessageLogMapper;
@Autowired
private RabbitOrderSender rabbitOrderSender;
public void createOrder(Order order) throws Exception {
// order current time
Date orderTime = new Date() ;
// order insert //业务数据入库
orderMapper.insert(order);
// log insert // 构建消息日志记录对象
BrokerMessageLog brokerMessageLog = new BrokerMessageLog() ;
brokerMessageLog.setMessageId (order.getMessageId()) ;
// save order message as json
brokerMessageLog.setMessage(FastJsonConvertUtil.convertObjectToJSON(order));
brokerMessageLog.setStatus("0"); // 设置订单的发送状态为0表示发送中
brokerMessageLog.setNextRetry(DateUtils.addMinutes(orderTime, Constants.ORDER_TIMEOUT)); // 为1分钟,表示1分钟后状态还没改为1就是超时了, 定时任务就会重新投递
brokerMessageLog.setCreateTime(new Date());
brokerMessageLog.setUpdateTime(new Date());
// 保存MQ消息日志
brokerMessageLogMapper.insert(brokerMessageLog);
// order message sender
rabbitOrderSender.sendOrder(order) ;
}
}
定时任务(补偿机制)
@Component
public class RetryMessageTasker {
@Autowired
private RabbitOrderSender rabbitOrderSender;
@Autowired
private BrokerMessageLogMapper brokerMe ssageLogMapper;
@Scheduled (initialDelay = 3000, fixedDelay = 10000)
public void reSend() {
System.err.println ("--------------定时任务开始---------------");
//pu11 status = 0 and timeout message(超时时间小于当前时间)
List<BrokerMessageLog> list = brokerMessageLogMapper.query4StatusAndTimeoutMessage();
list.forEach (messageLog -> {
if (messageLog.getTryCount() >= 3) {
// update fail message 重试次数达到3次, 修改消息状态为2失败, 由人工补偿
brokerMessageLogMapper.changeBrokerMessageLogStatus(messageLog.getMessageId(), Constants.ORDER_SEND_FAILURE, new Date());
} else {
// resend
// 更新重试次数
brokerMessageLogMapper.update4ReSend (messageLog.getMessageId(), new Date());
// 取出消息, 反序列化为对象
OrderreSendOrder = FastJsonConvertUtil.convertJSONToobject(message);
try {
// 重新投递
rabbitOrderSender.sendOrder(reSendOrder);
} catch (Exception e) {
e.printStackTrace () ;
System.err.println ("----------异常处理----------");
}
}
}
}
}
消费者
消费者配置
spring.rabbitmq.addresses=192.168.11.81:5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
spring.rabbitmq.connection-timeout=15000
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.simple.concurrency=5
spring.rabbitmq.listener.simple.prefetch=1
spring.rabbitmq.listener.simple.max-concurrency=10
spring.rabbitmq.listener.order.queue.name=order-queue
spring.rabbitmq.listener.order.queue.durable=true
spring.rabbitmq.listener.order.exchange.name=order-exchange
spring.rabbitmq.listener.order.exchange.durable=true
spring.rabbitmq.listener.order.exchange.type=topic
spring.rabbitmq.listener.order.exchange.ignoreDeclarationExceptions=true
spring.rabbitmq.listener.order.key=order.*
消费代码
@Component
public class RabbitReceiver {
/**
*
* spring.rabbitmq.listener.order.queue.name=order-queue
* spring.rabbitmq.listener.order.queue.durable=true
* spring.rabbitmq.listener.order.exchange.name=order-exchange
* spring.rabbitnq.listener.order.exchange.durable=true
* spring,rabbitmq.listener.order.exchange.type=topic
* spring.rabbitmq.listener.order.exchange.ignoreDeclarationExceptions=true
* spring.rabbitmq.listener.order.key=order.*
*
* @param order
* @param channeI
* @param headers
* @throws Exception
*/
@RabbtListener (bindings = @QueueBinding(
value = @Queue (value = "${spring.rabbitmq.listener.order.queue.name}"),
durable = "${spring.rabbitmq.listener.order.queue.durable}"),
exchange = @Exchange (value = "${spring.rabbitmq.listener.order.exchange.name}")
durable = "${spring.rabbitnq.listener.order.exchange.durable}",
type = "${spring,rabbitmq.listener.order.exchange.type}",
ignoreDeclarationExceptions = "${spring.rabbitmq.listener.order.exchange.ignoreDeclarationExceptions}"),
key = "${spring.rabbitmq.listener.order.key}"
)
)
@RabbitHandler
public void onOrderMessage (@Payload Order order,
Channel channel,
@HeadersMap<String, Object> headers) throws Exception {
System.err.println("--------------------------------------") ;
System.err.println("消费端order:" + order.getId());
Long deliveryTag = (Long)headers.get(AmqpHeaders.DELIVERY_TAG) ;
//手工 ACK
channel.basicAck(deliveryTag, false) ;
}
}
测试代码
@Autowired
private OrderService orderService;
@Test
public void testCreateOrder() throws Exception {
Order order = new Order () ;
order.setId ("2018081900000001") ;
order.setName ("测试创建订单");
order.setMessageId (System.currentTimeMillis() + "$" + UUID.randomUUID().toString());
orderService.createOrder(order);
}
保障100%消息投递成功设计方案(二)
两次投递方案