Redis 磁盘 I/O 阻塞导致连接超时问题复盘!

一、问题背景与现象

1.1 故障概述

某日上午,生产环境监控系统触发大量告警,显示系统接口响应超时率异常升高。初步排查发现,应用日志中出现大量Redis连接超时异常,典型错误堆栈如下:

redis.clients.jedis.exceptions.JedisConnectionException: 
    java.net.SocketTimeoutException: Read timed out
    at redis.clients.jedis.util.RedisInputStream.ensureFill(RedisInputStream.java:205)
    at redis.clients.jedis.util.RedisInputStream.readByte(RedisInputStream.java:40)
    at redis.clients.jedis.Protocol.process(Protocol.java:151)
    at redis.clients.jedis.JedisFactory.validateObject(JedisFactory.java:214)

1.2 初步分析

错误主要集中在JedisFactory.validateObject方法,该方法用于验证连接池中连接的有效性。初步怀疑可能的原因包括:

  1. 1. 连接池配置问题:连接池未正确配置或资源耗尽

  2. 2. 网络问题:Redis服务器网络不稳定

  3. 3. Redis服务异常:Redis服务本身存在性能问题

经过配置检查,确认连接池配置正常且资源充足,网络连通性良好,因此将排查重点转向Redis服务本身。


二、Redis 服务状态分析

2.1 Redis 日志分析

登录Redis服务器,检查Redis日志文件,发现大量如下警告信息:

[WARNING] Asynchronous AOF fsync is taking too long (disk is busy?). 
Writing the AOF buffer without waiting for fsync to complete, 
this may slow down Redis.

日志解读

  • • Redis在执行AOF(Append Only File)持久化时,fsync()系统调用耗时过长

  • • 磁盘I/O繁忙导致数据同步延迟

  • • Redis为避免阻塞主线程,选择不等待fsync完成直接继续处理请求

  • • 这种处理方式会影响Redis整体性能

同时观察到频繁的RDB快照任务日志:

[INFO] Background saving started by pid 12345
[INFO] DB saved on disk

2.2 Redis 性能指标分析

使用redis-cli info persistence命令获取持久化相关指标:

# Persistence
loading:0
rdb_changes_since_last_save:156789
rdb_bgsave_in_progress:0
rdb_last_save_time:1698123456
rdb_last_bgsave_status:ok
rdb_last_bgsave_time_sec:45
aof_enabled:1
aof_rewrite_in_progress:0
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:120
aof_current_rewrite_time_sec:-1
aof_last_bgrewrite_status:ok
aof_last_write_status:ok
aof_last_cow_size:0
aof_current_size:1429053125
aof_base_size:1234567890
aof_pending_rewrite:0
aof_buffer_length:0
aof_rewrite_buffer_length:0
aof_pending_bio_fsync:0
aof_delayed_fsync:20796

关键指标分析

  1. 1. aof_delayed_fsync: 20796

    • • 表示fsync操作延迟超过1秒的累计次数

    • • 数值过高说明磁盘I/O严重阻塞

  2. 2. aof_current_size: 1429053125

    • • AOF文件大小约1.4GB,文件较大可能影响I/O性能

  3. 3. rdb_last_bgsave_time_sec: 45

    • • 上次RDB快照耗时45秒,时间较长


三、配置分析与问题定位

3.1 当前配置检查

检查Redis配置文件中的持久化相关设置:

# AOF配置
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# RDB配置  
save 900 1
save 300 10
save 60 10000

3.2 配置问题分析

通过配置分析,识别出以下关键问题:

问题一:双重持久化机制并发执行
  • • AOF持久化:每秒执行一次fsync操作

  • • RDB快照:根据save规则定期触发

  • • 影响:两种机制同时对磁盘进行写操作,加剧I/O竞争

问题二:AOF重写期间不暂停fsync
  • • 配置no-appendfsync-on-rewrite no

  • • 含义:即使在AOF重写过程中,仍然执行fsync操作

  • • 影响:重写期间磁盘I/O负载进一步增加

问题三:磁盘类型与配置不匹配
  • • 硬件:使用传统HDD机械硬盘

  • • 特点:随机I/O性能较差,fsync操作延迟高

  • • 配置:未针对HDD特性进行优化


四、根因分析

4.1 问题链路梳理

磁盘I/O阻塞 → fsync延迟 → Redis主线程阻塞 → 客户端请求超时

详细分析

  1. 1. 触发条件:AOF重写与RDB快照并发执行

  2. 2. 直接原因:磁盘I/O带宽饱和,fsync操作排队等待

  3. 3. 影响范围:Redis主线程处理能力下降,网络请求响应延迟

  4. 4. 最终表现:客户端连接超时,应用层异常

4.2 技术原理说明

AOF fsync机制

  • • appendfsync everysec:Redis每秒调用一次fsync确保数据持久化

  • • fsync是同步I/O操作,会阻塞调用线程直到数据写入磁盘

  • • 当磁盘繁忙时,fsync延迟增加,影响Redis性能

RDB与AOF并发影响

  • • RDB快照需要遍历整个数据集并写入磁盘

  • • 与AOF的fsync操作形成I/O竞争

  • • 在HDD环境下,并发写操作性能急剧下降


五、解决方案设计与实施

5.1 优化策略

基于问题分析,制定如下优化策略:

策略一:简化持久化机制

目标:减少磁盘I/O竞争
方案:关闭RDB快照,仅保留AOF持久化(Redis版本为3.x,不支持4.0+的混合持久化)

# 禁用RDB快照
save ""

理由

  • • AOF提供更好的数据安全性(最多丢失1秒数据)

  • • 避免RDB与AOF的I/O竞争

  • • 简化运维复杂度

策略二:优化AOF同步策略

目标:减少AOF重写期间的I/O压力
方案:重写期间暂停fsync操作

no-appendfsync-on-rewrite yes

理由

  • • AOF重写期间暂停fsync,减少I/O竞争

  • • 重写完成后恢复正常fsync,保证数据安全

  • • 在重写期间最多丢失重写时长的数据(通常可接受)

策略三:调整AOF重写参数

目标:减少AOF重写频率
方案:提高重写触发阈值

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 128mb

5.2 实施步骤

  1. 1. 配置备份:备份当前Redis配置文件

  2. 2. 逐步调整:先调整AOF参数,观察效果

  3. 3. 关闭RDB:确认AOF稳定后关闭RDB

  4. 4. 监控验证:持续监控关键指标变化

5.3 监控指标

建立以下监控指标用于效果验证:

# 关键性能指标
redis-cli info persistence | grep -E "(aof_delayed_fsync|rdb_bgsave_in_progress)"
redis-cli info stats | grep -E "(total_connections_received|rejected_connections)"
redis-cli info clients | grep connected_clients

六、效果验证与持续优化

6.1 优化效果

配置调整后24小时内的监控数据对比:

指标

优化前

优化后

改善幅度

aof_delayed_fsync

20796

23

99.9%

平均响应时间

150ms

45ms

70%

连接超时率

15.2%

0.1%

99.3%

CPU使用率

85%

45%

47%

6.2 长期监控策略

  1. 1. 日常监控

    • • 每日检查aof_delayed_fsync指标

    • • 监控磁盘I/O使用率

    • • 跟踪Redis响应时间趋势

  2. 2. 告警设置

    • • aof_delayed_fsync > 100时告警

    • • 磁盘I/O使用率 > 80%时告警

    • • Redis连接超时率 > 1%时告警

  3. 3. 定期优化

    • • 每月评估AOF文件大小增长趋势

    • • 季度性能基准测试

    • • 年度硬件升级规划


七、结语

本次Redis连接超时问题的成功解决,充分体现了系统性问题排查方法的重要性。通过深入的日志分析、指标诊断和配置审查,我们准确定位了磁盘I/O阻塞这一根本原因,并制定了针对性的优化方案。

这次经历提醒我们,在分布式系统中,性能问题往往涉及多个层面的交互。Redis作为内存数据库,其性能不仅取决于内存和网络,持久化机制对磁盘I/O的依赖同样不可忽视。只有建立全面的监控体系,深入理解各组件的工作原理,才能在问题发生时快速定位并有效解决。

希望本文的分析思路和解决方案能为遇到类似问题的技术团队提供有价值的参考。

1.    前言 1.1.  背景和目标 VMS Cloud Event模块作为承载海量设备事件上报的核心组件,负责事件过滤、转发、存储等复杂处理逻辑。当前依赖的公共组件EventCenter仅支持基础订阅消费能力,存在以下瓶颈: 异常事件处理缺失:失败事件直接丢弃,无重试/隔离机制; 时效性不足:缺乏延迟触发能力(如定时删除事件); 扩展性弱:无法适配新增的复杂业务场景(如设备批量操作或批处理)。 本模块在项目中处于事件处理链路的核心地位,直接影响设备事件的可靠性与时效性。本需求分析文档的撰写目标是明确死信队列与延迟队列的功能需求、非功能性需求及实现方案,为后续开发提供指导。 /**补充:vms业务汇总*/ 1.2.  定义 死信队列(Dead Letter Queue, DLQ):存储无法被正常消费的异常消息(如重试耗尽、消费失败),用于错误隔离与问题排查。 延迟队列(Delayed Queue):支持消息在指定延迟时间后被消费,用于异步定时任务(如定时删除事件)。 EventCenter:现有公共组件,提供事件发送、处理器注册等基础能力,支持Kafka、Local实现。 指数退避原则:设置初始等待时间,发生可重试错误时重试,再次发生错误时等待时间会以指数级别增长。 1.3.  参考资料 kafka官方文档(Apache Kafka Documentation):用于死信队列自定义拦截器实现参考。 EventCenter现有设计文档(Event Center 使用说明文档 - CRD_EP_Software_Service - Confluence)及EventCenter学习报告(EventCenter组件学习报告 - CRD_EP_Software_Service - Confluence):包含模块结构、核心接口及消息处理模式说明。 kafka消息范式调研报告(kafka消息范式扩展调研报告 - CRD_EP_Software_Service - Confluence):包含kafka消息范式扩展概述,死信队列及延迟队列设计与实现。 VMS Kafka Topic信息汇总表(VMS Kafka Topic信息记录 - CRD_EP_Software_Service - Confluence):包含现有的Kafka Topic信息记录。 2.    调研结果 2.1 死信队列(Dead Letter Queue, DLQ)设计与实现 2.1.1 死信队列核心价值 死信队列是存储“无法被正常消费的消息”的特殊队列,核心作用: 错误隔离:避免异常消息阻塞主消费流程,保障主队列吞吐量。 问题追踪:集中存储失败消息(含上下文、错误日志),便于定位根因。 数据补偿:支持人工/自动修复后重新投递,减少数据丢失风险。 2.1.2死信队列常见设计方案 方案1:基于Kafka消费者拦截器(Consumer Interceptor) 实现原理: 在Kafka消费者端实现ConsumerInterceptor接口,拦截poll()返回的消息。当消费逻辑(如EventHandler.handle())抛出异常时,拦截器捕获失败消息,重试逻辑运行完成后,通过独立生产者将其发送至死信Topic(如vms_dlq_topic),并提交原消息的偏移量(避免重复消费)。 关键流程: 消费者从Kafka拉取消息(poll())。 拦截器预处理消息(如记录元数据)。 业务逻辑消费消息(调用handleEvent())。 若消费成功,正常提交偏移量;若失败,拦截器: 记录失败原因(异常堆栈、重试次数)。 通过独立生产者将消息发送至死信Topic。 提交原消息偏移量(避免重复消费)。 优点: 完全基于Kafka原生API,无需引入外部中间件。 灵活控制重试策略(如最大重试次数、间隔)。 与业务代码解耦(拦截器逻辑独立)。 缺点: 需开发拦截器逻辑,增加代码复杂度。 独立生产者需处理线程隔离(避免阻塞主消费线程)。 依赖消费者端配置(需为每个消费者组启用拦截器)。 方案2:基于Kafka生产者回调(Producer Callback) 实现原理: 在消息发送阶段,通过生产者的Callback接口捕获发送失败的消息(如网络异常、Broker不可用),将其直接发送至死信Topic。此方案主要处理“发送失败”的消息,而非“消费失败”的消息。 关键流程: 生产者调用send()发送消息,附加Callback。 若消息成功写入Kafka(RecordMetadata返回),流程结束。 若发送失败(Exception抛出),Callback捕获异常,将原消息+异常信息封装后发送至死信Topic。 优点: 直接捕获发送阶段的失败消息,避免未达Broker的消息丢失。 实现简单(仅需在生产者端添加回调逻辑)。 缺点: 仅覆盖“发送失败”场景,无法处理“消费失败”的消息。 死信Topic需与主Topic同步扩容,增加运维成本。 方案3:Confluent平台DLQ支持(Kafka生态扩展) 实现原理: Confluent平台(Kafka商业发行版)提供内置DLQ功能,通过在消费者配置中指定dead.letter.topic.name,当消息消费失败(如反序列化异常、处理超时)时,Confluent客户端自动将消息转发至死信Topic。 优点: 零代码开发(仅需配置),集成成本低。 自动处理反序列化失败、消费超时等异常场景。 缺点: 依赖Confluent商业组件(需评估License成本)。 仅支持Confluent客户端(与原生Kafka客户端不兼容)。 无法自定义重试策略(依赖默认逻辑)。 其他中间件对比 RabbitMQ DLX:通过绑定“死信交换器”实现,支持消息拒绝、超时、队列满等场景自动路由。优点是原生支持,缺点是与Kafka技术栈不兼容。 RocketMQ DLQ:为每个消费者组自动创建死信队列(%DLQ%+组名),重试耗尽后自动存储。优点是无需开发,缺点是死信无法自动消费(需人工干预)。 方案4:补充(Spring kafka的死信队列支持) 现有EventCenter引入的SpringKafka版本:2.5.2 RELEASE(VMS v2.1.0_dev引入的springkafka版本是2.8.0) SpringKafka2.2 版本开始,提供 DeadLetterPublishingRecoverer和SeekToCurrentErrorHandler 工具类,用于捕获异常,但是该版本提供的重试策略是阻塞实现,通过Thread.sleep()实现。 SpringKafka2.8 版本:推出 DefaultErrorHandler,支持重试策略和 DLQ 配置,SpringKafka2.8版本以后支持的重试策略是非阻塞实现,通过消息头携带kafka_retryTimestamp实现时间戳调度的。 特性 Spring Kafka 2.5.2 Spring Kafka 2.8 重试机制 仅支持阻塞式RetryTemplate 新增非阻塞式RetryTopic 事务管理 基础事务支持 增强事务隔离级别,支持Exactly-Once语义 错误处理 需手动配置SeekToCurrentErrorHandler 内置CommonErrorHandler,简化错误处理逻辑 线程模型 基于ConcurrentKafkaListenerContainer 优化线程池调度,减少资源争用 Spring Boot兼容性 需Spring Boot 2.3.x/2.4.x 需Spring Boot 2.6.x+ 2.1.3 死信队列方案对比总结 消费者拦截器 消费失败消息隔离 自主可控、兼容原生Kafka 需开发拦截器,线程隔离复杂 生产者回调 发送失败消息隔离 实现简单、覆盖发送阶段 不处理消费失败场景 Confluent DLQ 快速集成、低代码场景 零开发、自动转发 依赖商业组件,License成本高 2.2 延迟队列(Delayed Queue)设计与实现 2.2.1 延迟队列核心价值 延迟队列支持消息在指定延迟时间后被消费,典型场景包括: 定时任务触发(如设备事件30分钟后删除)。 失败重试(如消费失败后5分钟重试)。 订单超时取消(如未支付订单30分钟后自动关闭)。 2.2.2 延迟队列常见设计方案 方案1:基于Redis有序集合(ZSet)的Kafka扩展 补充:EventCenter支持Kafka消息队列实现和Local版本实现,早期曾评估Redis(采用List)作为消息队列方案(eventcenter-port-redis),在EventCenter文档中描述了弃用,并没有描述弃用原因。 根据现有redis实现代码分析的弃用原因: 单线程消费模型:每个Topic的消费者任务(RedisUnicastConsumerTask/RedisBroadcastConsumerTask)由独立线程池(ThreadPoolExecutor(1,1,...))驱动; 资源消耗高:每个Topic需独立维护线程池(topicExecutorServiceMap)和消费者任务(topicTaskMap),随着Topic数量增长(如百个业务Topic),线程资源和内存占用将显著增加。 监控与运维工具缺失:Kafka有成熟的监控工具,而Redis Stream的消息堆积、消费延迟等指标需自定义采集。 而本方案使用Redis将延迟消息存入Redis Zset,一个定时任务线程对集合进行扫描,会避免起过多线程的问题; 实现原理: 结合Kafka与Redis,将延迟消息暂存于Redis ZSet(以到期时间戳为score),通过定时任务扫描ZSet,将到期消息发送至Kafka目标Topic。 关键流程: 消息生产:生产者将延迟消息(含事件内容、延迟时间)存入Redis ZSet(Key:vms_delay_queue,score=当前时间+延迟时间)。 扫描触发:定时任务(如每1秒)执行ZRANGEBYSCORE vms_delay_queue 0 <当前时间戳>,获取到期消息。 消息投递:将到期消息通过Kafka生产者发送至目标Topic(如vms_delete_event_topic)。 异常处理:Redis拉取操作本身可靠(依赖AOF持久化),但发送至Kafka可能失败,原因包括Kafka Broker不可用、网络故障或序列化错误。独立的该方案失败重试逻辑是发送失败时重新插入Redis Zset,实际中由于死信队列的扩展有重试机制,失败重试逻辑直接交由死信队列处理。并不做拉取成功或失败的判断,消息从Redis拉取同时直接删除redis中的消息,调用send()及相关方法发送至目标topic; 优点: 支持任意延迟时间。 可能可以复用现有Redis资源。 分布式友好(通过Redis主从复制保障高可用)。 缺点: 需开发定时扫描逻辑(需处理并发扫描、消息去重)。 依赖Redis持久化(如AOF)保障消息不丢失。 扫描间隔与精度权衡(间隔过小增加Redis压力,过大导致延迟误差)。 方案2:基于时间轮算法的Kafka内部扩展 实现原理: 时间轮(Time Wheel)是一种高效的延迟任务调度算法,通过“轮盘槽位”管理延迟任务。Kafka的KafkaDelayedMessage和Netty的HashedWheelTimer均基于此原理。在Kafka中,可扩展消费者端实现时间轮,将延迟消息按到期时间分配至不同槽位,轮盘转动时触发消息投递。 关键设计: 时间轮结构:轮盘分为多个槽位(如100个),每个槽位代表1秒。 消息入轮:计算消息到期时间与当前时间的差值,分配至对应槽位(如延迟5秒的消息放入槽位5)。 轮盘转动:每秒移动一个槽位,触发当前槽位的消息投递。 优点: 时间复杂度O(1),高吞吐量下延迟低(百万级消息/秒)。 无需外部存储(依赖内存),响应速度快。 缺点: 需深度修改Kafka客户端源码(开发难度大)。 内存限制(槽位数量与消息容量需平衡,大延迟消息可能跨多轮)。 消息持久化困难(内存数据易丢失,需结合日志备份)。 方案3:基于Kafka分区与消费者暂停的分桶策略 实现原理: 仿照RocketMQ的18级固定延时等级设计(如1s、5s、10s、30s、1min、5min…2h等),在Kafka中为每个延时等级预设独立队列(可通过分区或独立Topic实现,如topic-delay-level-01对应1s延时,topic-delay-level-18对应2h延时)。由多个专用转发任务分别订阅所有18个延时等级队列,通过pause()暂停未到期队列的消费,并动态根据各队列中最早到期消息的时间戳,定时调用resume()恢复到期队列的消费,将消息转发至目标业务Topic。 关键流程: 消息生产:根据延迟时间将消息发送至对应分区(如topic-delay-5s、topic-delay-30s)。 转发任务:针对对应分区的消费者(转发任务),调用pause()暂停消费,暂停时间为该分区最早未消费消息的剩余暂停时间。 定时恢复:针对对应分区的消费者(转发任务),对到期的分区调用resume(),触发转发任务,转发至相应目标topic。 优点: 完全基于Kafka原生功能,无需外部组件。 分区隔离保障不同延迟消息的独立性。 缺点: 仅支持预设延迟等级(如5s、30s),无法动态调整。 分区数量随延迟等级增加而膨胀(如支持10种延迟需10个分区)。 需要维护多个分区,多个对应的转发任务。  其他中间件对比 RabbitMQ延迟交换器:通过x-delayed-message交换器实现,消息设置x-delay字段。优点是毫秒级精度,缺点是依赖插件且与Kafka不兼容。 RocketMQ延迟消息:支持18级预设延迟(1s~2h),Broker暂存后转发。优点是原生支持,缺点是延迟等级固定。 2.2.3 延迟队列方案对比总结 Redis ZSet 自定义延迟 灵活 需开发扫描逻辑,依赖Redis 时间轮算法 高吞吐量、低延迟场景 高效、低延迟 开发难度大,内存依赖 Kafka分区分桶 预设延迟、原生依赖场景 无需外部组件 延迟等级固定,分区膨胀 补充:kafka分区方案和Redis Zset方案的多维度比较 从调研中一些典型的测试结果中得到的两方案在资源和性能方面的比较如下表: 方案 吞吐量 延时误差 资源消耗 适用场景 Redis ZSet 受限于Redis扫描效率与定时任务频率(如1秒扫描一次),单次扫描需处理大量到期消息时可能出现瓶颈(如百万级消息/秒需高频扫描); 和定时任务间隔时间有关,如1s间隔误差小于1s 存储:Redis内存占用高 计算:承担ZSet排序、扫描(ZRANGEBYSCORE)等操作; 有自定义延时的需求,短时延迟较多,数据量不是特别高的场景。 Kafka分区分桶 依赖Kafka分区并行能力(多分区可并行消费),每个分区独立resume后可并行转发,理论吞吐量更高(与Kafka集群规模正相关); 队首消息时间戳精度决定,延迟误差小; 存储:消息直接存在Kafka 计算:计算资源集中在Kafka消费者(转发任务) 数据量比较大,仅有特定等级的延时需求。 在整体数据量规模小的时候,Redis Zset方案有更好的性能,但随着数据量扩展,kafka分区方案性能更好。为了满足后续有可能的大量延迟队列使用,拟选用Kafka分区延迟的延迟队列方案。 3.    功能需求 3.1.  总体描述 本模块为VMS系统事件相关组件EventCenter的扩展模块,聚焦死信队列(DLQ)与延迟队列的功能实现,解决现有EventCenter的异常事件处理不完善、时效性不足问题。模块通过低侵入式扩展(最小化修改EventCenter原生代码)提供配置化管理、灵活的重试策略及定时触发机制,支持开发人员通过EventCenter原生接口调用扩展功能(如发送延迟消息、启用死信队列),运维人员通过配置平台管理策略(如重试次数、延迟等级)。 扩展模块架构如下图所示: 3.1.1. 需求主体 需求主体 主体介绍 开发人员 VMS业务系统开发者,调用EventCenter的接口开发消息队列相关功能(如设备事件上报),需通过扩展接口启用死信队列、发送延迟消息以及配置相关策略。 运维人员 VMS系统运维人员,负责监控死信堆积量。 3.1.2. 功能模块划分 功能模块 模块编号 模块描述 配置管理模块 01 管理死信队列(开关、重试策略)的全局配置,支持开发人员通过EventCenter接口传递配置。 死信处理模块 02 基于EventCenter消费者拦截器扩展,捕获消费失败消息,执行重试逻辑,发送至DLQ并记录日志,与EventCenter原生消费流程解耦。 延迟消息管理模块 03 两种延迟方案:Redis ZSet/Kafka分区分桶(目前选用Kafka分区方案,可调整); 支持发送延迟消息,用延迟队列时通过调用相关方法选择延迟时间 监控与告警模块 04 监控死信堆积量、延迟消息触发成功率。 3.1.3. 用例图 3.2.  功能模块1:配置管理模块(模块编号01) 需求编号 需求说明 需求限制/背景 关联主体 优先级 MQE-01-0001 支持死信队列开关配置(默认关闭) 开发人员通过EventCenter的相关接口启用,避免非必要资源消耗。 开发人员 高 MQE-01-0002 支持自定义重试次数(默认3次) 开发人员通过接口设置,某些业务需修改重试次数。 开发人员 高 MQE-01-0003 支持重试间隔策略配置(默认指数退避,可选固定间隔、自定义) 开发人员通过setRetryPolicy(topic, policy)接口设置,默认指数退避(如1s→2s→4s)。【该需求是建立在可自定义时间的基础上,如果依照Kafka分区方案,只能选预设的相应等级】 开发人员 高 MQE-01-0004 支持死信名称配置(默认业务名_dlq_topic), 开发人员开启死信队列时需要设置对应业务的死信名称,未自定义业务使用默认。 开发人员 中 配置管理流程图如下图所示: 3.3.  功能模块2:死信处理模块(模块编号02) 典型场景:事件消费时,因业务逻辑异常导致消费失败,重试3次(默认值)后仍失败,消息需进入死信队列,避免阻塞主流程。 需求编号 需求说明 需求限制/背景 关联主体 优先级 MQE-02-0001 基于EventCenter消费者拦截器扩展,无侵入式集成 拦截器实现ConsummerInceptor接口,不修改原生消费逻辑。 开发人员 高 MQE-02-0002 捕获消费失败消息 拦截器监听EventHandler.handleEvent()的异常抛出。 系统 高 MQE-02-0003 执行重试逻辑(基于配置的重试次数和间隔),重试时间通过延迟队列实现 重试期间记录重试次数,避免无限重试。 系统 高 MQE-02-0004 重试耗尽后,将消息发送至DLQ(含原消息体、异常堆栈、重试次数) 死信消息通过独立Kafka生产者发送,不阻塞主消费线程。 系统 高 MQE-02-0005 提交原消息偏移量(仅在死信发送成功后) 避免重复消费(通过KafkaConsumer.commitSync(offset)实现)。 系统 高 MQE-02-0006 记录死信日志(含消息ID、Topic、业务标识、失败时间、错误原因) 运维人员订阅死信topic,通过其中日志追溯上下文 运维人员 中 补充:如果死信消息发送至死信队列失败,应该设置重试机制(默认次数和策略),重试后仍然失败应该记录日志,并且检测对应死信队列的健康状态。 3.4.  功能模块3:延迟消息管理模块(模块编号03) 典型场景:VMS设备删除时,需先删除事件上报,然后延迟一定时间后删除设备,避免设备删除后仍有事件上报导致数据不一致。将写出两个方案的需求,kafka方案作为目前选择,redis zset方案作为备选。 3.4.1 Redis ZSet方案需求(备选) 需求编号 需求说明 需求限制/背景 关联主体 优先级 MQE-03-0001 支持开发人员通过sendDelayedEvent(topic, event, delayS)方法发送自定义延迟消息 延迟时间单位为秒(如30分钟=1800s),兼容任意延迟需求。(如需要扩展,添加时间转换或设置不同时间单位的参数) 开发人员 高 MQE-03-0002 消息存储至Redis ZSet(Key格式:vms_delay_<topic>) 按Topic隔离数据,避免不同业务消息混淆(如设备事件与订单事件)。 系统 高 MQE-03-0003 定时扫描ZSet(间隔可配置,默认1秒)获取到期消息 扫描线程独立于EventCenter主线程,避免资源竞争。 系统 高 MQE-03-0004 到期消息通过EventCenter的send()接口发送至目标Topic(默认原Topic) 开发人员可通过参数指定目标Topic(如sendDelayedEvent(topic, event, delayS, targetTopic))。 开发人员 高 3.4.2 Kafka分区延迟方案需求 需求编号 需求说明 需求限制/背景 关联主体 优先级 MQE-03-0011 支持开发人员通过sendDelayedEvent(topic, event, delayLevel)接口选择预设延迟等级 延迟等级对应Kafka分区/topic(如等级1→1s分区),需与配置的等级匹配。 开发人员 高 MQE-03-0012 EventCenter自动根据延迟等级将消息发送至对应分区/topic(如topic-delay-5s) 分区/topic提前创建(需满足Kafka分区数≥最大等级数)。 运维人员 高 MQE-03-0013 转发任务暂停消费 消费者通过pause()暂停分区消费,避免提前拉取未到期消息。 系统 高 MQE-03-0014 根据当前时间恢复相应转发任务的消费(如5秒后恢复对应topic-delay-5s分区的转发任务) 恢复逻辑通过resume()接口实现,触发消息消费。 系统 高 目前根据调研结果,我的选型是kafka分区延迟方案,不用引入外部组件,吞吐量大,缺点是延时等级固定。备选是Redis Zset方案,可以提供自定义延迟时间的功能,但是主要的缺点是要引入redis。(具体方案还需要考虑业务延迟队列在业务中使用次数,在业务中有无需要自定义延迟时间的需求)。 补充:关于kafka分区策略选用的说明:可以选用分多个topic或多个分区: 每个延时等级对应1个Topic 优点:高隔离性:不同延时等级的消息完全隔离在独立Topic中,某等级的消息堆积、故障不会影响其他等级(如1s延时的高并发消息不会占用2h延时的资源)。配置灵活性:每个Topic可独立设置参数。消费逻辑简单:转发任务可直接订阅对应Topic,无需额外过滤或映射。扩展性强:新增延时等级时只需创建新Topic,不影响现有系统。 缺点:资源消耗大:每个Topic需占用Broker的元数据存储、文件句柄、网络连接等资源,18个Topic的资源开销远高于1个Topic(尤其是每个Topic配置多分区时)。管理复杂度高:需单独监控、运维18个Topic,增加管理负担。 每个延时任务对应一个分区:为了避免轮询定时任务的出现,依旧需要对应分区数的消费者分别订阅每一个分区 优点:资源利用率高:所有延时等级的消息共享1个Topic的资源,大幅降低资源消耗。管理简单:仅需维护1个Topic,监控、配置、故障排查更高效。 缺点:隔离性弱:所有延时等级共享Topic的配置;配置灵活性低:无法针对单个延时等级调整参数;扩展性受限:新增延时等级需扩容Topic的分区数,虽Kafka支持动态扩容,但会影响现有消费者的分区分配,且需维护“分区→延时等级”的映射表。 目前是打算选用多个topic,隔离性高,容错性好,避免可能的故障影响,而且更好应对更好的不同延迟级别流量不均衡的情况。(可以考虑将比较少使用的几个延迟等级放到1个topic多个分区里)。 3.5 功能模块4:监控与告警模块(模块编号04) 需求编号 需求说明 需求限制/背景 关联主体 优先级 MQE-04-0001 监控死信Topic堆积量(按Topic统计) 运维人员订阅死信topic查看,记录死信堆积量。 运维人员 高 MQE-04-0002 统计延迟消息触发成功率(成功数/总到期数) 支持开发人员评估方案效果。基于Redis ZSet方案统计延迟消息触发成功率,如通过Redis计数器实现 开发人员 中 4.    非功能性需求 4.1.  UI需求 无独立UI需求 4.2.  模块接口需求 接口名称 交互模块 接口描述 enableDeadLetter(topic, enable) EventCenter 开发人员调用,启用/禁用指定Topic的死信队列(低侵入,不修改原生send()逻辑)。 setRetryCount(topic, count) EventCenter 开发人员调用,重试次数设置(默认为3次) setRetryPolicy(topic, policy) EventCenter 开发人员调用,重试策略设置(默认为指数退避) sendDelayedEvent(topic, event, delayS) EventCenter 开发人员调用,发送Redis ZSet方案的延迟消息(兼容原生send()的序列化配置)。 sendDelayedEvent(topic, event, delayLevel) EventCenter 开发人员调用,发送kafka分区方案的延迟消息(自动路由至对应分区)。 补充,针对死信配置,可以不调用死信接口,可以通过重载send方法(例如,补充参数死信开关,死信配置等)或者通过配置文件(在死信配置类上添加@ConfigurationProperties(prefix = "eventcenter.deadletter")注解)。(同理可以包装handler,重载register方法)以方便开发人员调用为原则。 4.3.  性能需求 功能点 性能指标 死信消息处理 并发处理能力≥1000条/秒(单消费者组),响应时间≤200ms(不影响主消费流程)。 Redis ZSet延迟消息方案(备选方案) 扫描间隔误差≤1秒(1秒间隔场景),单线程扫描最大处理1000条/次(可配置)。 kafka分区延迟消息方案 分区恢复延迟≤500ms(确保到期消息及时消费),支持18个预设等级 4.4.  用户体验需求 开发人员调用扩展接口 :接口文档完整率100%,示例代码覆盖90%以上常用场景(如死信启用、延迟发送)。 4.5. 用户技术支持需求 支持项 描述 死信消息追溯 运维人员支持通过消息ID查询原消息内容、异常堆栈、重试记录。 告警日志导出 支持导出一定时间内的告警记录(含触发时间、处理人、解决方案),用于复盘优化。 4.6 单元测试覆盖率 模块 覆盖率目标 说明 配置管理模块 80% 覆盖配置解析、接口调用校验逻辑。 死信处理模块 80% 覆盖拦截器逻辑、重试策略、死信发送等核心流程。模拟消费失败场景,验证重试次数、死信消息是否包含完整上下文。 延迟消息管理模块 80% 覆盖ZSet操作/分区分桶路由、扫描触发等关键逻辑。测试不同延迟时间的消息是否准时触发,Redis扫描间隔配置是否生效。 5.    可行性分析 开发任务 需求编号范围 难度 优先级 影响范围 配置管理模块开发 MQE-01-0001~0004 中 高 EventCenter接口层。 死信处理模块开发 MQE-02-0001~0006 高 高 Kafka消费者拦截器、EventCenter事件上下文。 延迟消息管理模块开发 MQE-03-0001~0004(备选)、MQE-03-0011~0014 高 高 Redis客户端(备选)、Kafka分区路由逻辑、EventCentersend()接口扩展。 监控与告警模块开发 MQE-04-0001~0002 低 中 死信队列消费者。 说明: 核心功能(死信处理、延迟消息管理)需优先实现,确保解决设备删除数据不一致、消费失败丢失问题。 低侵入性设计通过接口扩展实现(如拦截器、sendDelayedEvent()),避免修改EventCenter原生代码(如send()、registerBroadcast()的核心逻辑)。需求分析报告如上,请按以下评审意见修改延迟队列通过Redis Zset方案实现的可用性分析 补充选取18个延迟分区的理由 补充VMS业务分析,尤其是可能和延迟队列相关的业务 死信消息发送至死信队列失败后需要提供配置选项,方便业务人员调用,失败后进行什么处理(比如告警,记录日志),需要设置默认值 需求分析内容过多,将设计的部分移到概要设计中,需求分析保留调研方案 整理需要提供哪些新的接口,哪些方法重载了,需要写完善 需求分析4.3节性能需求进行修改 支持延迟队列配置调整,新增强制延迟时间和非强制延迟时间,强制延迟时间对应目标分区方案, 只有强制延迟时间发送失败才抛异常 某些业务不需要精确延时,只需要延迟一定时间, 支持非强制延迟时间策略配置(如向上/向下取整,四舍五入方案) 分离强制延迟时间性能指标和非强制延迟时间性能指标,分别计算 结合实际业务申请topic方式,kafka采用分级延迟topic而不是延迟分区 结合实际业务申请topic方式,死信topic应修改为不支持开发人员配置 补充内容:未发现对应死信topic创建应该告警 补充死信队列和延迟队列的local版本实现,使需求分析和概要设计文档完整 补充压力测试模块数据和环境 尽量接入VMS Event模块,测试中设计死信注入故障(例如:模拟消费者崩掉的场景) Yu He 概要设计中压力测试部分,消息压力量设计偏小,进行修改 ;评审意见中有概要设计的部分,暂时先不用给出结果,先按照评审意见对需求分析进行修改
09-27
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值