fix(redis): 生产路径禁止使用 KEYS,改用 SCAN,修复 Redis 阻塞导致巡检服务不可用
现象:巡检服务(dliip-patrol)容器连续约 9 小时 unhealthy;Tomcat 200 个请求线程
全部阻塞在 Lettuce(AsyncCommand.await 无超时,永不失败),容器 CPU 在 2%~500% 间跳变,
健康检查(curl 127.0.0.1:9901,10s 超时)连续 284 次超时。
根因:inspect-job 定时调用 /exec/makeCurrentDayTask,该接口遍历所有启用任务,对每个
"间隔执行"任务的每个执行时刻调用一次 parseTaskToRedis,而 parseTaskToRedis 内部用
redisService.keys(TASK_CODE@taskCode@*) 做全库扫描来清理策略 key。于是一次调用会对
Redis 发起成百上千次 KEYS:Redis 单线程,单次 KEYS 约 13ms 且返回体巨大(实测输出
8MB/s,单个客户端连接积压 321 条回复 / 5.8MB 输出缓冲),KEYS 执行期间其它所有客户端
的命令全部排队,实测 Redis 响应延迟 avg 287ms / max 1365ms,进而导致上游业务请求堆积、
Tomcat 线程池被占满、健康检查排不上队而超时。
修复:
1. RedisService 新增 scan(pattern[, count]),基于 SCAN 游标分批读取,不阻塞其它客户端;
原 keys() 标记 @Deprecated 并注明生产环境禁用。
2. 全仓库 11 处 redisService.keys() 调用点改为 scan()。
3. PatrolTaskExecController:抽出 cleanTaskKeysAfter(),策略 key 清理由"每个执行时刻一次"
改为"每个任务一次",一次 makeCurrentDayTask 的 KEYS 次数从成百上千次降到与任务数同量级。
4. PatrolTaskController:批量删除任务时原为 N 个 taskId 扫描 N 次全库,改为只扫描一次。
影响面:仅改变"按 pattern 查找 key"的实现方式(KEYS→SCAN,语义等价;SCAN 可能重复返回
同一个 key,对删除/遍历场景无影响),未改动任何业务判定逻辑。
|