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 会更丝滑。
相关推荐
Mr.朱鹏12 小时前
SQL深度分页问题案例实战
java·数据库·spring boot·sql·spring·spring cloud·kafka
山沐与山21 小时前
【MQ】Kafka与RocketMQ深度对比
分布式·kafka·rocketmq
yumgpkpm1 天前
Cloudera CDP7、CDH5、CDH6 在华为鲲鹏 ARM 麒麟KylinOS做到无缝切换平缓迁移过程
大数据·arm开发·华为·flink·spark·kafka·cloudera
树下水月1 天前
Easyoole 使用rdkafka 进行kafka的创建topic创建 删除 以及数据发布 订阅
分布式·kafka
Cat God 0071 天前
基于Docker搭建kafka集群
docker·容器·kafka
Cat God 0071 天前
基于 Docker 部署 Kafka(KRaft + SASL/PLAIN 认证)
docker·容器·kafka
KD1 天前
设计模式——责任链模式实战,优雅处理Kafka消息
后端·设计模式·kafka
原神启动12 天前
Kafka详解
分布式·kafka
一只懒鱼a2 天前
搭建kafka集群(安装包 + docker方式)
运维·容器·kafka
青春不流名2 天前
如何在Kafka中使用SSL/TLS证书认证
分布式·kafka·ssl