RabbitMQ延时队列
1.延时队列定时关单
复习RabbitMQ:7.延迟队列-7.4.队列TTL
https://www.cnblogs.com/yydscn/p/15208402.html
升级为:
1.1.代码
1.交换机、队列、路由key常量声明类:
public class OrderConstant {
//定时关单交换机
public static final String ORDER_EVENT_EXCHANGE = "order-event-exchange";
//定时关单延时队列
public static final String ORDER_DELAY_QUEUE = "order-delay-queue";
//定时关单-订单释放队列
public static final String ORDER_RELEASE_ORDER_QUEUE = "order-release-order-queue";
//定时关单-下单路由key
public static final String ORDER_CREATE_ORDER_ROUTING_KEY = "order.create.order.routing.key";
//定时关单-订单释放路由key
public static final String ORDER_RELEASE_ORDER_ROUTING_KEY = "order.release.order.routing.key";
}
2.RabbitMQ配置类:
@Configuration
public class RabbitMQConfig {
/**
* 使用JSON序列化机制,进行消息转换
*
* @return
*/
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
/**
* 定时关单交换机
*
* @return
*/
@Bean
public DirectExchange orderEventExchange() {
return ExchangeBuilder.directExchange(OrderConstant.ORDER_EVENT_EXCHANGE)
.durable(true)
.build();
}
/**
* 定时关单延时队列
*
* @return
*/
@Bean
public Queue orderDelayQueue() {
Map<String, Object> arguments = new HashMap<>(3);
// 声明当前队列绑定的死信交换机
arguments.put("x-dead-letter-exchange", OrderConstant.ORDER_EVENT_EXCHANGE);
// 声明当前队列的死信路由 key
arguments.put("x-dead-letter-routing-key", OrderConstant.ORDER_RELEASE_ORDER_ROUTING_KEY);
// 声明队列的TTL
arguments.put("x-message-ttl", 60000);
return QueueBuilder.durable(OrderConstant.ORDER_DELAY_QUEUE)
.withArguments(arguments)
.build();
}
/**
* 定时关单-订单释放队列
*
* @return
*/
@Bean
public Queue orderReleaseOrderQueue() {
return QueueBuilder.durable(OrderConstant.ORDER_RELEASE_ORDER_QUEUE).build();
}
/**
* 定时关单-下单绑定路由
*
* @param orderDelayQueue
* @param orderEventExchange
* @return
*/
@Bean
public Binding orderCreateOrderBinding(@Qualifier("orderDelayQueue") Queue orderDelayQueue,
@Qualifier("orderEventExchange") DirectExchange orderEventExchange) {
return BindingBuilder.bind(orderDelayQueue).to(orderEventExchange).with(OrderConstant.ORDER_CREATE_ORDER_ROUTING_KEY);
}
/**
* 定时关单-订单释放绑定路由
*
* @param orderReleaseOrderQueue
* @param orderEventExchange
* @return
*/
@Bean
public Binding orderReleaseOrderBinding(@Qualifier("orderReleaseOrderQueue") Queue orderReleaseOrderQueue,
@Qualifier("orderEventExchange") DirectExchange orderEventExchange) {
return BindingBuilder.bind(orderReleaseOrderQueue).to(orderEventExchange).with(OrderConstant.ORDER_RELEASE_ORDER_ROUTING_KEY);
}
}
3.消费者监听队列
@Slf4j
@Component
public class OrderReleaseConsumer {
@Autowired
private RedisTemplate redisTemplate;
@RabbitListener(queues = OrderConstant.ORDER_RELEASE_ORDER_QUEUE)
public void listenOrderReleaseOrderQueue(String msg, Message message, Channel channel) throws IOException {
String messageId = message.getMessageProperties().getHeader("spring_returned_message_correlation");
log.info("队列{}已经接收到id为{}的消息:{}", OrderConstant.ORDER_RELEASE_ORDER_QUEUE, messageId, msg);
try {
String fullKey = OrderCacheConst.ORDER_RELEASE_ORDER_KEY + messageId;
Boolean flag = redisTemplate.hasKey(fullKey);
//redis中不存在,则说明消息没有消费过,按正常业务处理;否则,直接跳过
if (!flag) {
Map messageMap = JSON.parseObject(msg, HashMap.class);
String content = (String) messageMap.get("content");
log.info("定时关单:" + content);
//存入redis
redisTemplate.opsForValue().setIfAbsent(fullKey, fullKey, OrderCacheConst.MQ_MESSAGE_ID_CACHE_TIMEOUT_DAYS, TimeUnit.DAYS);
}
} catch (Exception e) {
//业务异常,不重回队列,而是放入死信队列处理
log.error("队列{}接收到id为{}的消息:{},处理业务异常:{}", OrderConstant.ORDER_RELEASE_ORDER_QUEUE, messageId, msg, e);
//TODO:记录消息日志:消费失败
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
}
//TODO:记录消息日志:消费成功
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}
4.测试模拟发送下单消息
@SpringBootTest
class GulimallOrderApplicationTests {
@Autowired
private MQSenderUtil mqSenderUtil;
@Test
void contextLoads() throws InterruptedException {
for (int i = 0; i < 10; i++) {
Map<String, Object> messageMap = Maps.newHashMap();
messageMap.put("content", "下单成功,延时队列定时关单" + i);
String message = JSON.toJSONString(messageMap);
mqSenderUtil.sendMessage(OrderConstant.ORDER_EVENT_EXCHANGE, OrderConstant.ORDER_CREATE_ORDER_ROUTING_KEY, message);
System.out.println("下单成功" + i);
TimeUnit.SECONDS.sleep(3);
}
}
}
1.2.附:RabbitMQ相关配置
1.引入依赖:
implementation 'org.springframework.boot:spring-boot-starter-amqp'
testImplementation 'org.springframework.amqp:spring-rabbit-test'
2.配置YML
spring:
# 消息中间件配置
rabbitmq:
addresses: 192.168.56.10:5672
username: guest
password: guest
# 配置虚拟机
virtual-host: /
# 开启发送端消息抵达Broker交换机确认
publisher-confirm-type: correlated
# 开启发送端消息抵达Queue确认
publisher-returns: true
# 只要消息抵达Queue,就会异步发送优先回调return confirm
template:
mandatory: true
listener:
type: simple
# 消费端开启手动ack消息,不使用默认自动ack
simple:
acknowledge-mode: manual
retry:
enabled: true # 开启重试机制
max-attempts: 3 # 最大重试传递次数
initial-interval: 5000ms # 第一次和第二次尝试传递消息的间隔时间,单位毫秒
multiplier: 3 # 上一重试间隔的乘数 步长
max-interval: 300000ms # 最大重试时间间隔,单位毫秒
# 以上配置的间隔0s 5s 15s 45s
# 重试次数超过上面的设置之后是否重回队列(消费者listener抛出异常,是否重回队列,默认true:重回队列,false为不重回队列(结合死信交换机))
default-requeue-rejected: false
3.发送消息工具类
@Slf4j
@Configuration
public class MQSenderUtil {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 依赖注入rabbitTemplate之后再设置它的回调对象
* Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
*/
@PostConstruct
public void init() {
/**
* 交换机不管是否收到消息的一个回调方法
* 1.CorrelationData 消息相关数据
* 2.ack 交换机是否收到消息
* 3.cause 未收到消息的原因
*/
RabbitTemplate.ConfirmCallback confirmCallback = (correlationData, ack, cause) -> {
String id = correlationData != null ? correlationData.getId() : "";
if (ack) {
log.info("交换机已经接收到id为:{}的消息", id);
} else {
//交换机未接收到的消息,需要重试发送消息
log.error("交换机未接收到id为:{}的消息,由于原因:{}", id, cause);
}
};
/**
* 消息回退:只有当消息传递过程中不可达目的地时才将消息返回给生产者
*/
RabbitTemplate.ReturnsCallback returnsCallback = returned -> {
//被退回的消息保存到备份交换机,由备份交换机来进行转发和处理
String errorMsg = String.format("消息:%s被交换机退回,退回原因:%s,交换机是:%s,路由key:%s",
new String(returned.getMessage().getBody()), returned.getReplyText(), returned.getExchange(), returned.getRoutingKey());
log.error(errorMsg);
String messageId = returned.getMessage().getMessageProperties().getHeader("spring_returned_message_correlation");
log.error("被退回的消息ID:{}", messageId);
};
rabbitTemplate.setConfirmCallback(confirmCallback);
rabbitTemplate.setReturnsCallback(returnsCallback);
}
/**
* 发送消息
*
* @param exchange 交换机名
* @param routingKey 路由key名
* @param message 消息
*/
public void sendMessage(String exchange, String routingKey, String message) {
CorrelationData correlationData = new CorrelationData(String.valueOf(SnowflakeIdGenerator.generateUniqueId()));
log.info("生产者发送消息id:{}, message:{}, exchange:{}, routingKey:{}", correlationData.getId(), message, exchange, routingKey);
try {
rabbitTemplate.convertAndSend(exchange, routingKey, message, messagePostProcessor -> {
//设置消息持久化
messagePostProcessor.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return messagePostProcessor;
}, correlationData);
} catch (Exception e) {
log.error("生产者发送消息失败id:{}, message:{}, exchange:{}, routingKey:{}, 异常原因:{}", correlationData.getId(), message, exchange, routingKey, e);
}
}
/**
* 消息重试
*
* @param exchange
* @param routingKey
* @param messageId
* @param message
*/
public void retryMessage(String exchange, String routingKey, String messageId, String message) {
CorrelationData correlationData = new CorrelationData(messageId);
log.info("ERP生产者重试消息id:{}, message:{}, exchange:{}, routingKey:{}", messageId, message, exchange, routingKey);
try {
rabbitTemplate.convertAndSend(exchange, routingKey, message, messagePostProcessor -> {
//设置消息持久化
messagePostProcessor.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return messagePostProcessor;
}, correlationData);
} catch (Exception e) {
log.error("ERP生产者重试消息失败id:{}, message:{}, exchange:{}, routingKey:{}, 异常原因:{}", correlationData.getId(), message, exchange, routingKey, e);
}
}
}
分布式ID生成器(雪花算法):
package com.sen.gulimall.order.utils;
import java.net.Inet4Address;
import java.net.UnknownHostException;
import java.util.Random;
/**
* @author Kisen
* @email liuqs@jaid.cn
* @date 2022/8/28 17:25
* @detail 分布式ID生成器(雪花算法)
*/
public class SnowflakeIdGenerator {
/**
* 时间戳标识所占二进制位数
*/
private static final int TIME_STAMP_BIT_LEN = 41;
/**
* 机房标识所占二进制位数
*/
private static final int SERVER_ROOM_BIT_LEN = 5;
/**
* 服务器标识所占二进制位数
*/
private static final int SERVER_BIT_LEN = 5;
/**
* 每毫秒中序列所占二进制位数
*/
private static final int SEQ_BIT_LEN = 12;
/**
* 时间戳标识向左移动的位数(这里的1标识最高位)
*/
private static final int TIME_STAMP_LEFT_BIT_LEN = 64 - 1 - TIME_STAMP_BIT_LEN;
/**
* 机房标识左移位数
*/
private static final int SERVER_ROOM_LEFT_BIT_LEN = TIME_STAMP_LEFT_BIT_LEN - SERVER_ROOM_BIT_LEN;
/**
* 服务器标识左移位数
*/
private static final int SERVER_LEFT_BIT_LEN = SERVER_ROOM_LEFT_BIT_LEN - SERVER_BIT_LEN;
/**
* 开始时间戳,此处为 2022年8月28日
*/
private static final long START_TIME_STAMP = 1661616000000L;
/**
* 上次生成ID的时间戳
*/
private static long LAST_TIME_STAMP = -1L;
/**
* 上一次毫秒内存序列值
*/
private static long LAST_SEQ = 0L;
/**
* 获取机房标识(可以手动定义0-31之间的数)
*/
private static final long SERVER_ROOM_ID = getServerRoomId();
/**
* 获取服务器标识(可以手动定义0-31之间的数)
*/
private static final long SERVER_ID = getServerId();
/**
* 机房标识最大值 +1
*/
private static final int SERVER_ROOM_MAX_NUM_1 = ~(-1 << SERVER_ROOM_BIT_LEN) + 1;
/**
* 服务器标识最大值 +1
*/
private static final int SERVER_MAX_NUM_1 = ~(-1 << SERVER_BIT_LEN) + 1;
/**
* 毫秒内存列的最大值
*/
private static final long SEQ_MAX_NUM = ~(-1 << SEQ_BIT_LEN);
/**
* 对服务器地址的哈希码取余作为服务器标识
*
* @return 服务器标识
*/
private static int getServerId() {
try {
String hostAddress = Inet4Address.getLocalHost().getHostAddress();
return (hostAddress.hashCode() & Integer.MAX_VALUE) % SERVER_MAX_NUM_1;
} catch (UnknownHostException e) {
return new Random().nextInt(SERVER_MAX_NUM_1);
}
}
/**
* 对服务器名称的哈希码取余作为机房标识
*
* @return 机房标识
*/
private static int getServerRoomId() {
try {
String hostName = Inet4Address.getLocalHost().getHostName();
return (hostName.hashCode() & Integer.MAX_VALUE) % SERVER_ROOM_MAX_NUM_1;
} catch (UnknownHostException e) {
return new Random().nextInt(SERVER_ROOM_MAX_NUM_1);
}
}
/**
* 一直循环直到获取到下毫秒的时间戳
*
* @param lastMillis
* @return 下一毫秒的时间戳
*/
private static long nextMillis(long lastMillis) {
long now = System.currentTimeMillis();
while (now <= lastMillis) {
now = System.currentTimeMillis();
}
return now;
}
/**
* 生成唯一ID
* 须加锁避免并发问题
*
* @return 返回唯一ID
*/
public synchronized static long generateUniqueId() {
long currentTimeStamp = System.currentTimeMillis();
// 如果当前时间小于上一次ID生成的时间戳,说明系统时间回退过,此时应抛出异常
if (currentTimeStamp < LAST_TIME_STAMP) {
throw new RuntimeException(String.format("系统时间错误! %d 毫秒内拒绝生成雪花ID", START_TIME_STAMP));
}
if (currentTimeStamp == LAST_TIME_STAMP) {
LAST_SEQ = (LAST_SEQ + 1) & SEQ_MAX_NUM;
if (LAST_SEQ == 0) {
currentTimeStamp = nextMillis(LAST_TIME_STAMP);
}
} else {
LAST_SEQ = 0;
}
// 上次生成ID的时间戳
LAST_TIME_STAMP = currentTimeStamp;
return ((currentTimeStamp - START_TIME_STAMP) << TIME_STAMP_LEFT_BIT_LEN | (SERVER_ROOM_ID << SERVER_ROOM_LEFT_BIT_LEN) | (SERVER_ID << SERVER_LEFT_BIT_LEN) | LAST_SEQ);
}
/**
* 主函数测试
*
* @param args
*/
public static void main(String[] args) {
long start = System.currentTimeMillis();
int num = 100;
for (int i = 0; i < num; i++) {
System.out.println(generateUniqueId());
}
long end = System.currentTimeMillis();
System.out.println("共生成 " + num + " 个ID,用时 " + (end - start) + " 毫秒");
}
}
2.下订单-消息队列架构图
库存解锁的场景:
1)、下订单成功,订单过期没有支付被系统自动取消、被用户手动取消。都要解锁库存
2)、下订单成功,库存锁定成功,接下来的业务调用失败,导致订单回滚。
2.1.库存定时自动解锁代码
1.交换机、队列、路由key常量声明类
public class WareConstant {
//解锁库存交换机
public static final String STOCK_EVENT_EXCHANGE = "stock-event-exchange";
//解锁库存延时队列
public static final String STOCK_DELAY_QUEUE = "stock-delay-queue";
//解锁库存队列
public static final String STOCK_RELEASE_STOCK_QUEUE = "stock-release-stock-queue";
//解锁库存路由key
public static final String STOCK_RELEASE_ROUTING_KEY = "stock.release.#";
//锁定库存路由key
public static final String STOCK_LOCKED_ROUTING_KEY = "stock.locked";
}
2.RabbitMQ配置类
@Configuration
public class RabbitMQConfig {
/**
* 使用JSON序列化机制,进行消息转换
*
* @return
*/
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
/**
* 解锁库存交换机
*
* @return
*/
@Bean
public TopicExchange stockEventExchange() {
return ExchangeBuilder.topicExchange(WareConstant.STOCK_EVENT_EXCHANGE)
.durable(true)
.build();
}
/**
* 解锁库存延时队列
*
* @return
*/
@Bean
public Queue stockDelayQueue() {
Map<String, Object> arguments = new HashMap<>(3);
// 声明当前队列绑定的死信交换机
arguments.put("x-dead-letter-exchange", WareConstant.STOCK_EVENT_EXCHANGE);
// 声明当前队列的死信路由 key
arguments.put("x-dead-letter-routing-key", "stock.release");
// 声明队列的TTL
arguments.put("x-message-ttl", 50 * 60 * 1000);
return QueueBuilder.durable(WareConstant.STOCK_DELAY_QUEUE)
.withArguments(arguments)
.build();
}
/**
* 解锁库存队列
*
* @return
*/
@Bean
public Queue stockReleaseStockQueue() {
return QueueBuilder.durable(WareConstant.STOCK_RELEASE_STOCK_QUEUE).build();
}
/**
* 锁定库存绑定路由
*
* @param stockDelayQueue
* @param stockEventExchange
* @return
*/
@Bean
public Binding stockLockedBinding(@Qualifier("stockDelayQueue") Queue stockDelayQueue,
@Qualifier("stockEventExchange") TopicExchange stockEventExchange) {
return BindingBuilder.bind(stockDelayQueue).to(stockEventExchange).with(WareConstant.STOCK_LOCKED_ROUTING_KEY);
}
/**
* 解锁库存绑定路由
*
* @param stockReleaseStockQueue
* @param stockEventExchange
* @return
*/
@Bean
public Binding stockReleaseBinding(@Qualifier("stockReleaseStockQueue") Queue stockReleaseStockQueue,
@Qualifier("stockEventExchange") TopicExchange stockEventExchange) {
return BindingBuilder.bind(stockReleaseStockQueue).to(stockEventExchange).with(WareConstant.STOCK_RELEASE_ROUTING_KEY);
}
}
3.监听解锁队列
StockReleaseListener类:
@Slf4j
@Service
@RabbitListener(queues = WareConstant.STOCK_RELEASE_STOCK_QUEUE)
public class StockReleaseListener {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private WareSkuService wareSkuService;
/**
* 1、库存自动解锁。
* 下订单成功,库存锁定成功,接下来的业务调用失败,导致订单回滚。之前锁定的库存就要自动解锁。
* 2、订单失败。
* 锁库存失败
*
* @param to
* @param message
* @param channel
* @throws IOException
*/
@RabbitHandler
public void handleStockLockedRelease(StockLockedTo to, Message message, Channel channel) throws IOException {
String messageId = message.getMessageProperties().getHeader("spring_returned_message_correlation");
log.info("队列{}已经接收到id为{}的消息:{}", WareConstant.STOCK_RELEASE_STOCK_QUEUE, messageId, to);
try {
String fullKey = WareCacheConst.STOCK_RELEASE_STOCK_KEY + messageId;
Boolean flag = redisTemplate.hasKey(fullKey);
//redis中不存在,则说明消息没有消费过,按正常业务处理;否则,直接跳过
if (!flag) {
wareSkuService.unlockStock(to);
//存入redis
redisTemplate.opsForValue().setIfAbsent(fullKey, fullKey, WareCacheConst.MQ_MESSAGE_ID_CACHE_TIMEOUT_DAYS, TimeUnit.DAYS);
}
} catch (Exception e) {
//业务异常,不重回队列,而是放入死信队列处理
log.error("队列{}接收到id为{}的消息:{},处理业务异常:{}", WareConstant.STOCK_RELEASE_STOCK_QUEUE, messageId, to, e);
//TODO:记录消息日志:消费失败
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
}
//TODO:记录消息日志:消费成功
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}
库存业务类WareSkuServiceImpl:
@Override
public void unlockStock(StockLockedTo to) {
Long id = to.getId();
StockDetailTo detailTo = to.getDetailTo();
Long detailId = detailTo.getId();
/**
* 解锁
* 1、查询数据库关于这个订单的锁定库存信息
* 有:证明库存锁定成功了
* 解锁:订单情况。
* 1、没有这个订单。必须解锁
* 2、有这个订单。不是解锁库存
* 订单状态:已取消,解锁库存;没取消,不能解锁
* 没有:库存锁定失败了,库存回滚了。这种情况无需解锁
*/
log.info("收到解锁库存的消息");
WareOrderTaskDetailEntity entity = wareOrderTaskDetailService.getById(detailId);
if (entity != null) {
WareOrderTaskEntity taskEntity = wareOrderTaskService.getById(id);
String orderSn = taskEntity.getOrderSn(); //根据这个订单号查询订单的状态
R r = orderFeignService.getOrderStatus(orderSn);
if (r.getCode() != 0) {
throw new RuntimeException("远程服务失败");
}
//订单数据返回成功
OrderVo data = r.getData(new TypeReference<OrderVo>() {
});
if (data == null || data.getStatus() == 4) {
//订单不存在或订单状态已取消,解锁库存
if (entity.getLockStatus() == 1) {
//当前库存工作单是已锁定状态,才需解锁
unLockStock(detailTo.getSkuId(), detailTo.getWareId(), detailTo.getSkuNum(), detailId);
}
}
}
}
private void unLockStock(Long skuId, Long wareId, Integer skuNum, Long detailId) {
//库存解锁
wareSkuDao.unLockStock(skuId, wareId, skuNum);
//更新库存工作单的状态
WareOrderTaskDetailEntity updateDetailEntity = new WareOrderTaskDetailEntity();
updateDetailEntity.setId(detailId);
updateDetailEntity.setLockStatus(2); //已解锁
wareOrderTaskDetailService.updateById(updateDetailEntity);
}
4.测试模拟库存定时自动解锁
StockLockedTo lockedTo = new StockLockedTo();
lockedTo.setId(wareOrderTaskEntity.getId());
StockDetailTo stockDetailTo = new StockDetailTo();
BeanUtils.copyProperties(taskDetailEntity, stockDetailTo);
lockedTo.setDetailTo(stockDetailTo);
mqSenderUtil.sendMessage(WareConstant.STOCK_EVENT_EXCHANGE, WareConstant.STOCK_LOCKED_ROUTING_KEY, lockedTo);
2.如何保证消息可靠性(消息丢失、消息重复、消息积压)
2.1.消息丢失
- 消息发送出去,由于网络问题没有抵达服务器
- 做好容错方法(try-catch),发送消息可能会网络失败,失败后要有重试机制,可记录到数据库,采用定期扫描重发的方式
- 做好日志记录,每个消息状态是否都被服务器收到都应该记录
- 做好定期重发,如果消息没有发送成功,定期去数据库扫描未成功的消息进行重发
- 消息抵达Broker,Broker要将消息写入磁盘(持久化)才算成功。此时Broker尚未持久化完成,宕机。
- publisher也必须加入确认回调机制,确认成功的消息,修改数据库消息状态。
- 自动ACK的状态下。消费者收到消息,但没来得及消息然后宕机
- 一定开启手动ACK,消费成功才移除,失败或者没来得及处理就noAck并重新入队
2.2.消息重复
- 消息消费成功,事务已经提交,ack时,机器宕机。导致没有ack成功,Broker的消息重新由unack变为ready,并发送给其他消费者
- 消息消费失败,由于重试机制,自动又将消息发送出去
- 成功消费,ack时宕机,消息由unack变为ready,Broker又重新发送
方案:
- 消费者的业务消费接口应该设计为幂等性的。比如扣库存有工作单的状态标志
- 使用防重表(redis/mysql),发送消息每一个都有业务的唯一标识,处理过就不用处理
- rabbitMQ的每一个消息都有redelivered字段,可以获取是否是被重新投递过来的,而不是第一次投递过来的
2.3.消息积压
- 消费者宕机积压
- 消费者消费能力不足积压
- 发送者发送流量太大
方案:
- 上线更多的消费者,进行正常消费
- 上线专门的队列消费服务,将消息先批量取出来,记录数据库,离线慢慢处理
总结:
防止消息丢失:
1、做好消息确认机制( pulisher, consumer【手动 ack】 )
2、每一个发送的消息都在数据库做好记录。 定期将失败的消息再次发送一遍
本文来自博客园,作者:冰枫丶,转载请注明原文链接:https://www.cnblogs.com/lqsblog/p/17028994.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
2022-01-05 JVM-内存与垃圾回收篇