redis slow log 可通过 config set slowlog-log-slower-than 1000 降低阈值、slowlog get 查看慢日志、重点关注 sort/smembers 等 o(n) 命令及耗时与参数,结合 slowlog-max-len 调整与 info 辅助排查,再以 scan/sscan 替代并客户端拦截高危命令实现精准定位与治理。

如何用 Redis Slow Log 快速定位高耗时命令
Redis 自身提供 SLOWLOG 是最直接、最低侵入的巡检手段。它不依赖客户端埋点,也不需要改应用代码,只要开启就能捕获实际执行慢的命令。
默认只记录执行时间 ≥ 10ms 的操作,但线上业务对延迟敏感时,建议调低到 1ms:
CONFIG SET slowlog-log-slower-than 1000
SLOWLOG GET 10 可查最近 10 条慢日志,重点关注 SORT、SMEMBERS、KEYS、HGETALL 这类 O(N) 或更差的命令。注意看第三字段(执行耗时,单位微秒)和第四字段(完整命令,含参数)——尤其当 key 名含通配符或集合 size 很大时,基本就是根因。
- 务必检查
slowlog-max-len是否过小(默认 128),否则旧日志被轮转丢弃,建议设为 1000+ 方便回溯 - 如果
SLOWLOG几乎为空,但监控显示 latency 高,可能是网络抖动、fork 阻塞或 AOF rewrite 导致,需结合INFO commandstats和INFO stats综合判断
为什么 SORT 和 SMEMBERS 在生产环境特别危险
SORT 看似只是排序,但实际行为远比名字复杂:它会先全量读取 key 对应数据(如 list/set/zset),再在内存中排序,最后写回或返回结果。若 list 有 50 万元素,SORT mylist 就是 O(N log N) + O(N) 内存占用,极易触发 Redis 主线程卡顿。
SMEMBERS 同理:哪怕只想要其中几个成员,它也必须把整个 set 全部加载进内存、序列化、再发给客户端。一个含 20 万字符串的 set,响应体可能超 5MB,不仅慢,还挤占带宽和 client buffer。
-
SORT带BY或GET参数时更糟,会额外触发多次 key 查询,放大延迟 -
SMEMBERS在 Redis 6.0+ 虽支持SSCAN渐进式遍历,但业务代码若仍用SMEMBERS,就等于主动放弃流控能力 - 这两类命令在集群模式下无法跨 slot 执行,一旦 key 不在本地节点,直接报
CROSSSLOT错误
用 SCAN 替代 KEYS、用 SSCAN/SSREM 分批处理替代 SMEMBERS
发现 SMEMBERS 频繁出现后,不能只改一句命令,而要重构访问模式。核心思路是「不一次性拿全部,而是按需分批」。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
例如原逻辑:members = redis.smembers("user:123:friends") → 改为基于游标分页:
cursor = 0<br>while cursor != 0:<br> cursor, members = redis.sscan("user:123:friends", cursor, count=100)<br> process_batch(members)
注意 count 不是硬限制,只是 hint,实际返回数量可能更少;游标为 0 表示遍历结束。
- 对需要“随机抽样”的场景,避免用
SRANDMEMBER key count拿大量数据,改用多次小SRANDMEMBER key 1+ 去重 - 若业务真需要全量 set 成员做计算(如交集),优先考虑把逻辑下沉到 Lua 脚本里,在服务端完成,减少网络往返和 client 内存压力
-
KEYS pattern必须禁用,一律改用SCAN cursor MATCH pattern COUNT 100,并确保应用能处理游标重试逻辑
如何在代码层拦截高危命令(以 Python redis-py 为例)
靠人工巡检容易漏,最稳妥的是在客户端加一道过滤。redis-py 提供 ConnectionPool 和自定义 Connection,可在 send_command 前做白名单校验。
简单实现方式:包装 Redis 实例,覆盖 execute_command 方法:
class SafeRedis(Redis):<br> DANGEROUS_COMMANDS = {"sort", "smembers", "hgetall", "keys"}<br> def execute_command(self, *args, **kwargs):<br> cmd = args[0].lower()<br> if cmd in self.DANGEROUS_COMMANDS:<br> raise RuntimeError(f"Blocked dangerous command: {cmd}")<br> return super().execute_command(*args, **kwargs)
上线前务必灰度验证——有些管理脚本或老模块可能合法使用 SMEMBERS,可加开关或白名单 key 前缀(如只允许 cache:*:members)。
- 不要只拦命令名,
SORT带STORE参数时风险略低(结果存 server 端),但依然要评估数据规模 - Java 侧可用 Lettuce 的
CommandHandler或 Jedis 的BinaryJedis包装;Go 用redis.UniversalClient中间件拦截 - 拦截只是兜底,真正要解决的是业务为何需要全量拉取——是不是缓存设计不合理?是不是本该用关系查询却压到 Redis?
高复杂度命令的问题从来不在 Redis 本身,而在于我们是否清楚每一次 SMEMBERS 背后到底有多少数据、多少客户端在同时发起、失败后有没有降级路径。巡检不是找 bug,是确认数据规模与操作方式是否匹配。










