分布式微服务系统架构第119集:WebSocket监控服务内部原理和执行流程

加群联系作者vx:xiaoda0423

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

https://1024bat.cn/

👉 Kafka Producer 端(生产者)配置

👉 Kafka Consumer 端(消费者)配置

区分了不同的消费模式 (批量消费、单条消费),并且生产者也是分别独立管理。

🛠️ 流程步骤概览

阶段 内容
配置阶段 通过 @Configuration 注解注册生产者、消费者配置。
属性注入 通过 @Value 注解注入 application.ymlapplication.properties 中的 Kafka 配置。
Bean 定义 生产者定义 KafkaTemplate,消费者定义 KafkaListenerContainerFactory(分批量和单条消费两种)。
使用阶段 在业务代码中注入对应的 KafkaTemplate 发送消息,注解 @KafkaListener 配合不同的 ContainerFactory 接收消息。

📈 数据结构变化过程

阶段 数据结构 变化说明
生产者发送前 Java对象/String Java对象通常先转为 String(通过序列化器)
传输到 Kafka byte[] Kafka 内部只认识字节数组
消费者接收 byte[] → String 消费者接收到字节流,反序列化回 String
消费逻辑处理 Java对象/String 再根据业务场景反序列化成 Java 对象处理

📚 示例操作流程(完整链路)

1. 启动阶段

  • Spring Boot 扫描到 @Configuration,注册 KafkaTemplate 和 KafkaListenerContainerFactory。

2. 发送消息(生产者)

go 复制代码
@Autowired
@Qualifier("kafkaTemplate")
private KafkaTemplate<String, String> kafkaTemplate;

public void sendMessage(String topic, String message) {
    kafkaTemplate.send(topic, message);
}

3. 消费消息(消费者)

go 复制代码
@KafkaListener(topics = "test-topic", containerFactory = "kafkaBRConsumerFactory")
public void listenBatch(List<ConsumerRecord<String, String>> records) {
    for (ConsumerRecord<String, String> record : records) {
        System.out.println("Received message: " + record.value());
    }
}

// 或者单条消费
@KafkaListener(topics = "test-topic", containerFactory = "kafkaOBORConsumerFactory")
public void listenSingle(ConsumerRecord<String, String> record) {
    System.out.println("Received single message: " + record.value());
}
  • 生产者和消费者配置完全隔离,职责清晰。

  • 支持批量消费 (性能高)和逐条消费(处理更精细)两种模式。

  • KafkaTemplate 统一封装消息发送,内部使用的是 ProducerFactory

  • KafkaListenerContainerFactory 统一封装消息监听,内部使用的是 ConsumerFactory

  • 生产消费流程:Java对象 → Kafka字节流传输 → Java对象。

📄 1. application.yml Kafka配置模板

go 复制代码
kafka:
  producer:
    servers: 127.0.0.1:9092
    retries: 3              # 发送失败重试次数
    batch:
      size: 16384           # 批量发送最大字节数
    linger: 1               # 等待时间(ms)
    buffer:
      memory: 33554432      # 32MB缓冲区大小
  consumer:
    servers: 127.0.0.1:9092
    enable:
      auto:
        commit: false       # 是否自动提交offset
    session:
      timeout: 30000        # session超时时间
    auto:
      commit:
        interval: 1000      # 自动提交offset时间间隔
    group:
      id: my-consumer-group
    auto:
      offset:
        reset: latest       # 最新位置消费(可选 earliest)
    concurrency: 3          # 并发线程数

注意

  • producer.serversconsumer.servers 通常是一样的(Kafka 集群地址)。

  • auto.offset.reset

    • latest:只消费启动后新的消息

    • earliest:从最早可用的消息开始消费(适合首次启动的消费者)

🚀 2. 消费者端:加上消息重试机制

在你的消费者容器工厂上,加个 SeekToCurrentErrorHandler,遇到异常能自动重试N次(否则Kafka默认抛出异常整个消费线程就挂掉了!)

go 复制代码
private KafkaListenerContainerFactory<?> getConsumerFactory(boolean batchListener) {
    ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
    factory.setConsumerFactory(consumerFactory());
    factory.setConcurrency(concurrency);
    factory.setBatchListener(batchListener);

    // 消息处理异常时重试配置(重要)
    factory.setErrorHandler(new SeekToCurrentErrorHandler(
            new FixedBackOff(5000L, 3) // 5秒重试一次,总共重试3次
    ));

    return factory;
}

细节说明

  • SeekToCurrentErrorHandler 会把这条消息重新投到消费队列中,不会丢失。

  • FixedBackOff(5000L, 3):表示每隔5秒重试一次重试3次后如果还失败就记录error日志。

如果你想更极致一点,比如:重试 N 次还失败就发到 死信队列(DLQ) ,也可以配!

⚡ 3. 生产者端:Kafka发送消息异步带回调

别直接 kafkaTemplate.send()完就不管了,加一个Callback回调,拿到真正的发送成功 or 失败,做链路监控。

补充一下发送方法:

go 复制代码
@Autowired
@Qualifier("kafkaTemplate")
private KafkaTemplate<String, String> kafkaTemplate;

public void sendMessageWithCallback(String topic, String message) {
    kafkaTemplate.send(topic, message).addCallback(
        success -> {
            if (success != null) {
                System.out.println("发送成功!topic:" + success.getRecordMetadata().topic() + 
                                   " partition:" + success.getRecordMetadata().partition() + 
                                   " offset:" + success.getRecordMetadata().offset());
            }
        },
        failure -> {
            System.err.println("发送失败!原因:" + failure.getMessage());
        }
    );
}

说明

  • success 可以拿到:topic、partition、offset,链路追踪好用!

  • failure 可以拿到异常信息,比如超时、网络问题、队列满等。

✅ 总结(目前你的Kafka模块)

组件 细节
Producer 配置 ProducerFactory、KafkaTemplate,支持异步Callback
Consumer 配置 ConsumerFactory、ListenerContainerFactory,支持批量、逐条消费、异常重试
配置文件 application.yml统一管理
错误处理 加了 SeekToCurrentErrorHandler 自动重试
发送保障 生产者发送加回调,失败报警

🚀 你可以直接拿来实战用,真正可以扛流量的 Kafka 消费生产链路!

🧩 1. 死信队列处理机制整体流程

✅ 正常消费 →

❌ 消费失败 + 重试多次 →

🔁 还是失败 →

➡️ 将原消息投递到【死信Topic】(Dead Letter Topic)

➡️ 后续人工修复 or 自动补偿

🛠️ 2. 代码实现分三步

(1)修改你的 getConsumerFactory 方法,加 DeadLetterPublishingRecoverer

我们要替换掉原来的 SeekToCurrentErrorHandler,换成支持死信队列的异常处理器:

go 复制代码
@Autowired
@Qualifier("kafkaTemplate") // 用来发到死信topic
private KafkaTemplate<String, String> kafkaTemplate;

private KafkaListenerContainerFactory<?> getConsumerFactory(boolean batchListener) {
    ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
    factory.setConsumerFactory(consumerFactory());
    factory.setConcurrency(concurrency);
    factory.setBatchListener(batchListener);

    // 配置死信队列处理器
    DeadLetterPublishingRecoverer recoverer = new DeadLetterPublishingRecoverer(kafkaTemplate,
        (r, e) -> new TopicPartition(r.topic() + ".DLT", r.partition()) 
        // 失败后投递到原Topic后缀加.DLT(Dead Letter Topic)
    );

    // 失败后重试3次,每次间隔5秒,重试完仍失败则交给recoverer处理
    factory.setErrorHandler(new SeekToCurrentErrorHandler(recoverer, new FixedBackOff(5000L, 3)));

    return factory;
}

🔵 解释一下:

  • DeadLetterPublishingRecoverer:失败后自动把消息重新生产到 .DLT 结尾的新Topic,比如消费 order-event 失败了,就投到 order-event.DLT

  • FixedBackOff(5000, 3):5秒重试1次,总共重试3次

  • 如果3次都失败,就触发 recoverer,把消息扔到死信队列里。

(2)application.yml 配置补充(DLQ Topic创建)

Kafka死信队列是一个普通的Topic,只不过你最好提前建好(也可以用自动创建)。

比如:

go 复制代码
kafka:
  topics:
    - name: order-event
    - name: order-event.DLT   # 死信队列

如果你用的 Kafka Server 开了自动建Topic(auto.create.topics.enable=true),也可以不手动建。

(3)写个死信队列监听器(DLT Consumer)

比如你可以新建一个 DLQ 处理器,专门监听死信消息:

go 复制代码
@Component
public class DeadLetterConsumer {

    @KafkaListener(topics = "order-event.DLT", groupId = "dead-letter-group")
    public void consumeDeadLetter(String message) {
        System.err.println("【死信消息处理】收到消息:" + message);
        // TODO: 这里可以发告警、落库、人工介入、补偿逻辑等
    }
}

注意:这里可以记录日志、发告警(钉钉/飞书/邮件),或者存到DB里,方便后续人工修复。

🎯 3. DLQ机制总结图

go 复制代码
正常消费成功 --> 直接ack
正常消费失败 --> 重试 --> 成功ack
重试N次失败 --> DeadLetterPublishingRecoverer --> 投递到 死信Topic
死信消费者监听 --> 告警 or 补偿处理

🔥 总结

步骤 内容
1 给 KafkaListenerContainerFactory 加上 DeadLetterPublishingRecoverer
2 配好死信Topic(一般就是正常Topic后加 .DLT
3 写一个专门的死信消费者
4 死信消息可以告警、存库、人工补偿

这样配置下来,你的 Kafka 消费链路就非常稳定了:不会因为单条脏数据影响整体消费系统健康。

  • 死信消息里最好加上:

    • 原始消息体

    • 消费失败原因(异常栈)

    • 时间戳

  • 死信消费可以打到链路追踪系统,比如接入 Skywalking/Zipkin

  • 对重要消息类型可以设置死信消息补偿重试机制

🚀 每个Bean的作用详细解释

Bean 作用
ServerEndpointExporter 核心 !Spring Boot 启动时会扫描 @ServerEndpoint 注解的类,自动注册成 WebSocket 端点。没有它的话,@ServerEndpoint 注解不会生效!
BaseEndpointConfigure 扩展配置 ,通常用来自定义 WebSocket 握手过程,比如:校验token、设置自定义属性、统一Session管理、拦截器(handshake拦截)等。

🔥 WebSocket启动内部执行流程

  1. Spring Boot 容器启动 →

  2. 扫描到 @Configuration 注解的 WebSocketConfig

  3. 创建 ServerEndpointExporter 实例,注册到 Spring 容器中 →

  4. ServerEndpointExporter 开始扫描项目中所有 @ServerEndpoint 注解的类 →

  5. 找到后,自动将这些类注册成 WebSocket端点(endpoint) →

  6. 创建 BaseEndpointConfigure Bean,供你后续扩展(比如你可以写自己的 modifyHandshake

相关推荐
胡萝卜糊了Ohh2 分钟前
kafka
分布式·kafka
艾厶烤的鱼4 分钟前
架构-计算机网络
计算机网络·架构
hoho不爱喝酒38 分钟前
微服务Nacos组件的介绍、安装、使用
微服务·云原生·架构
艾厶烤的鱼2 小时前
架构-系统工程与信息系统基础
架构
云达闲人2 小时前
1.6软考系统架构设计师:架构师的角色与能力要求 - 练习题附答案及超详细解析
系统架构·软考·系统架构设计师
桑榆08062 小时前
Spark-Streaming核心编程
大数据·分布式·spark
nbsaas-boot3 小时前
分布式微服务架构,数据库连接池设计策略
分布式·微服务·架构
littleplayer4 小时前
iOS Swift Redux 架构详解
前端·设计模式·架构