李锋镝的博客

  • 首页
  • 时间轴
  • 插件
  • 评论区显眼包🔥
  • 左邻右舍
  • 博友圈
  • 关于我
    • 关于我
    • 另一个网站
    • 我的导航站
    • 网站地图
  • 留言
  • 赞助
Destiny
自是人生长恨水长东
  1. 首页
  2. 中间件
  3. 正文

深度解析 Kafka Rebalance:从原理到实战,彻底解决消息积压、重复与丢失

2025年10月30日 67点热度 0人点赞 0条评论

在 Kafka 消费者集群运维中,你是否遇到过这些棘手问题?

  • 生产环境突然告警,Topic 消息积压量10分钟内飙升至50万条,下游服务断流;
  • 支付回调消息重复处理,导致用户被重复扣款;
  • 订单状态更新消息莫名丢失,部分用户订单一直卡在“待支付”状态。

多数时候,这些问题的根源并非消费者代码bug或Kafka集群故障,而是被忽视的 Rebalance(重平衡)。本文将从底层原理出发,详细拆解 Rebalance 的触发机制、执行流程、引发的问题及优化方案,结合真实生产案例,帮你彻底掌握 Rebalance 的管控能力。

一、先搞懂基础:Rebalance 依赖的核心概念

在深入 Rebalance 前,必须先理清 Kafka 消费者组的核心组件与交互逻辑——这是理解 Rebalance 的前提。

1. 消费者组(Consumer Group)

  • 定义:一组共同消费某个/某些 Topic 的消费者,组内每个消费者负责消费部分分区,确保“一个分区仅被组内一个消费者消费”(避免重复消费)。
  • 核心作用:通过水平扩展消费者数量,提升 Topic 的整体消费能力(如 10 个分区的 Topic,最多可部署 10 个消费者,每个消费者处理 1 个分区)。

2. 协调者(Coordinator)

  • 定义:Kafka 集群中负责管理消费者组的 Broker,每个消费者组对应一个 Coordinator(通过消费者组 ID 的哈希值分配)。
  • 核心职责:
    1. 维护消费者组的存活状态(通过心跳机制);
    2. 触发并管理 Rebalance 流程;
    3. 存储消费者组的 offset 信息(默认存储在 __consumer_offsets 内部 Topic)。

3. Offset(消费进度)

  • 定义:消费者对某个分区的消费进度,记录“下一条要消费的消息的位置”(如 offset=100 表示已消费完前 100 条消息,下次从 100 开始)。
  • 提交方式:
    • 自动提交(enable.auto.commit=true):Kafka 定期(默认 5 秒)提交 offset,无需代码干预;
    • 手动提交:通过 commitSync()(同步)或 commitAsync()(异步)在代码中控制提交时机。

4. 分区分配策略

  • 定义:Coordinator 决定如何将 Topic 的分区分配给消费者组内的消费者,直接影响 Rebalance 后的负载均衡效果。
  • 常见策略:
策略名称 逻辑 优点 缺点
RangeAssignor 按 Topic 分组,每个消费者分配连续分区 分区连续,利于顺序消费 分区数不均时负载失衡(如 5 分区→2 消费者,3:2 分配)
RoundRobinAssignor 所有分区按顺序轮询分配给消费者 负载更均衡 分区分散,跨分区顺序消费困难
StickyAssignor 保留原有分配,仅调整变化的分区 减少 Rebalance 后的分区变动 逻辑复杂,旧版本不支持(Kafka 0.11+)

二、什么时候会触发 Rebalance?4 类场景+底层原因

Rebalance 的本质是“消费者组内分区与消费者的映射关系重新计算”,仅当原有映射被打破时才会触发。以下是 4 类常见场景及底层逻辑:

1. 消费者数量变化(最频繁触发场景)

消费者组内新增或减少消费者,会直接打破“分区-消费者”的映射,必须通过 Rebalance 重新分配。

(1)新增消费者(扩容)

  • 场景:业务高峰时,为提升消费能力,手动新增消费者节点(如 K8s 扩容 Pod 从 2 个到 3 个)。
  • 底层逻辑:新增消费者会向 Coordinator 发送 JoinGroup 请求,Coordinator 发现消费者数量变化,判定需要 Rebalance。
  • 案例:某电商大促期间,order-topic 有 6 个分区,原 2 个消费者各处理 3 个分区;扩容到 3 个消费者后,Rebalance 重新分配为每个消费者处理 2 个分区,消费能力提升 50%。

(2)减少消费者(下线)

  • 场景:消费者节点宕机、网络断连、进程被误杀,或 K8s 因资源不足导致 Pod 重启。
  • 底层逻辑:消费者下线后,Coordinator 超过 session.timeout.ms 未收到心跳,判定该消费者“死亡”,触发 Rebalance 让剩余消费者接管其分区。
  • 生产坑点:K8s 节点内存不足时,消费者 Pod 频繁重启(每 5 分钟一次),每次重启都会触发 Rebalance,导致消费能力反复波动,消息积压越来越严重。

2. Topic 分区数量增加

Kafka 不支持减少分区,但新增分区时,已存在的消费者组无法自动感知新分区,必须通过 Rebalance 分配新分区。

底层逻辑:

  • Kafka 消费者组启动时,会获取 Topic 当前的分区列表并缓存;
  • 当 Topic 新增分区后,消费者组的缓存未更新,仍只消费旧分区;
  • 只有触发 Rebalance,Coordinator 才会将新分区纳入分配范围,分配给组内消费者。

案例:

某支付系统的 pay-topic 原 5 个分区,扩容到 8 个分区后,消费者组仍只消费 5 个旧分区,新增的 3 个分区无消费者处理,消息持续积压;直到 1 小时后因某个消费者重启触发 Rebalance,新分区才被分配,积压消息才开始处理。

3. 消费者订阅的 Topic 变化

消费者通过 subscribe() 方法订阅 Topic 列表时,若修改订阅范围(如从仅订阅 order-topic 改为订阅 order-topic+pay-topic),会触发 Rebalance。

关键区别:subscribe() vs assign()

  • subscribe():订阅 Topic 列表,Kafka 自动管理分区分配,支持 Rebalance;
  • assign():手动指定消费的分区,不触发 Rebalance(适合固定分区消费场景)。

案例:

某日志系统的消费者原本订阅 app-log-topic,后来需求变更,需同时订阅 db-log-topic,开发人员修改代码为 subscribe(Arrays.asList("app-log-topic", "db-log-topic")),重启消费者后触发 Rebalance,重新分配两个 Topic 的所有分区。

4. 心跳或消费超时(隐性触发场景)

消费者需通过“心跳”和“消费进度”证明自己存活,若超时,会被 Coordinator 踢出组,触发 Rebalance。这是最容易被忽视的场景,核心与三个超时参数相关:

参数名称 默认值 作用 关键逻辑
heartbeat.interval.ms 3000ms 消费者发送心跳的间隔 建议设为 session.timeout.ms 的 1/3,确保及时上报存活状态
session.timeout.ms 45000ms 心跳超时时间(超过则判定消费者死亡) 超时后,消费者被踢出组,其分区被回收
max.poll.interval.ms 300000ms 消费超时时间(处理单批消息的最大时长) 超时后,即使心跳正常,也会被踢出组

隐性坑点:

  • 心跳超时:消费者因 GC 停顿(如 Full GC 持续 50 秒),未及时发送心跳,超过 session.timeout.ms 被踢出组;
  • 消费超时:处理大消息(如 100MB 的日志消息)耗时 6 分钟,超过 max.poll.interval.ms(5 分钟),被判定“消费能力不足”,强制踢出组。

案例:

某订单系统处理大金额订单消息时,单条消息需调用 3 个外部接口,处理耗时约 6 分钟;因未调整 max.poll.interval.ms,每次处理大消息都会触发消费超时,导致消费者被踢出组,Rebalance 频繁发生(每 6 分钟一次),大消息反复被不同消费者处理,直到某次处理完成前未超时才提交 offset。

三、Rebalance 底层流程:为什么会耗时?

很多人误以为 Rebalance 是“瞬间完成”的,实则需经历三个阶段,大消费者组(如 100 个消费者、1000 个分区)的 Rebalance 可能持续几十秒,期间消费完全暂停。

Rebalance 三阶段流程(以 StickyAssignor 为例)

1. JoinGroup 阶段:消费者加入组

  • 步骤:
    1. 消费者向 Coordinator 发送 JoinGroupRequest,携带自身元数据(如订阅的 Topic、支持的分配策略);
    2. Coordinator 收集所有存活消费者的请求,等待 session.timeout.ms 的 1/3 时间(确保所有消费者都能加入);
    3. Coordinator 从组内选择一个消费者作为 Leader(通常是第一个加入的消费者),负责后续的分区分配计算。
  • 耗时点:若消费者网络延迟高,或部分消费者未及时响应,会延长等待时间。

2. SyncGroup 阶段:分配分区并同步

  • 步骤:
    1. Coordinator 将所有消费者的元数据发送给 Leader;
    2. Leader 根据分配策略(如 StickyAssignor)计算分区分配方案(哪个消费者处理哪个分区);
    3. Leader 向 Coordinator 发送 SyncGroupRequest,携带分配方案;
    4. Coordinator 将分配方案同步给所有消费者,消费者确认后返回 SyncGroupResponse。
  • 耗时点:分区数量多(如 1000 个分区)或分配策略复杂(如 StickyAssignor 需保留原有分配),会增加 Leader 的计算时间。

3. 初始化阶段:消费者准备消费

  • 步骤:
    1. 消费者接收分配方案,停止消费旧分区,提交旧分区的 offset;
    2. 消费者初始化新分区的消费上下文(如重置 offset、创建消息处理器);
    3. 消费者开始从新分区的当前 offset 消费。
  • 耗时点:若消费者需处理大量旧分区的 offset 提交,或初始化逻辑复杂(如加载本地缓存),会延长准备时间。

流程可视化(以 3 个消费者、6 个分区为例)

file

四、Rebalance 引发的 3 类核心问题及根源

Rebalance 期间,消费者组处于“不稳定状态”,容易引发消息积压、重复消费和数据丢失,这些问题的根源往往与 offset 提交时机 和 流程中断 相关。

1. 消费暂停与消息积压

  • 现象:Rebalance 期间,所有消费者停止消费,Topic 消息持续写入但无消费,积压量快速上升。
  • 根源:
    • Rebalance 三阶段流程耗时(大消费者组可达 30+ 秒);
    • 若 Rebalance 频繁触发(如每 5 分钟一次),消费暂停时间累积,积压量越来越大。
  • 案例:某社交平台的 feed-topic 有 10 个分区、5 个消费者,因 K8s 资源不足,消费者 Pod 每 10 分钟重启一次,每次 Rebalance 耗时 15 秒;1 小时内触发 6 次 Rebalance,累计消费暂停 90 秒,消息积压量从 0 飙升至 30 万条。

2. 消息丢失:offset 提交与 Rebalance 时机不匹配

Rebalance 本身不直接丢数据,但结合 offset 提交逻辑,容易导致未处理的消息被跳过。以下是两种典型场景:

场景 1:自动提交 offset + 消息未处理完

  • 原理:自动提交的时机是“Poll 消息后,等待 auto.commit.interval.ms(默认 5 秒)提交”,若提交后消息未处理完就触发 Rebalance,新消费者会从已提交的 offset 开始消费,跳过未处理的消息。
  • 时间线案例:
时间 操作 offset 状态
00:00 消费者 A Poll 到 offset 100-200 的消息 未处理,offset 未提交
00:05 自动提交 offset 200 已提交,消息处理到 150 条
00:06 消费者 A 宕机,触发 Rebalance 消息 150-199 未处理
00:08 消费者 B 接手分区,从 offset 200 消费 消息 150-199 丢失

场景 2:手动提交 offset 时机错误(提交在前,处理在后)

  • 错误代码示例:
    // 错误:先提交 offset,再处理消息
    consumer.poll(Duration.ofSeconds(1));
    for (ConsumerRecord record : records) {
      // 错误:未处理消息就提交 offset
      consumer.commitSync(); 
      processMessage(record); // 处理消息(可能耗时)
    }
  • 风险:提交 offset 后、处理消息前触发 Rebalance,新消费者会跳过已提交的消息,导致未处理的消息丢失。

3. 消息重复:offset 提交滞后于 Rebalance

重复消费是 Rebalance 最常见的问题,核心原因是“消息处理完成,但 offset 未提交成功”,新消费者从上次提交的 offset 重新消费。

场景 1:手动提交被 Rebalance 打断

  • 时间线案例:
时间 操作 offset 状态
00:00 消费者 A Poll 到 offset 100-200 的消息 未处理,offset 100
00:03 消费者 A 处理完所有消息,准备提交 消息处理完成,offset 未提交
00:04 消费者 A 心跳超时,被踢出组 提交操作被中断
00:06 消费者 B 接手分区,从 offset 100 消费 消息 100-200 重复处理

场景 2:消费超时导致重复

  • 原理:处理消息耗时超过 max.poll.interval.ms,消费者被踢出组,但消息仍在处理;新消费者接手后重新消费,原消费者处理完后提交 offset 失败。
  • 代码坑点:未根据消息处理耗时调整 max.poll.interval.ms,默认 5 分钟的超时时间无法满足大消息处理需求。

场景 3:offset 丢失导致回退

  • 原理:若消费者组的 auto.offset.reset 设为 earliest(默认是 latest),Rebalance 后找不到已提交的 offset(如 __consumer_offsets 主题数据损坏),会从 Topic 最早的消息开始消费,导致历史消息重复。

五、实战优化:从“减少触发”到“降低影响”

Rebalance 无法完全避免,但通过以下优化措施,可将其影响降至最低:

1. 避免频繁触发 Rebalance(核心优化)

(1)调优超时参数,避免误判

根据业务场景调整三个核心超时参数,遵循“心跳间隔 = 会话超时的 1/3,消费超时 > 最大消息处理耗时”:

Properties props = new Properties();
// 心跳间隔:设为 session.timeout.ms 的 1/3,确保及时上报
props.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG, 2000); 
// 会话超时:延长至 60 秒,避免 GC 或网络抖动导致误判
props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 60000); 
// 消费超时:根据最大消息处理耗时设置(如大消息设为 10 分钟)
props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 600000); 

(2)确保消费者稳定运行

  • K8s 环境优化:
    • 调整 Pod 资源限制(CPU、内存),避免因资源不足导致重启;
    • 配置合理的健康检查探针(livenessProbe 间隔设为 10 秒,failureThreshold 设为 5),避免误杀正常运行的消费者;
  • 服务器监控:实时监控消费者节点的 CPU、内存、网络,提前预警资源瓶颈(如 CPU 使用率超过 80% 时扩容)。

(3)避免不必要的订阅/分区变化

  • 订阅 Topic 时,尽量一次性确定范围,避免频繁修改;
  • 新增 Topic 分区时,选择业务低峰期操作,并提前通知消费者组(如重启部分消费者触发 Rebalance)。

2. 安全处理 offset 提交(避免丢失/重复)

(1)优先手动提交,关闭自动提交

自动提交无法控制时机,建议关闭自动提交,在消息处理完成后手动提交:

// 关闭自动提交
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); 
Consumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("order-topic"));

while (true) {
    ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
    for (ConsumerRecord<String, String> record : records) {
        try {
            // 1. 处理消息(确保业务逻辑执行成功)
            processMessage(record); 
            // 2. 消息处理完成后,同步提交 offset(失败会重试)
            consumer.commitSync(); 
        } catch (Exception e) {
            log.error("处理消息或提交 offset 失败", e);
            // 可选:记录失败消息,后续重试
            saveFailedMessage(record);
        }
    }
}

(2)区分 commitSync 与 commitAsync

  • commitSync(同步提交):提交成功前阻塞,失败会重试,适合对一致性要求高的场景(如金融交易);
  • commitAsync(异步提交):非阻塞,失败不会重试,需通过回调处理失败:

    // 异步提交 + 回调处理
    consumer.commitAsync((offsets, exception) -> {
      if (exception != null) {
          log.error("异步提交 offset 失败", exception);
          // 处理失败:如记录 offsets 到本地,后续重试
      }
    });

3. 优化分区分配策略(减少负载失衡)

(1)优先选择 StickyAssignor

StickyAssignor(粘性分配策略)在 Rebalance 时会尽量保留原有分区分配,仅调整变化的部分,减少消费者的分区变动,降低初始化耗时:

// 设置分区分配策略为 StickyAssignor
props.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, 
          StickyAssignor.class.getName());

(2)案例对比:Range vs Sticky

假设 order-topic 有 5 个分区,消费者组有 2 个消费者:

  • RangeAssignor:分配结果为 [0,1,2](消费者1)、[3,4](消费者2),负载失衡;
  • StickyAssignor:若消费者2下线,Rebalance 后分配 [0,1](消费者1)、[2,3,4](新消费者3),保留消费者1的原有分区,减少初始化成本。

4. 消费逻辑优化(容忍重复,避免超时)

(1)实现消费幂等性

即使发生重复消费,也不会导致业务错误,常见方案:

  • 数据库唯一键:用消息 ID 或业务唯一键(如订单 ID)作为数据库主键,重复插入会报错;
  • Redis 原子操作:用 SETNX 或 HSETNX 记录消费状态,重复消费时直接返回成功;

    // Redis 原子操作实现幂等
    String messageId = record.headers().lastHeader("message-id").value().toString();
    Boolean isFirstConsume = redisTemplate.opsForValue().setIfAbsent("msg:" + messageId, "consumed", 24, TimeUnit.HOURS);
    if (Boolean.FALSE.equals(isFirstConsume)) {
      log.info("消息已消费,跳过重复处理");
      continue;
    }
    // 处理消息...

(2)拆分大消息,控制批量消费 size

  • 大消息拆分:将 100MB 的大消息拆分为 10 个 10MB 的小消息,避免单条消息处理超时;
  • 控制 poll 数量:通过 max.poll.records 限制单次 Poll 的消息数(如设为 100),避免一次性处理过多消息导致超时:

    // 单次 Poll 最多 100 条消息
    props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 100); 

六、实战排查:如何定位 Rebalance 问题?

当遇到消息积压、重复或丢失时,可按以下步骤排查是否由 Rebalance 引起:

1. 查看 Kafka Broker 日志(Coordinator 日志)

Coordinator 运行在 Broker 上,日志默认存储在 $KAFKA_HOME/logs/server.log,搜索关键词 Rebalance:

# 日志示例:消费者组触发 Rebalance
[2025-10-16 14:30:00] INFO [GroupCoordinator 1001]: Starting rebalance for group order-consumer-group, reason: Adding new member consumer-1-xxx (kafka.coordinator.group.GroupCoordinator)
[2025-10-16 14:30:05] INFO [GroupCoordinator 1001]: Rebalance completed for group order-consumer-group, new generation 10, assigned partitions: [order-topic-0, order-topic-1] (kafka.coordinator.group.GroupCoordinator)
  • 关键信息:Rebalance 触发原因(如 Adding new member)、消费者组、新的 generation 编号、分配的分区。

2. 查看消费者日志

在消费者应用日志中搜索 Rebalance 或 partition assignment,确认是否收到新的分区分配:

# 日志示例:消费者收到分区分配
[2025-10-16 14:30:05] INFO Consumer clientId=consumer-order-1, groupId=order-consumer-group: Assigned partitions: [order-topic-0, order-topic-1] (org.apache.kafka.clients.consumer.internals.ConsumerCoordinator)

3. 监控 Rebalance 相关指标

通过 Prometheus + Grafana 监控以下指标,实时感知 Rebalance 频率:

  • kafka_consumer_rebalance_count_total:消费者组触发 Rebalance 的总次数;
  • kafka_consumer_partition_assignment_latency_seconds:分区分配的耗时;
  • kafka_consumer_heartbeat_failures_total:心跳失败次数(预示可能触发 Rebalance)。

4. 定位 Rebalance 原因

  • 消费者下线:查看消费者节点的系统日志(如 dmesg 查看是否 OOM,K8s 日志查看 Pod 重启原因);
  • 超时触发:查看消费者的 GC 日志(是否有长时间 Full GC),或消息处理耗时日志(是否超过 max.poll.interval.ms);
  • 分区/订阅变化:检查 Topic 分区数量(kafka-topics.sh --describe --topic order-topic --bootstrap-server localhost:9092),或消费者订阅代码是否变更。

七、总结:Rebalance 管控的核心原则

Rebalance 是 Kafka 消费者组的“双刃剑”——合理触发可均衡负载,频繁触发则会引发故障。核心管控原则可总结为三点:

  1. 减少触发频率:通过调优超时参数、确保消费者稳定、避免不必要的订阅/分区变化,从源头减少 Rebalance 次数;
  2. 控制影响范围:选择 StickyAssignor 策略、优化 offset 提交逻辑,降低 Rebalance 后的消费暂停时间和数据异常风险;
  3. 容忍业务异常:实现消费幂等性、完善监控告警,即使发生 Rebalance,也能避免业务故障(如重复扣款、数据丢失)。

最后,记住:Kafka 运维的核心是“稳定性优先”,不要为了追求极致性能而忽视 Rebalance 的潜在风险。合理管控 Rebalance,才能让 Kafka 消费者集群持续稳定运行。

除非注明,否则均为李锋镝的博客原创文章,转载必须以链接形式标明本文链接

本文链接:https://www.lifengdi.com/zhong-jian-jian/4548

相关文章

  • 10 个MQ高频业务场景深度解析
  • Kafka 为什么要抛弃 Zookeeper?
  • 为什么同样是分布式架构的Kafka需要Leader而Redis不需要?
  • Kafka常见面试题(一)
  • 深入了解PostgreSQL
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
标签: Kafka MQ Rebalance
最后更新:2025年10月30日

李锋镝

既然选择了远方,便只顾风雨兼程。

打赏 点赞
< 上一篇
下一篇 >

文章评论

1 2 3 4 5 6 7 8 9 11 12 13 14 15 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 46 47 48 49 50 51 52 53 54 55 57 58 60 61 62 63 64 65 66 67 69 72 74 76 77 78 79 80 81 82 85 86 87 90 92 93 94 95 96 97 98 99
取消回复

位卑未敢忘忧国,事定犹须待阖棺。

那年今日(12月17日)

  • 1981年:德国足球运动员蒂姆·维泽出生
  • 1971年:印度和东巴基斯坦达成停火协议
  • 1909年:比利时国王利奥波德二世逝世
  • 1905年:狙击之王西蒙·海耶出生
  • 1902年:京师大学堂正式开学
  • 更多历史事件
最新 热点 随机
最新 热点 随机
AI原生数据库新标杆:seekdb深度解析,轻量架构与混合搜索的双重革命 做了一个WordPress文章热力图插件 Spring WebFlux底层原理深度剖析-从响应式流到事件循环的全链路拆解 Spring WebFlux深度解析:异步非阻塞架构与实战落地指南 规范驱动AI编程:用OpenSpec实现100%可控开发,从需求到代码的全流程闭环 WordPress网站换了个字体,差点儿把样式换崩了
玩博客的人是不是越来越少了?准备入手个亚太的ECS,友友们有什么建议吗?使用WireGuard在Ubuntu 24.04系统搭建VPNWordPress实现用户评论等级排行榜插件Gemini 3 Pro 深度测评:多模态AI编程的跨代际突破,从一句话到完整应用的全链路革命WordPress网站换了个字体,差点儿把样式换崩了
使用itext和freemarker来根据Html模板生成PDF文件,加水印、印章 项目中不用 redis 分布式锁,怎么防止用户重复提交? SpringBoot框架自动配置之spring.factories和AutoConfiguration.imports JAVA线程池简析(JDK1.6) IDEA版本2020.*全局MAVEN配置 Gemini 3 深度解析:从像素级复刻到 AGI 雏形,多模态 AI 如何重构开发与创作?
标签聚合
JVM WordPress SQL 日常 K8s 架构 SpringBoot AI编程 MySQL ElasticSearch 多线程 分布式 数据库 AI JAVA docker 设计模式 Spring IDEA Redis
友情链接
  • Blogs·CN
  • Honesty
  • 临窗旋墨
  • 哥斯拉
  • 彬红茶日记
  • 志文工作室
  • 搬砖日记
  • 旧时繁华
  • 林羽凡
  • 瓦匠个人小站
  • 皮皮社
  • 知向前端
  • 蜗牛工作室
  • 韩小韩博客
  • 风渡言

COPYRIGHT © 2025 lifengdi.com. ALL RIGHTS RESERVED.

Theme Kratos Made By Dylan

津ICP备2024022503号-3