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 会更丝滑。
相关推荐
cui_win5 分钟前
Kafka 配置参数详解:ZooKeeper 模式与 KRaft 模式对比
分布式·zookeeper·kafka
cui_win4 小时前
深入理解 Kafka 核心:主题、分区与副本的协同机制
网络·分布式·kafka
黄雪超7 小时前
Kafka——无消息丢失配置怎么实现?
大数据·分布式·kafka
livemetee7 小时前
springboot 整合spring-kafka客户端:SASL_SSL+PLAINTEXT方式
spring boot·spring·kafka
黄雪超1 天前
Kafka——应该选择哪种Kafka?
大数据·分布式·kafka
remCoding1 天前
Java大厂面试实录:从Spring Boot到AI大模型的深度技术拷问
java·spring boot·redis·spring cloud·ai·kafka·microservices
Linux-palpitate2 天前
kafka的部署
分布式·kafka
飘飘渺渺渺红尘2 天前
消息中间件(Kafka VS RocketMQ)
分布式·kafka·rocketmq
黄雪超2 天前
Kafka——生产者压缩算法
大数据·分布式·kafka
亿牛云爬虫专家2 天前
Kafka与Flink打造流式数据采集方案:以二手房信息为例
flink·kafka·数据采集·爬虫代理·数据处理·二手房·定时抓取