真实系统优化:RocketMQ消息积压问题解决

真实系统优化:RocketMQ消息积压问题解决

在大规模分布式系统中,消息队列(MQ)是实现解耦和异步处理的关键组件,但在实际应用中,消息积压问题常常会成为系统稳定性的瓶颈。本文将探讨如何在生产环境中解决 RocketMQ 消息积压问题,特别是在多种系统和服务交互中,如何优化消息消费效率,确保系统的高可用性和稳定性。

生产中MQ场景

在我们的系统中,RocketMQ主要用于:

  • IDM身份认证系统同步档案与权限数据(消费者与发送者):和IDM身份认证系统实现数据同步,如经销商、分销商、业务员等。IDM系统较为稳定,只需要注意消费的顺序性即可,在之前的文章中已经解决了。
  • 和门店运营平台同步门店数据(消费者):消息体过大,往往门店信息包含许多属性信息(门店联系人、事业部、历史版本、流程相关数据等),并且门店运营平台不太稳定,有时会出现系统问题造成消息无法及时消费
  • 和数据平台同步订单信息(消费者):消息多,如果因系统不稳定,那么就会造成需要重试发送消息的数量大
  • 和SAP基础信息平台同步(发送者):同步除了订单外所有档案信息、所有物料信息,消息内容不大、量不大、但是要保证实时性,SAP系统很稳定,对方基本不出错。

问题分析与总结

我们要解决的问题:

  1. 即使消费者平台不稳定,消息也不会被过度积压。
  2. 不会因为消息积压导致我们的系统崩溃或者性能问题。
  3. 直接通过异步、重试等机制确保消息的可靠消费

总结问题:

  • 和门店运营平台同步门店数据:容易造成消息挤压
  • 和数据平台同步订单信息:消息数量大,容易造成消息挤压
  • 和SAP基础信息平台同步:要保证实时性与准确性。

解决方案

门店与订单方面解决方案:同步发送单个数据+定时任务重试+超过重试5次前端页面管理重试+预留批量重试接口

在实际业务中,特别是门店与订单数据同步的场景中,消费者(数据平台)不稳定时,使用传统的消息队列(MQ)方案可能会导致消息积压,影响系统稳定性。针对这个问题,提出了 异步发送同步数据 + 定时任务重试 + 前端页面管理重试 + 预留批量重试接口 的方案,来确保数据同步的可靠性、实时性,同时保证系统稳定性。

  • 由于数据平台是消费者,并且他们不太稳定,那么就不适合用MQ了吧,即使使用了很好的消息重试机制,但是也会造成我方消息的积压,所以就用异步发送同步数据+定时任务重试+超过重试5次前端页面管理重试+预留批量重试接口的机制保障数据的同时也保护我们系统的稳定性
  • 和门店运营平台同步门店数据商量着做数据拆分,但是不是我跟紧的,后面就先也改成异步发送同步数据+定时任务重试+超过重试5次前端页面管理重试+预留批量重试接口的方案了,要先保证业务性,因为后面可能门店运营平台会直接接入到我们的系统。

大体实现步骤如下:

1. 异步发送同步数据

利用异步编程模型,采用 @Async 注解实现异步处理,可以避免同步操作阻塞业务逻辑,并确保数据能够尽早发送。

异步发送订单数据(pushRetailOrder 方法)

@Async
public Boolean pushRetailOrder(Long orderId) {
    // 记录接口消息
    IntegrateRecordLine integrateRecordLine = integrateRecordHeadService.recordApi(IntegrateConstants.PUSH_RETAIL_ORDER, orderId + "", "", IntegrateConstants.INTEGRATE_OUT, null, IntegrateConstants.STR_TWO);
    
    // 触发计算工资
    sendRetailOrder(orderId);
    
    try {
        // 获取零售订单信息
        BaseResponse<RetailOrderRespVM> baseResponse = retailOrderFacade.findRetailOrder(orderId);
        
        if (baseResponse.isSuccess() && baseResponse.getContent() != null) {
            RetailOrderRespVM retailOrderRespVM = baseResponse.getContent();
            
            if (retailOrderRespVM.getItemList().size() > 0) {
                String jsonReq = FastjsonUtil.toNullAsEmpty(retailOrderRespVM);
                log.info("pushRetailOrder:{}", jsonReq);
                
                // 推送数据到目标系统
                boService.sendRetailOrder(FastjsonUtil.toNullAsEmpty(Arrays.asList(retailOrderRespVM)), orderId);
            }

            // 更新接口记录状态为成功
            integrateRecordHeadService.updateRecordMsg(integrateRecordLine.getIntegrateRecordLineId(), integrateRecordLine.getIntegrateRecordHeadId(), IntegrateConstants.STATUS_SUC, "接受到零售订单推送, 请查阅其他接口推送是否成功");
        } else {
            // 更新接口记录状态为失败
            integrateRecordHeadService.updateRecordMsg(integrateRecordLine.getIntegrateRecordLineId(), integrateRecordLine.getIntegrateRecordHeadId(), IntegrateConstants.STATUS_FAIL, baseResponse.getChnDesc());
        }
    } catch (Exception e) {
        // 捕获异常并更新记录状态为失败
        integrateRecordHeadService.updateRecordMsg(integrateRecordLine.getIntegrateRecordLineId(), integrateRecordLine.getIntegrateRecordHeadId(), IntegrateConstants.STATUS_FAIL, e.getMessage());
    }

    return Boolean.TRUE;
}

关键点

  • 使用 @Async 注解异步执行 pushRetailOrder 方法,避免同步调用带来的阻塞。
  • 通过 retailOrderFacade.findRetailOrder(orderId) 获取订单信息,并通过 boService.sendRetailOrder 进行数据推送。
  • 使用 integrateRecordHeadService 记录推送状态,确保推送成功后能更新记录状态,便于后续追踪。

2. 定时任务重试

为了处理因目标平台不稳定导致的消息发送失败,方案引入定时任务来进行消息重试。假设消费平台暂时不可用,系统通过定时任务进行消息重试,确保系统的高可靠性。

@Slf4j
@Component
public class RetailPushJob extends AbstractJob {

    @Autowired
    private ZookeeperRegistryCenter zookeeperRegistryCenter;


    @Autowired
    private RetailOrderService retailOrderService;

    public InsurancePushJob(
            @Value("${elasticJob.retailPushJob.cron}") String cron,
            @Value("${elasticJob.retailPushJob.shardingTotalCount}") Integer shardingTotalCount,
            @Value("${elasticJob.retailPushJob.shardingItemParameters}") String shardingItemParameters,
            @Value("${elasticJob.retailPushJob.jobParameter}") String jobParameter) {
        super(cron, shardingTotalCount, shardingItemParameters, jobParameter);
    }

    @PostConstruct
    public void init() {

        super.start(zookeeperRegistryCenter);
    }

    @Override
    public void executeJob(Integer shardingTotalCount, Integer shardingItem, String itemParameter, String jobParameter) {
        RetailOrderSearchDTO request=new RetailOrderSearchDTO();
        request.setUpdateDate(LocalDate.now());
        retailOrderService.rePushRetailOrder(request);
    }
}

3. 前端管理重试

如果消息推送在系统中多次失败,可以通过前端页面提供管理界面,让用户主动进行重试操作。

前端可以提供一个 “重试” 按钮,用户点击后调用后台的重试接口。后台通过 retryFailedOrders 或专门的接口重新发送失败的消息。

@RequestMapping("/retryOrder")
public String retryFailedOrder(@RequestParam Long orderId) {
    IntegrateRecordLine recordLine = integrateRecordHeadService.getRecordByOrderId(orderId);
    
    if (recordLine != null && recordLine.getStatus().equals(IntegrateConstants.STATUS_FAIL)) {
        try {
            // 尝试重新推送消息
            pushRetailOrder(orderId);
            return "重试成功";
        } catch (Exception e) {
            return "重试失败:" + e.getMessage();
        }
    }
    
    return "没有找到失败的订单记录";
}

关键点

  • 通过前端提供重试功能,用户可以主动触发失败记录的消息重试。
  • 重试结果会直接返回到前端,用户可以根据返回的消息进行相应的操作。

4. 预留批量重试接口

为了提升效率,在某些情况下可能需要批量处理消息。可以提供批量重试接口,允许一次性重试多个失败的订单。

@RequestMapping("/batchRetryOrders")
public String batchRetryOrders(@RequestParam List<Long> orderIds) {
    for (Long orderId : orderIds) {
        try {
            // 重新推送每个失败的订单
            pushRetailOrder(orderId);
        } catch (Exception e) {
            return "批量重试失败,订单ID: " + orderId + ",错误:" + e.getMessage();
        }
    }
    return "批量重试成功";
}

关键点

  • 允许批量传入订单ID,后台一次性尝试重试多个失败订单。
  • 处理每个订单的推送失败时返回错误信息,保证批量操作的灵活性。

总结

通过 异步发送同步数据定时任务重试前端页面管理重试预留批量重试接口,我们能够确保门店与订单数据同步过程的实时性与准确性。具体来说:

  • 异步发送订单数据,确保不会阻塞系统的主流程。
  • 通过定时任务进行消息的重试,处理消费者平台的不稳定。
  • 提供前端重试功能和批量重试接口,提升系统的可靠性与用户的灵活性。

这种方案保证了数据同步的高可靠性,并且避免了消息积压,同时能够有效应对消费者系统的不稳定性。

SAP基础信息解决方案:并发消费+消费重试5次+定时任务重试+超过重试5次前端页面管理重试+预留批量重试接口

为了保障从 SAP基础信息平台 消费消息的实时性与准确性,涵盖消息消费的可靠性、实时性、准确性以及异常处理。

配置 RocketMQ 的相关参数,确保消费者能够正确连接到 RocketMQ 服务,并且开启消息跟踪功能:通过记录和追踪每条消息的流转过程,帮助开发人员分析消息的生命周期、状态变更及故障排查。

spring:
  cloud:
    alibaba:
      rocketmq:
        name-server: xxx:9876 # RocketMQ NameServer 地址
        consumer:
          group: SAP_CONSUMER_GROUP
          # 确保消息的持久化和可靠性
          enable-msg-trace: true
          max-reconsume-times: 5 # 最大重试次数
          retry-times-when-consume-failed: 5 # 重试次数
        producer:
          group: SAP_PRODUCER_GROUP

定义一个消费监听器,消费来自 SAP 基础信息平台的消息。

@Component
@MessageListener(consumerGroup = "SAP_CONSUMER_GROUP", topic = "SAP_TOPIC", selectorExpression = "*", consumeMode = ConsumeMode.ORDERLY)
public class SAPMessageListener implements RocketMQListener<MessageExt> {

    // 订单同步的最大重试次数
    private static final int MAX_RETRY_TIMES = 5;

    @Override
    @Transactional
    public void onMessage(MessageExt message) {
        String messageBody = new String(message.getBody());
        JSONObject messageJson = JSONObject.parseObject(messageBody);

        // 处理消息
        processSAPMessage(messageJson);

        // 如果消费失败,重试逻辑
        boolean success = false;
        int retryCount = 0;
        while (!success && retryCount < MAX_RETRY_TIMES) {
            try {
                // 数据同步逻辑同步至数据库
                syncToDatabase(messageJson);
                success = true; // 同步成功
            } catch (Exception e) {
                retryCount++;
                if (retryCount == MAX_RETRY_TIMES) {
                    // 达到最大重试次数,记录失败并发送到死信队列
                    sendToDeadLetterQueue(message);
                }
            }
        }
    }

    private void processSAPMessage(JSONObject messageJson) {
        // 根据业务需求处理消息
        // 比如,解析消息中的 SAP 订单信息、档案数据等
        System.out.println("处理 SAP 消息: " + messageJson);
    }

    private void syncToDatabase(JSONObject messageJson) {
        // 将消息数据同步到数据库
        // 在此执行同步操作,假设是同步订单数据到本地数据库
        // 例如:orderRepository.save(order);
        System.out.println("将数据同步到数据库: " + messageJson);
    }

    private void sendToDeadLetterQueue(MessageExt message) {
        // 将无法消费的消息发送到死信队列进行人工处理
        System.out.println("消息消费失败,发送到死信队列: " + message);
        // 实现消息发送到死信队列的逻辑
    }
}

通过配置 RocketMQ 消费者的并发消费模式,可以提高消息消费的吞吐量。

spring:
  cloud:
    alibaba:
      rocketmq:
        consumer:
          consumeMode: ORDERLY  # 顺序消费
          # 配置并发消费
          max-thread: 16

如果消息量较大,可以使用批量消费的方式来提高性能。RocketMQ 支持批量消费,可以减少消费频率,提高系统吞吐量。

@MessageListener(consumerGroup = "SAP_CONSUMER_GROUP", topic = "SAP_TOPIC", consumeMode = ConsumeMode.BROADCASTING)
public class BatchSAPMessageListener implements RocketMQListener<List<MessageExt>> {
    @Override
    public void onMessage(List<MessageExt> messages) {
        // 批量处理消息
        for (MessageExt message : messages) {
            processSAPMessage(message);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清河大善人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值