Kafka 流量回放机制设计

声明:本次设计仅考虑 Kafka,其他消息队列本人了解程度不高故不作考虑。

背景

部门维护的平台有一个核心模块,其本质是以 Kafka 为消息队列的实时流式系统,一旦链路中某个环节出现问题,都有可能产生不可挽回的后果。希望能有一个流量回放的机制,出现问题后能在一定程度上挽回回来,提高系统的健壮性和可用性。

目标

支持回放某一时间段的流量,使其重新进行归因或回传逻辑。

调研

Kafka

整个归因回传系统主要依赖 Kafka 来进行数据流转,所以流量回放的核心在于如何重新消费来自 Kafka 的消息。

因此调研了一下 Kafka 如何从指定时间开始消费消息,下面贴出Demo 代码:

ini 复制代码
public static void main(String[] args) {
    Properties props = new Properties();
    props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
    props.put(ConsumerConfig.GROUP_ID_CONFIG, "my-group-1");
    props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");
    props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "30000");
    props.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG, "10000");
    props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
    props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
    KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);

    String topicName = "my-topic";
    // 消费指定分区
    consumer.assign(Arrays.asList(new TopicPartition(topicName, 0)));

    Map<TopicPartition, Long> map = new HashMap<>();
    map.put(new TopicPartition(topicName, 0), 1669888189952L);
    // 根据时间戳查找 offset
    // 如果指定的时间戳早于分区的第一条消息,那么返回分区的第一条消息的 offset;如果指定的时间戳晚于分区的最后一条消息,那么返回 null。
    Map<TopicPartition, OffsetAndTimestamp> topicPartitionOffsetAndTimestampMap = consumer.offsetsForTimes(map);
    for (Map.Entry<TopicPartition, OffsetAndTimestamp> entry : topicPartitionOffsetAndTimestampMap.entrySet()) {
        TopicPartition topicPartition = entry.getKey();
        OffsetAndTimestamp offsetAndTimestamp = entry.getValue();
        System.out.println(topicPartition + ": " + offsetAndTimestamp);
        if (offsetAndTimestamp != null) {
            // 设置分区的消费 offset
            consumer.seek(topicPartition, offsetAndTimestamp.offset());
        }
    }

    while (true) {
        ConsumerRecords<String, String> records = consumer.poll(Long.MAX_VALUE);
        for (ConsumerRecord<String, String> record : records) {
            System.out.printf("partition: %s, offset: %s, timestamp: %s, value: %s%n", record.partition(), record.offset(), record.timestamp(), record.value());
        }
    }
}

技术方案

蓝色部分是消息的正常处理流程,黄色部分是流量回放准备阶段,绿色部分是流量回放执行阶段

流量回放分为两个阶段,分别是准备阶段和执行阶段。

准备阶段

根据回放的时间段,转化为具体的 offset,进而读取到相应的范围的消息,消息经过一系列的 ETL 处理后,写入到专门用于流量回放的 Topic 里。

  1. ETL:在这里可以进行更细粒度、更精准化的筛选,比如只希望回放xx应用的流程。
  2. 专门用于流量回放的 Topic:与原 Topic 分隔开,不污染原 Topic,有助于使整个流量回放过程更安全、灵活。

执行阶段

流量回放数据准备完毕后,就进入到执行阶段,读取流量回放数据并最终交给原消息处理器处理。

  1. 流量回放调度器:用于监听流量回放任务,通知进度等,发现有新任务后,交给流量回放执行器来处理。

    实现选型:Zookeeper or 轮询数据库?

  2. 流量回放执行器:用于维护整个流量回放任务的生命周期,根据配置读取回放消息,经过流量回放限流器后,最后交给消息处理器处理。

  3. 流量回放限流器:限流的原因有两点,一是减少流量回放对服务的压力,二是如果回传频率过快的话,媒体可能会认为作假。

    限流器选型:本地 or Redis?因为在当前需求场景下不需要精准的全局限流,所以不需要依靠 Redis 等外部服务来做限流,使用本地限流即可。

其他

  1. 公司目前默认 Kafka Topic 消息最多保留7天,接入时要注意具体的 Kafka Topic 消息的有效期。
  2. 回放逻辑必须保证幂等。

代码设计

整体设计

整体设计分为3层,分别是流量回放 Server 层、流量回放 Client 层、业务层。

  • 业务层:订阅来自 Kafka topic 的消息,处理业务逻辑,这一层是原来存在的,不在流量回放框架的范畴里。

  • 流量回放 Server 层:接收流量回放请求,生成流量回放任务,管理整个流量回放任务的生命周期,并把状态变更通知给 Client 层。

    • Server:流量回放框架对外的入口,生成流量回放任务后,把流量回放任务交给 Task Controller Manager 控制和管理。
    • Task Controller Manager:维护单个流量回放任务的生命周期,把任务状态变更通知给外部。
    • 流量回放任务状态包括:已创建、已初始化、进行中、已完成、已终止(代表任务未完成强制终止)。
  • 流量回放 Client 层:流量回放任务初始化完成后,就进入到 进行中 的状态,此时 Client 层监听到就进入正式的流量回放工作中。其中 Client 层有两个流程,分别是读取原始 topic 消息到中转 topic 和读取中转 topic 消息用于业务逻辑处理,所以,把 Client 层分为两个不同的类型,负责不同的流程。

    • Source Channel Client:监听需要从原始 topic 读取消息到 中转 topic 的任务,然后交给 Task Source Channel Handler 处理。

      • Task Source Channel Handler:负责从原始 topic 读取消息,经过 Message Processor Chain 处理后,写入到中转 topic。
    • Transfer Channel Client:监听需要从中转 topic 读取消息到实际业务逻辑的任务,然后交给 Task Transfer Channel Handler 处理。

      • Task Transfer Channel Handler:负责从中转 topic 读取消息,经过 Message Processor Chain 处理后,调用实际业务逻辑。
    • Message Processor Chain:消息处理链,用户可以根据具体情况进行一些数据加工或筛选处理。

类设计

TODO 待补充

优化点

  1. 增加一种回放模式------把中转通道去掉,直接原始通道-->业务逻辑。
  2. 流量回放任务增加暂停、恢复操作。
  3. 目前的限流实现是本地限流,基于令牌桶算法,可以提供多种限流方式,例如全局限流。
  4. 目前使用 MySQL + 轮询来做状态监听,使用 Zookeeper 会更丝滑。
相关推荐
骇客野人18 小时前
SpringBoot 接入 Kafka 完整实操步骤
spring boot·kafka·linq
Sayai18 小时前
Kafka 集群开启 SASL 认证实战(二):外置 ZooKeeper 之 kafka 与 zk 间认证
分布式·zookeeper·kafka
Sayai19 小时前
Kafka 集群开启 SASL 认证实战(一):内置 ZooKeeper 场景,JAAS 配置全流程
分布式·zookeeper·kafka
Sayai19 小时前
Kafka 集群开启 SASL 认证实战(三):外置 ZooKeeper 完整版,quorum 认证 + ACL 访问控制
zookeeper·kafka·linq
海兰19 小时前
【Kafka进阶6】顺序消费深度解析
分布式·kafka·linq
2601_9609067220 小时前
Anthropic把Claude Code 2.1.236及以上版本的Fable 5会话
人工智能·kafka·时序数据库·etcd·tdengine
xiamo@moment1 天前
kafka学习笔记-概念总集
运维·kafka
あ-3 天前
kafka broker迁移
kafka
海兰3 天前
【 Kafka进阶5】使用 Docker 部署 Apache Kafka 集群完全指南
docker·kafka·apache
海兰4 天前
【Kafka进阶4】KRaft 完全指南:Apache Kafka 摆脱 ZooKeeper 的架构
zookeeper·kafka·apache