分布式微服务系统架构第94集:Kafka 消费监听处理类,redisson延时队列

加群联系作者vx:xiaoda0423

仓库地址:https://webvueblog.github.io/JavaPlusDoc/

https://1024bat.cn/

go 复制代码
public void handleAccessEvent(ConsumerRecord<?, ?> recordMsg, Acknowledgment ack) {
        long start = System.currentTimeMillis();
        List<ConsumerRecord<?, ?>> records = Collections.singletonList(recordMsg);
        String timeStr = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
        handleBiz(records, start, timeStr);
        ack.acknowledge(); // 手动提交偏移量,确保消息处理完毕
        log.info("==>设备数据-柜子事件处理耗时: {}ms", System.currentTimeMillis() - start);
    }
    
    public void handleBiz(List<ConsumerRecord<?, ?>> records, long countTime, String timeStr) {
        for (ConsumerRecord<?, ?> record : records) {
            try {
                String value = record.value().toString();
优化点 说明
幂等性处理 若多次消费同一订单,确保不会重复写入(订单状态比对)
异步执行更新 bizOrderService.setRenewEndOrder() 可使用线程池异步提高性能
延迟队列支持 若逾期处理需延迟精确执行,可用 Kafka 延迟消息或 Redis 延时任务
Prometheus监控指标 上报处理成功数、失败数、处理耗时等,用于高可用监控
消费线程隔离 可将不同事件类型拆分不同消费者容器,提升隔离性与扩展性
go 复制代码
/**
 * Kafka 消费监听 - 处理订单逾期事件
 */
@KafkaListener(
    topics = KafkaConstant.ORDER_OVERDUE_TOPIC,
    containerFactory = "bizBRConsumerFactory",
    properties = {"max.poll.records = 100"} // 每次最多拉取 100 条,提升吞吐量
)
public void handle(List<ConsumerRecord<String, String>> records, Acknowledgment ack) {
    if (CollectionUtils.isEmpty(records)) {
        return;
    }

    String dateTime = LocalDateTime.now().format(inputFormatter); // 当前时间格式化
    List<String> failedOrders = new ArrayList<>(); // 记录失败订单 ID 以便后续排查

    for (ConsumerRecord<String, String> record : records) {
        // 空数据直接跳过
        if (StringUtils.isBlank(record.value())) {
            continue;
        }

        try {
            log.info("[Kafka-订单逾期] 接收到原始消息:{}", record.value());

            // 反序列化消息为事件对象
            OrderOverdueEvent overdueEvent = GsonUtils.getObjectFromJson(record.value(), OrderOverdueEvent.class);
            if (overdueEvent == null || StringUtils.isBlank(overdueEvent.getOrderId())) {
                log.warn("[Kafka-订单逾期] 消息格式异常:{}", record.value());
                continue;
            }

            BExchSvcOrder order = bExchSvcOrderService.selectById(overdueEvent.getOrderId());
            if (!updatePreCheck(order)) {
                continue;
            }

            // 标记逾期延迟(内部逻辑需要)
            order.setOverdueDelay(true);

            // 首先尝试通过续费接口完结
            BExchSvcOrder result = bizOrderService.setRenewEndOrder(order, dateTime);
            if (result == null) {
                // 若续费接口返回为空,说明未续费成功,则直接设置为逾期状态
                log.info("[Kafka-订单逾期] 订单逾期,未续费完结,准备状态更新,订单:{}", JacksonUtils.toJson(order));

                BExchSvcOrderBO updateBO = BExchSvcOrderBO.builder()
                        .orderId(order.getOrderId())
                        .oStatus(OrderConstant.OVERDUE) // 设置订单状态为 OVERDUE
                        .overdueDelay(true)
                        .updType(OrderConstant.CHANGEBAT)
                        .build();

                // 更新数据库状态
                bExchSvcOrderService.update(updateBO);

            }

        } catch (Exception ex) {
            // 单条处理失败,继续处理下一条,不影响整体消费
            String failedId = record.value();
            log.error("[Kafka-订单逾期] 处理失败,订单原始内容:{}", failedId, ex);
            failedOrders.add(failedId);
        }
    }

    // 所有消息处理完成后统一提交 offset
    ack.acknowledge();

    if (!failedOrders.isEmpty()) {
        log.warn("[Kafka-订单逾期] 以下订单处理失败需排查:{}", failedOrders);
    }
}
  1. 配置优化

    properties

    go 复制代码
    # 消费者配置
    spring.kafka.consumer.max-poll-records=500
    spring.kafka.listener.concurrency=3
    spring.kafka.listener.poll-timeout=5000
    
    # 线程池配置
    io.executor.corePoolSize=10
    io.executor.maxPoolSize=20
    io.executor.queueCapacity=1000
  2. 监控增强

  • 添加Metrics指标(处理速率、耗时分布等)

  • 实现Circuit Breaker模式(如Resilience4j)

  • 关键操作添加TraceID实现全链路追踪

  • 数据一致性

    • 实现幂等处理(通过eventId去重)

    • 使用事务消息保证最终一致性

    • 添加死信队列处理无法消费的消息

  • 压力测试

    • 使用JMeter进行不同批量大小的性能测试

    • 调整线程池参数找到最优配置

    • 测试故障恢复能力(如ES宕机时的重试机制)

    • ✅ 使用线程池替代单线程消费,提高并发能力;

    • ✅ 增强可配置性和灵活性(比如线程数量、阻塞时间);

    • ✅ 异常处理更加细粒度;

    • ✅ 避免重复初始化;

    • ✅ 增强日志和可追踪性;

    go 复制代码
    /**
     * 抽象延迟消息处理器,支持 Redisson 延迟队列消费
     * 适用于高并发、可扩展的异步延迟消息处理场景
     */
    public abstract class xxx<T> implements xxx<T> {
    
        private static final Logger log = LoggerFactory.getLogger(AbstractDelayMessageHandler.class);
    
        private static final int DEFAULT_POLL_SLEEP_TIME = 5000; // 默认阻塞队列轮询等待时间(毫秒)
    
        @Autowired
        private RedissonClient redissonClient; // 注入 Redisson 客户端,用于操作延迟队列
    
        private RBlockingDeque<T> blockingDeque; // Redisson 阻塞队列
        private RDelayedQueue<T> delayedQueue;   // Redisson 延迟队列
        
        // 消费标志位,控制线程是否运行
        // 是否启用消费者,配置优先级高
        
        @Value("${redisson.delay.consumer.enable:false}")
        protected boolean enable; // 是否启用消费者,配置优先级高
    
        @Value("${redisson.delay.consumer.threads:2}")
        private int consumerThreads; // 消费者线程数,默认2个,可通过配置覆盖
    
        @Value("${redisson.delay.consumer.pollTimeout:5000}")
        private long pollTimeoutMs; // 轮询延迟消息的超时时间,默认5000ms
    
        private ExecutorService consumerExecutor; // 消费线程池
        
        // 阻塞拉取延迟消息(支持设置超时时间)
        message = blockingDeque.poll(pollTimeoutMs, TimeUnit.MILLISECONDS);
    
        if (Objects.isNull(message)) {
            continue;
        }
    
        // 过滤空消息
        if ((message instanceof String) && StringUtils.isBlank((String) message)) {
            continue;
        }
    
        log.info("消费延迟队列:{},接收消息:{}", queueName(), message);
    
        // 处理延迟消息(交由子类实现)
        this.handle(message);

    ✅ 延迟场景 1:优惠券即将过期提醒(提前1小时提醒)

    实体类 CouponExpireReminderMessage.java

    go 复制代码
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class CouponExpireReminderMessage implements Serializable {
        private static final long serialVersionUID = 1L;
    
        private String userId;
        private String couponId;
        private String couponName;
        private String expireTime; // 格式:yyyy-MM-dd HH:mm:ss
    }

    延迟队列处理器 CouponExpireReminderHandler.java

    go 复制代码
    @Component
    public class CouponExpireReminderHandler extends AbstractDelayMessageHandler<CouponExpireReminderMessage> {
    
        private static final Logger log = LoggerFactory.getLogger(CouponExpireReminderHandler.class);
    
        @Override
        protected String queueName() {
            return"coupon:expire:remind:delay:queue";
        }
    
        @Override
        protected void handle(CouponExpireReminderMessage message) {
            try {
                log.info("【优惠券过期提醒】用户:{},券:{}({}),过期时间:{}",
                        message.getUserId(), message.getCouponId(), message.getCouponName(), message.getExpireTime());
    
                // 示例:通过站内信或推送系统通知用户
                // notificationService.sendExpireReminder(message.getUserId(), message.getCouponName());
    
            } catch (Exception e) {
                log.error("处理优惠券过期提醒异常:{}", message, e);
            }
        }
    }

    加入延迟队列

    go 复制代码
    CouponExpireReminderMessage msg = new CouponExpireReminderMessage(
        "user123", "coupon456", "满50减20", "2025-04-10 23:59:59"
    );
    redissonClient.getDelayedQueue(redissonClient.getBlockingDeque("coupon:expire:remind:delay:queue", new JsonJacksonCodec()))
        .offer(msg, 1, TimeUnit.HOURS); // 提前一小时提醒

    ✅ 延迟场景 2:用户未读通知提醒(24小时后仍未读则再次提醒)

    实体类 UnreadNotificationReminderMessage.java

    go 复制代码
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class UnreadNotificationReminderMessage implements Serializable {
        private static final long serialVersionUID = 1L;
    
        private String userId;
        private String noticeId;
        private String title;
    }

    处理器类 UnreadNotificationReminderHandler.java

    go 复制代码
    @Component
    public class UnreadNotificationReminderHandler extends AbstractDelayMessageHandler<UnreadNotificationReminderMessage> {
    
        private static final Logger log = LoggerFactory.getLogger(UnreadNotificationReminderHandler.class);
    
        @Override
        protected String queueName() {
            return"notice:unread:remind:delay:queue";
        }
    
        @Override
        protected void handle(UnreadNotificationReminderMessage message) {
            try {
                // 模拟判断是否未读(例如通过DB检查状态)
                // boolean unread = noticeService.isUnread(message.getUserId(), message.getNoticeId());
                boolean unread = true; // 模拟
    
                if (unread) {
                    log.info("【未读通知提醒】用户:{},通知标题:{}", message.getUserId(), message.getTitle());
                    // 推送提醒
                    // pushService.sendUnreadNotification(message.getUserId(), message.getTitle());
                }
    
            } catch (Exception e) {
                log.error("未读通知提醒处理失败:{}", message, e);
            }
        }
    }
    高可用
    Redisson 自带重连机制;建议集群部署支持备份

    Redisson / RabbitMQ / Kafka 的延迟队列功能

    无需关心底层使用哪种消息系统

    RabbitMQ 延迟队列实现

    RabbitMQ 本身不支持真正的延迟队列,但可使用插件或 TTL + DLX(死信交换机) 实现

    (使用延迟插件)

    go 复制代码
    @Bean
    public CustomExchange delayExchange() {
        Map<String, Object> args = new HashMap<>();
        args.put("x-delayed-type", "direct");
        return new CustomExchange("delay.exchange", "x-delayed-message", true, false, args);
    }

    实现类

    多队列统一管理调度

    支持多个不同延迟任务(如:订单取消、支付过期、短信发送等)统一注册和调度,提高可扩展性。

    结构如下

    go 复制代码
    com.xxx.delay
    ├── AbstractDelayMessageHandler<T>      // 抽象类:每种业务继承它
    ├── DelayQueueManager                   // 统一注册&调度
    ├── ThreadPoolExecutorFactory           // 统一线程池管理
    ├── impl
    │   └── OrderCancelDelayHandler         // 实现类:订单取消
    ├── model
    │   └── OrderCancelMessage              // 消息体

    功能清单

    功能 说明
    🔁 失败重试机制 支持自定义最大重试次数和退避重试
    消费超时检测 如果处理时间超出阈值,记录慢消费日志
    📊 Prometheus监控支持 可用于 Grafana 展示消费速率/失败率/重试次数等

    失败重试机制(自动重新入队)

    🎯 实现点

    • 每条消息增加 retryCount 字段

    • 重试时延迟投递到 Redisson 延迟队列

    • 超过最大次数后记录错误日志,不再消费

相关推荐
安防视频中间件/视频资源汇聚平台30 分钟前
SVMSPro分布式综合安防管理平台--地图赋能智慧指挥调度新高度
分布式
SYC_MORE1 小时前
vLLM实战:多机多卡大模型分布式推理部署全流程指南
分布式
程序猿阿伟6 小时前
《解锁分布式软总线:构建智能设备统一管理平台》
分布式
程序猿阿伟6 小时前
《从底层逻辑剖析:分布式软总线与传统计算机硬件总线的深度对话》
分布式
工业甲酰苯胺6 小时前
zk基础—zk实现分布式功能
分布式
hi星尘7 小时前
深入理解Apache Kafka
分布式·kafka·apache
IT成长日记8 小时前
【Kafka基础】监控与维护:分区健康检查,确保数据高可用
分布式·kafka·健康检查·监控与维护
开心码农1号9 小时前
当一个 HTTP 请求发往 Kubernetes(K8s)部署的微服务时,整个过程流转时怎样的?
http·微服务·kubernetes
Yharim10 小时前
ruoyi-cloud起步、新增模块和定制
后端·微服务
敏君宝爸11 小时前
kafka 配置SASL认证
分布式·kafka