MQ 业务实际使用与问题处理详解

一、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 性能优化建议

  1. 批量发送:合并小消息,减少网络IO

    java 复制代码
    public 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);
        }
    }
  2. 压缩消息:对大消息进行压缩

    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 成本控制策略

    1. 消息生命周期管理

      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 关键原则

  1. 消息可靠性:确保不丢消息是第一位

  2. 消费幂等性:至少消费一次是常态

  3. 监控可观测:没有监控的MQ就是定时炸弹

  4. 容量规划:提前规划,避免堆积

5.2 技术选型参考

场景 推荐MQ 关键特性
金融交易 RocketMQ 事务消息、顺序消息、高可靠
日志收集 Kafka 高吞吐、持久化、分区
实时通知 RabbitMQ 灵活路由、低延迟
IoT数据 Pulsar 多租户、分层存储
简单任务 Redis Stream 轻量级、易用

5.3 持续优化方向

  1. 智能化调优:基于AI预测消息量,动态调整资源

  2. 跨云部署:多云MQ集群,避免云厂商锁定

  3. Serverless MQ:按量付费,自动扩缩容

  4. 绿色计算:优化资源使用,降低能耗

MQ是现代分布式系统的基石,合理使用MQ可以显著提升系统解耦度、可靠性和扩展性。但同时也需要投入足够精力进行监控、维护和优化,才能让MQ真正成为业务的加速器而非瓶颈。

相关推荐
kylezhao20193 小时前
第三节、C# 上位机面向对象编程详解(工控硬件封装实战版)
开发语言·前端·c#
散峰而望3 小时前
【算法竞赛】C++入门(三)、C++输入输出初级 -- 习题篇
c语言·开发语言·数据结构·c++·算法·github
kingwebo'sZone3 小时前
c# 遍历 根据控件名获取控件实例
开发语言·c#
星空椰3 小时前
jvms Java 版本管理工具
java·开发语言
REDcker3 小时前
C++ 崩溃堆栈捕获库详解
linux·开发语言·c++·tcp/ip·架构·崩溃·堆栈
qq_406176143 小时前
JavaScript闭包:从底层原理到实战
开发语言·前端·javascript
沐知全栈开发3 小时前
`.toggleClass()` 方法详解
开发语言
Rysxt_3 小时前
鸿蒙开发语言ArkTS全面介绍
开发语言·华为·harmonyos
三天不学习3 小时前
【入门教学】Python包管理与pip常用包
开发语言·python·pip