一、MQ 业务场景实践
1.1 典型业务场景
电商系统
java
// 订单创建异步化
@Service
@Slf4j
public class OrderService {
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 创建订单 - 异步解耦
*/
@Transactional
public OrderDTO createOrder(OrderRequest request) {
// 1. 基础校验(同步)
validateOrderRequest(request);
// 2. 创建订单记录(同步)
Order order = saveOrder(request);
// 3. 发送订单创建消息(异步解耦)
OrderCreatedEvent event = buildOrderCreatedEvent(order);
rocketMQTemplate.asyncSend(
"order-created-topic",
event,
new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
log.info("订单创建消息发送成功: {}", order.getOrderNo());
}
@Override
public void onException(Throwable e) {
log.error("订单创建消息发送失败: {}", order.getOrderNo(), e);
// 记录到补偿表
compensateService.recordFailedMessage(event);
}
}
);
// 4. 扣减库存(同步)
inventoryService.deductStock(order.getItems());
return convertToDTO(order);
}
}
// 订单创建消息消费者
@Component
@RocketMQMessageListener(
topic = "order-created-topic",
consumerGroup = "order-created-group"
)
@Slf4j
public class OrderCreatedConsumer {
@Autowired
private CouponService couponService;
@Autowired
private PointsService pointsService;
@Autowired
private AnalyticsService analyticsService;
@Override
public void onMessage(OrderCreatedEvent event) {
log.info("收到订单创建消息: {}", event.getOrderNo());
try {
// 1. 更新优惠券状态(异步)
couponService.markCouponUsed(event.getCouponId());
// 2. 增加用户积分(异步)
pointsService.addPoints(event.getUserId(), event.getPoints());
// 3. 数据统计(异步)
analyticsService.recordOrderEvent(event);
// 4. 发送通知(异步)
notificationService.sendOrderCreatedNotification(event);
log.info("订单创建后续处理完成: {}", event.getOrderNo());
} catch (Exception e) {
log.error("订单创建消息处理失败: {}", event.getOrderNo(), e);
// 抛异常触发重试
throw new RuntimeException("处理失败", e);
}
}
}
实时库存同步
java
// 基于 MQ 的最终一致性库存同步
@Component
@Slf4j
public class InventorySyncService {
/**
* 库存变更事件生产者
*/
public void publishInventoryChange(InventoryChangeEvent event) {
// 本地事务确保数据库和MQ一致性
transactionTemplate.execute(status -> {
// 1. 更新本地库存
inventoryDAO.updateStock(event);
// 2. 发送库存变更消息
kafkaTemplate.send(
"inventory-change-topic",
event.getSkuId(),
event
).addCallback(
result -> log.info("库存消息发送成功"),
ex -> {
log.error("库存消息发送失败", ex);
// 记录到本地补偿表
localCompensationService.recordFailedMessage(event);
}
);
return true;
});
}
}
// 库存消费者
@Component
@KafkaListener(topics = "inventory-change-topic", groupId = "inventory-group")
@Slf4j
public class InventorySyncConsumer {
@Autowired
private ElasticsearchService esService;
@Autowired
private RedisService redisService;
@Autowired
private CacheService cacheService;
@KafkaHandler
public void handleInventoryChange(InventoryChangeEvent event) {
// 使用分布式锁,防止并发更新问题
String lockKey = "lock:inventory:sync:" + event.getSkuId();
boolean locked = redisService.lock(lockKey, 10, TimeUnit.SECONDS);
if (!locked) {
log.warn("获取锁失败,跳过处理: {}", event.getSkuId());
return;
}
try {
// 1. 更新ES库存(搜索服务)
esService.updateInventory(event.getSkuId(), event.getQuantity());
// 2. 更新Redis缓存
redisService.set(
"inventory:" + event.getSkuId(),
event.getQuantity(),
5, TimeUnit.MINUTES
);
// 3. 清理本地缓存
cacheService.evictInventoryCache(event.getSkuId());
log.info("库存同步完成: {}", event.getSkuId());
} catch (Exception e) {
log.error("库存同步失败: {}", event.getSkuId(), e);
// 记录失败,用于补偿
compensationService.recordFailedSync(event);
} finally {
redisService.unlock(lockKey);
}
}
}
1.2 金融支付场景
java
// 支付状态异步通知
@Component
@Slf4j
public class PaymentStatusService {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 支付状态变更 - 广播通知
*/
public void notifyPaymentStatus(Payment payment) {
PaymentStatusEvent event = convertToEvent(payment);
// 使用 fanout 交换机广播
rabbitTemplate.convertAndSend(
"payment-status-exchange", // fanout 交换机
"", // fanout 不需要 routing key
event,
message -> {
// 设置消息头
message.getMessageProperties().setHeader("payment_id", payment.getId());
message.getMessageProperties().setHeader("event_time", System.currentTimeMillis());
return message;
}
);
log.info("支付状态变更通知已发送: {}", payment.getId());
}
}
// 多个系统监听支付状态
@Component
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "order.payment.status.queue"),
exchange = @Exchange(name = "payment-status-exchange", type = ExchangeTypes.FANOUT)
))
public class OrderPaymentListener {
@RabbitHandler
public void handlePaymentEvent(PaymentStatusEvent event) {
if (event.getStatus() == PaymentStatus.SUCCESS) {
// 更新订单状态为已支付
orderService.markOrderAsPaid(event.getOrderId());
}
}
}
@Component
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "account.payment.status.queue"),
exchange = @Exchange(name = "payment-status-exchange", type = ExchangeTypes.FANOUT)
))
public class AccountPaymentListener {
@RabbitHandler
public void handlePaymentEvent(PaymentStatusEvent event) {
// 更新账户余额变动
accountService.updateAccountBalance(event);
}
}
二、MQ 核心问题处理
2.1 消息丢失问题
解决方案一:生产者确认机制
java
@Configuration
@Slf4j
public class MQProducerConfig {
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
// 1. 开启确认模式
template.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
log.info("消息发送到Exchange成功: {}", correlationData.getId());
} else {
log.error("消息发送到Exchange失败: {}, cause: {}",
correlationData.getId(), cause);
// 重发机制
retryService.retrySend(correlationData);
}
});
// 2. 开启返回模式(消息未路由到队列的回调)
template.setReturnsCallback(returned -> {
log.error("消息未路由到队列: {}, replyCode: {}, replyText: {}",
returned.getMessage().getMessageProperties().getMessageId(),
returned.getReplyCode(),
returned.getReplyText());
// 处理未路由的消息
handleUnroutedMessage(returned.getMessage());
});
// 3. 设置消息持久化
template.setChannelTransacted(true);
return template;
}
}
// Kafka 生产者配置
@Configuration
public class KafkaProducerConfig {
@Bean
public ProducerFactory<String, Object> producerFactory() {
Map<String, Object> config = new HashMap<>();
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
// 确保消息不丢失的配置
config.put(ProducerConfig.ACKS_CONFIG, "all"); // 所有副本确认
config.put(ProducerConfig.RETRIES_CONFIG, 3); // 重试次数
config.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true); // 幂等性
config.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, 1);
return new DefaultKafkaProducerFactory<>(config);
}
}
解决方案二:本地消息表
java
@Entity
@Table(name = "mq_local_message")
@Data
public class LocalMessage {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String messageId;
private String topic;
private String content;
private Integer status; // 0-待发送, 1-已发送, 2-发送失败
private Integer retryCount;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
@Service
@Slf4j
public class LocalMessageService {
@Autowired
private LocalMessageRepository messageRepository;
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
/**
* 保存消息到本地表
*/
@Transactional
public void saveMessageForLocalTransaction(String messageId, String topic, String content) {
LocalMessage message = new LocalMessage();
message.setMessageId(messageId);
message.setTopic(topic);
message.setContent(content);
message.setStatus(0); // 待发送
message.setRetryCount(0);
message.setCreateTime(LocalDateTime.now());
messageRepository.save(message);
}
/**
* 定时任务扫描并发送消息
*/
@Scheduled(fixedDelay = 10000) // 每10秒执行
public void sendPendingMessages() {
List<LocalMessage> pendingMessages = messageRepository
.findByStatusAndRetryCountLessThan(0, 3);
for (LocalMessage message : pendingMessages) {
try {
kafkaTemplate.send(message.getTopic(), message.getContent())
.addCallback(result -> {
// 发送成功,更新状态
message.setStatus(1);
message.setUpdateTime(LocalDateTime.now());
messageRepository.save(message);
log.info("消息发送成功: {}", message.getMessageId());
}, ex -> {
// 发送失败,增加重试次数
message.setRetryCount(message.getRetryCount() + 1);
if (message.getRetryCount() >= 3) {
message.setStatus(2); // 标记为失败
}
message.setUpdateTime(LocalDateTime.now());
messageRepository.save(message);
log.error("消息发送失败: {}", message.getMessageId(), ex);
});
} catch (Exception e) {
log.error("处理待发送消息异常: {}", message.getMessageId(), e);
}
}
}
}
2.2 消息重复消费问题
解决方案:幂等性设计
java
@Component
@Slf4j
public class IdempotentMessageHandler {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String MESSAGE_IDEMPOTENT_KEY = "msg:idempotent:";
private static final long EXPIRE_HOURS = 24; // 24小时
/**
* 基于 Redis 的幂等性检查
*/
public boolean checkAndProcess(String messageId, Runnable processor) {
String key = MESSAGE_IDEMPOTENT_KEY + messageId;
// 使用 setnx 实现原子性检查
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(key, "processing", EXPIRE_HOURS, TimeUnit.HOURS);
if (Boolean.TRUE.equals(success)) {
try {
// 首次处理
processor.run();
// 更新状态为已完成
redisTemplate.opsForValue().set(key, "processed");
return true;
} catch (Exception e) {
// 处理失败,删除key允许重试
redisTemplate.delete(key);
throw e;
}
} else {
// 已处理过,检查状态
String status = redisTemplate.opsForValue().get(key);
if ("processed".equals(status)) {
log.info("消息已处理过,跳过: {}", messageId);
return false;
} else {
// 正在处理中,等待或重试
log.warn("消息正在处理中: {}", messageId);
return false;
}
}
}
/**
* 基于数据库的唯一约束
*/
@Transactional
public boolean processWithDBConstraint(String messageId, Consumer<String> businessProcessor) {
try {
// 先插入处理记录(唯一约束防止重复)
messageProcessRecordDAO.insertProcessRecord(messageId);
// 执行业务逻辑
businessProcessor.accept(messageId);
return true;
} catch (DuplicateKeyException e) {
// 记录已存在,说明已处理过
log.info("消息已处理: {}", messageId);
return false;
}
}
/**
* 基于业务状态机的幂等性
*/
public void processWithStateMachine(String orderId, String messageId) {
Order order = orderDAO.selectForUpdate(orderId); // 悲观锁
// 检查当前状态是否允许处理
if (order.getStatus() == OrderStatus.PENDING_PAYMENT) {
// 执行业务
order.setStatus(OrderStatus.PAID);
orderDAO.update(order);
log.info("订单支付处理成功: {}", orderId);
} else if (order.getStatus() == OrderStatus.PAID) {
log.info("订单已支付,幂等返回: {}", orderId);
} else {
log.warn("订单状态异常,无法处理支付: {}", orderId);
throw new IllegalStateException("订单状态异常");
}
}
}
2.3 消息堆积问题
解决方案一:消费端优化
java
@Component
@RocketMQMessageListener(
topic = "order-topic",
consumerGroup = "order-group",
consumeThreadNumber = 20, // 增加消费线程
consumeTimeout = 5L, // 超时时间
maxReconsumeTimes = 3 // 最大重试次数
)
@Slf4j
public class OrderMessageConsumer implements RocketMQListener<MessageExt> {
@Autowired
private ThreadPoolTaskExecutor orderProcessor;
@Override
public void onMessage(MessageExt message) {
// 异步处理,提高吞吐量
orderProcessor.execute(() -> {
try {
processOrderMessage(message);
} catch (Exception e) {
log.error("处理消息失败: {}", message.getMsgId(), e);
}
});
}
private void processOrderMessage(MessageExt message) {
// 批量处理逻辑
OrderEvent event = JSON.parseObject(message.getBody(), OrderEvent.class);
orderService.processOrderEvent(event);
}
}
@Configuration
public class ThreadPoolConfig {
@Bean("orderProcessor")
public ThreadPoolTaskExecutor orderProcessor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(100);
executor.setQueueCapacity(1000);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("order-processor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
解决方案二:消费速度监控与动态扩容
java
@Component
@Slf4j
public class MessageBacklogMonitor {
@Autowired
private RocketMQAdmin rocketMQAdmin;
@Autowired
private KubernetesClient kubernetesClient;
/**
* 监控消息堆积并自动扩容
*/
@Scheduled(fixedRate = 60000) // 每分钟检查
public void monitorAndScale() {
// 获取所有消费组的堆积情况
List<ConsumerGroupInfo> groups = rocketMQAdmin.queryAllConsumerGroups();
for (ConsumerGroupInfo group : groups) {
long backlog = calculateBacklog(group);
int consumerCount = getConsumerCount(group);
log.info("消费组 {} 堆积: {}, 消费者数量: {}",
group.getGroup(), backlog, consumerCount);
// 根据堆积情况调整消费者数量
if (backlog > 10000) {
// 严重堆积,扩容
scaleConsumerGroup(group.getGroup(), consumerCount + 2);
} else if (backlog < 1000 && consumerCount > 1) {
// 堆积很少,缩容
scaleConsumerGroup(group.getGroup(), consumerCount - 1);
}
}
}
/**
* 动态调整消费者 Pod 数量
*/
private void scaleConsumerGroup(String groupName, int targetReplicas) {
String deploymentName = "consumer-" + groupName;
// 更新 Kubernetes Deployment 副本数
kubernetesClient.apps().deployments()
.inNamespace("default")
.withName(deploymentName)
.scale(targetReplicas);
log.info("调整消费组 {} 的消费者数量为: {}", groupName, targetReplicas);
}
}
2.4 顺序消息处理
解决方案:队列选择和键分区
java
@Component
@Slf4j
public class OrderSequentialService {
/**
* 发送顺序消息(按订单ID分区)
*/
public void sendSequentialMessage(OrderEvent event) {
// 使用订单ID作为消息Key,确保同一订单的消息发送到同一分区
rocketMQTemplate.syncSendOrderly(
"order-sequential-topic",
event,
event.getOrderId() // 消息队列根据此key选择队列
);
}
}
// 顺序消息消费者
@Component
@RocketMQMessageListener(
topic = "order-sequential-topic",
consumerGroup = "order-sequential-group",
consumeMode = ConsumeMode.ORDERLY // 顺序消费模式
)
@Slf4j
public class OrderSequentialConsumer implements RocketMQListener<OrderEvent> {
// 使用本地队列处理同一订单的消息
private final ConcurrentHashMap<String, LinkedBlockingQueue<OrderEvent>> orderQueues =
new ConcurrentHashMap<>();
@Override
public void onMessage(OrderEvent event) {
String orderId = event.getOrderId();
// 获取或创建该订单的处理队列
LinkedBlockingQueue<OrderEvent> queue = orderQueues.computeIfAbsent(
orderId, k -> new LinkedBlockingQueue<>()
);
try {
// 将消息放入队列
queue.put(event);
// 如果队列只有一个消息,启动处理线程
if (queue.size() == 1) {
processOrderQueue(orderId, queue);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("处理顺序消息中断: {}", orderId, e);
}
}
private void processOrderQueue(String orderId, LinkedBlockingQueue<OrderEvent> queue) {
CompletableFuture.runAsync(() -> {
try {
while (!queue.isEmpty()) {
OrderEvent event = queue.peek(); // 查看但不移除
// 处理消息
orderService.processSequentialEvent(event);
// 处理成功,移除消息
queue.poll();
}
} finally {
// 处理完成后清理队列
orderQueues.remove(orderId);
}
});
}
}
三、生产环境最佳实践
3.1 监控告警体系
java
@Component
@Slf4j
public class MQMonitorService {
@Autowired
private PrometheusMeterRegistry meterRegistry;
@Autowired
private AlertService alertService;
// 监控指标
private final Counter messageSentCounter;
private final Counter messageConsumedCounter;
private final Gauge messageBacklogGauge;
private final Timer messageProcessTimer;
public MQMonitorService() {
messageSentCounter = Counter.builder("mq_message_sent_total")
.description("Total messages sent")
.register(meterRegistry);
messageConsumedCounter = Counter.builder("mq_message_consumed_total")
.description("Total messages consumed")
.register(meterRegistry);
messageBacklogGauge = Gauge.builder("mq_message_backlog")
.description("Message backlog count")
.register(meterRegistry);
messageProcessTimer = Timer.builder("mq_message_process_duration")
.description("Message processing duration")
.register(meterRegistry);
}
/**
* 监控消息处理
*/
public <T> T monitorProcess(String operation, Supplier<T> processor) {
return messageProcessTimer.record(() -> {
try {
T result = processor.get();
messageConsumedCounter.increment();
return result;
} catch (Exception e) {
// 记录失败指标
meterRegistry.counter("mq_message_process_failed_total").increment();
throw e;
}
});
}
/**
* 检查堆积并告警
*/
@Scheduled(fixedRate = 30000)
public void checkBacklogAndAlert() {
long backlog = calculateTotalBacklog();
messageBacklogGauge.set(backlog);
// 阈值告警
if (backlog > 10000) {
alertService.sendAlert(
"MQ_BACKLOG_HIGH",
"消息堆积严重,当前堆积: " + backlog,
AlertLevel.CRITICAL
);
} else if (backlog > 1000) {
alertService.sendAlert(
"MQ_BACKLOG_WARN",
"消息堆积警告,当前堆积: " + backlog,
AlertLevel.WARNING
);
}
// 检查消费延迟
long avgDelay = calculateAverageDelay();
if (avgDelay > 60000) { // 超过1分钟
alertService.sendAlert(
"MQ_CONSUME_DELAY",
"消息消费延迟过高,平均延迟: " + avgDelay + "ms",
AlertLevel.WARNING
);
}
}
/**
* 健康检查
*/
@GetMapping("/health/mq")
public Health checkMQHealth() {
try {
// 检查连接
boolean connected = checkMQConnection();
// 检查堆积
long backlog = calculateTotalBacklog();
Health.Builder builder = Health.up();
builder.withDetail("connected", connected);
builder.withDetail("backlog", backlog);
builder.withDetail("timestamp", LocalDateTime.now());
if (backlog > 50000) {
builder.down().withDetail("reason", "消息堆积过多");
}
if (!connected) {
builder.down().withDetail("reason", "MQ连接失败");
}
return builder.build();
} catch (Exception e) {
return Health.down(e).build();
}
}
}
3.2 灾备与容灾方案
java
@Component
@Slf4j
public class MQDisasterRecovery {
@Autowired
private PrimaryMQService primaryMQ;
@Autowired
private BackupMQService backupMQ;
private boolean primaryAvailable = true;
private final AtomicInteger failCount = new AtomicInteger(0);
/**
* 自动故障切换
*/
public void sendWithFailover(String topic, Object message) {
if (primaryAvailable) {
try {
primaryMQ.send(topic, message);
// 发送成功,重置失败计数
failCount.set(0);
} catch (Exception e) {
// 主MQ失败
int fails = failCount.incrementAndGet();
log.error("主MQ发送失败,失败次数: {}", fails, e);
if (fails >= 3) {
// 连续失败3次,切换到备MQ
switchToBackup();
}
// 尝试使用备MQ发送
backupMQ.send(topic, message);
}
} else {
// 已切换到备MQ
backupMQ.send(topic, message);
}
}
/**
* 切换到备份MQ
*/
private synchronized void switchToBackup() {
if (primaryAvailable) {
primaryAvailable = false;
log.warn("切换到备份MQ");
// 发送切换通知
alertService.sendAlert("MQ_SWITCH", "切换到备份MQ", AlertLevel.WARNING);
// 启动健康检查线程,尝试恢复主MQ
startPrimaryRecoveryCheck();
}
}
/**
* 尝试恢复主MQ
*/
private void startPrimaryRecoveryCheck() {
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(() -> {
try {
boolean recovered = primaryMQ.checkHealth();
if (recovered) {
primaryAvailable = true;
failCount.set(0);
log.info("主MQ恢复成功,切换回主MQ");
scheduler.shutdown();
}
} catch (Exception e) {
log.debug("主MQ恢复检查失败", e);
}
}, 30, 60, TimeUnit.SECONDS); // 30秒后开始,每60秒检查一次
}
/**
* 双向同步(主备消息同步)
*/
@Component
public class MQReplicationService {
/**
* 主到备的消息同步
*/
@RabbitListener(queues = "primary.queue")
public void replicateToBackup(Message message) {
// 消费主MQ消息的同时,转发到备MQ
backupMQ.send("replicated." + message.getMessageProperties().getReceivedRoutingKey(),
message.getBody());
log.debug("消息同步到备份MQ: {}", message.getMessageProperties().getMessageId());
}
/**
* 备到主的消息同步(故障恢复后)
*/
@RabbitListener(queues = "backup.queue")
public void replayToPrimary(Message message) {
if (primaryAvailable) {
// 将备份期间的消息回放到主MQ
primaryMQ.send("replay." + message.getMessageProperties().getReceivedRoutingKey(),
message.getBody());
log.info("消息回放到主MQ: {}", message.getMessageProperties().getMessageId());
}
}
}
3.3 消息治理平台
java
// 消息轨迹追踪
@Component
@Aspect
@Slf4j
public class MessageTraceAspect {
@Autowired
private TraceStorageService traceStorage;
@Pointcut("@annotation(org.springframework.amqp.rabbit.annotation.RabbitListener)")
public void rabbitListenerPointcut() {}
@Pointcut("@annotation(org.apache.rocketmq.spring.annotation.RocketMQMessageListener)")
public void rocketMQListenerPointcut() {}
@Around("rabbitListenerPointcut() || rocketMQListenerPointcut()")
public Object traceMessage(ProceedingJoinPoint joinPoint) throws Throwable {
Message message = extractMessage(joinPoint.getArgs());
String traceId = generateTraceId();
MessageTrace trace = new MessageTrace();
trace.setTraceId(traceId);
trace.setMessageId(message.getMessageId());
trace.setTopic(getTopic(message));
trace.setConsumer(getConsumerName(joinPoint));
trace.setStartTime(LocalDateTime.now());
try {
// 记录开始
traceStorage.saveTrace(trace);
// 执行业务
Object result = joinPoint.proceed();
// 记录成功
trace.setStatus("SUCCESS");
trace.setEndTime(LocalDateTime.now());
trace.setDuration(trace.getEndTime().toEpochSecond(ZoneOffset.UTC) -
trace.getStartTime().toEpochSecond(ZoneOffset.UTC));
traceStorage.saveTrace(trace);
return result;
} catch (Exception e) {
// 记录失败
trace.setStatus("FAILED");
trace.setError(e.getMessage());
trace.setEndTime(LocalDateTime.now());
traceStorage.saveTrace(trace);
throw e;
}
}
}
// 消息查询界面
@RestController
@RequestMapping("/api/message")
@Slf4j
public class MessageQueryController {
@Autowired
private MessageQueryService queryService;
/**
* 根据消息ID查询
*/
@GetMapping("/{messageId}")
public MessageTraceDTO getMessageTrace(@PathVariable String messageId) {
return queryService.getMessageTrace(messageId);
}
/**
* 查询失败消息
*/
@GetMapping("/failed")
public PageResult<FailedMessageDTO> getFailedMessages(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return queryService.getFailedMessages(page, size);
}
/**
* 重新发送失败消息
*/
@PostMapping("/{messageId}/retry")
public ApiResponse<String> retryFailedMessage(@PathVariable String messageId) {
boolean success = queryService.retryMessage(messageId);
return success ?
ApiResponse.success("重发成功") :
ApiResponse.error("重发失败");
}
/**
* 消息统计
*/
@GetMapping("/statistics")
public MessageStatisticsDTO getStatistics(
@RequestParam(required = false) String topic,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date) {
return queryService.getStatistics(topic, date);
}
}
四、实战经验总结
4.1 性能优化建议
-
批量发送:合并小消息,减少网络IO
javapublic void batchSendMessages(List<Message> messages) { // 每100条批量发送 int batchSize = 100; for (int i = 0; i < messages.size(); i += batchSize) { List<Message> batch = messages.subList(i, Math.min(i + batchSize, messages.size())); kafkaTemplate.send(batch); } } -
压缩消息:对大消息进行压缩
java@Component public class MessageCompressionService { public byte[] compressMessage(Object message) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (GZIPOutputStream gzip = new GZIPOutputStream(baos)) { gzip.write(JSON.toJSONBytes(message)); } return baos.toByteArray(); } public <T> T decompressMessage(byte[] compressed, Class<T> clazz) throws IOException { ByteArrayInputStream bais = new ByteArrayInputStream(compressed); try (GZIPInputStream gzip = new GZIPInputStream(bais)) { byte[] decompressed = gzip.readAllBytes(); return JSON.parseObject(decompressed, clazz); } } }4.2 成本控制策略
-
消息生命周期管理
java@Component @Slf4j public class MessageLifecycleManager { @Autowired private RocketMQAdmin rocketMQAdmin; /** * 清理过期消息 */ @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行 public void cleanupExpiredMessages() { // 清理3天前的消息 LocalDateTime expireTime = LocalDateTime.now().minusDays(3); // 清理各topic的过期消息 List<String> topics = rocketMQAdmin.getAllTopics(); for (String topic : topics) { if (shouldCleanup(topic)) { rocketMQAdmin.cleanupExpiredMessages(topic, expireTime); log.info("清理topic过期消息: {}", topic); } } } /** * 消息归档(转移到冷存储) */ public void archiveOldMessages(String topic, LocalDateTime beforeTime) { List<Message> oldMessages = messageStorageService.findMessagesBefore(topic, beforeTime); // 批量归档到S3/HDFS archiveService.archiveToColdStorage(oldMessages); // 从MQ删除已归档消息 messageStorageService.deleteMessages(oldMessages); log.info("归档消息完成,数量: {}", oldMessages.size()); } }4.3 团队协作规范
-
java
# mq-usage-standard.yaml
mq_usage_standard:
# 命名规范
naming_conventions:
topic: "{业务域}.{事件类型}.{环境}"
consumer_group: "{应用名}.{topic}.consumer.{环境}"
tag: "{动作}.{对象}"
# 消息大小限制
message_size:
max_size: "1MB"
compress_threshold: "100KB"
# 监控要求
monitoring:
required_metrics:
- sent_count
- consume_count
- backlog_size
- process_duration
- error_rate
alert_thresholds:
backlog: 10000
error_rate: "1%"
delay: "30s"
# 文档要求
documentation:
required_fields:
- topic_description
- message_schema
- producer_applications
- consumer_applications
- sla_requirements
- failure_handling
五、总结
5.1 关键原则
-
消息可靠性:确保不丢消息是第一位
-
消费幂等性:至少消费一次是常态
-
监控可观测:没有监控的MQ就是定时炸弹
-
容量规划:提前规划,避免堆积
5.2 技术选型参考
| 场景 | 推荐MQ | 关键特性 |
|---|---|---|
| 金融交易 | RocketMQ | 事务消息、顺序消息、高可靠 |
| 日志收集 | Kafka | 高吞吐、持久化、分区 |
| 实时通知 | RabbitMQ | 灵活路由、低延迟 |
| IoT数据 | Pulsar | 多租户、分层存储 |
| 简单任务 | Redis Stream | 轻量级、易用 |
5.3 持续优化方向
-
智能化调优:基于AI预测消息量,动态调整资源
-
跨云部署:多云MQ集群,避免云厂商锁定
-
Serverless MQ:按量付费,自动扩缩容
-
绿色计算:优化资源使用,降低能耗
MQ是现代分布式系统的基石,合理使用MQ可以显著提升系统解耦度、可靠性和扩展性。但同时也需要投入足够精力进行监控、维护和优化,才能让MQ真正成为业务的加速器而非瓶颈。