ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Redis可视化工具选型与实战:从连接集群到SCAN增量导出

Redis可视化工具选型与实战:从连接集群到SCAN增量导出 简介这是一份面向Redis初学者与运维开发人员的Windows端可视化数据库管理工具以Redis Desktop Manager为核心帮助用户摆脱命令行束缚通过图形界面完成连接、浏览与数据操作。压缩包共630个文件以59个dll动态库、23个exe可执行程序、20个jar包及22个properties配置为主另含大量时区数据、字体与图片资源整体约34.64MB解压后运行RedisClient即可使用。工具支持连接本地或远程服务器直观查看db0至db15中的键值对及过期时间覆盖字符串、哈希、列表、集合、有序集合等类型的增删改查并内置命令执行、JSON/CSV/XML导入导出、SSL与超时设置、多语言界面等功能。目前已有484人学习下载适合需要高效管理Redis、进行数据迁移备份或性能测试的开发者参考使用。1. 为什么我又把 Redis 可视化工具翻出来重装了一遍上周排查一个线上缓存击穿问题redis-cli敲了半小时SCAN、TTL、MEMORY USAGE眼睛都快看花了。当时就想要是有个能直接看 key 分布、能按前缀过滤、能实时刷新的可视化界面至少能省一半时间。Redis 可视化工具就是干这个的——把 Redis 里那些二进制安全的 key、嵌套的 hash、动辄几十万元素的 zset用图形界面摊开给你看。它适合三类人日常要巡检缓存的后端开发、需要快速定位热 key 的运维、以及刚接触 Redis 数据结构想直观理解的学生。但这类工具坑也不少选错了要么连不上集群要么一开监控就把生产库拖慢。这篇就把我实际拆过、配过、踩过坑的流程完整写一遍从选型到连集群到排错你照着能复现。2. 选型先看协议兼容单机、哨兵、集群三种连法差别在哪2.1 先搞清楚工具底层走的是什么协议绝大多数 Redis 可视化工具底层都是封装了RESP协议也就是 Redis 客户端和服务器之间那套文本协议。你界面上点一个 key工具实际发出去的是TYPE key、OBJECT ENCODING key、MEMORY USAGE key这几条命令。理解这一点很重要因为后面连不上、显示不全、刷新卡顿根子往往不在界面而在协议层。常见工具有几类实现路线。一类是纯客户端直连工具进程直接和 Redis 建 TCP 连接比如 RedisInsight、Another Redis Desktop Manager 这类桌面端。另一类是 Web 端中间加一层服务端代理浏览器连代理、代理连 Redis比如一些开源的 Web 管理面板。还有一类是命令行增强型本质还是 CLI只是加了 TUI 界面。选型时我一般看三个硬指标支不支持集群模式、支不支持 TLS、支不支持SCAN游标分批。前两个决定你能不能连上生产第三个决定你打开一个百万 key 的库时会不会直接卡死。很多工具默认用KEYS *拉全量这在生产库上就是灾难一定要确认它用的是SCAN。2.2 单机、哨兵、集群的连接参数怎么填单机最简单填 host、port、密码就行。哨兵模式要额外填哨兵节点列表和 master 名称工具会先连哨兵问出当前 master 地址再连。集群模式最容易被坑因为集群有 16384 个槽key 分散在多个节点上工具必须支持CLUSTER SLOTS或CLUSTER SHARDS来发现拓扑。下面是一个典型的连接配置结构我用伪配置的方式写出来你对照自己工具的填写项# 单机 host: 127.0.0.1 port: 6379 password: your_password db: 0 # 哨兵 sentinels: - 127.0.0.1:26379 - 127.0.0.1:26380 master_name: mymaster password: your_password # 集群 cluster_nodes: - 127.0.0.1:7000 - 127.0.0.1:7001 - 127.0.0.1:7002 password: your_password # 注意集群模式没有 db 概念只能连 db 0逻辑说明哨兵模式下工具不会直接连你填的哨兵节点做数据操作而是先发SENTINEL get-master-addr-by-name拿到 master 真实地址。集群模式下工具连上任意一个节点后发CLUSTER SLOTS拿到槽位和节点的映射表之后每个 key 先算 CRC16 再对 16384 取模路由到对应节点。参数上最容易错的是集群的密码。有些工具把密码填在「节点密码」一栏有些要求填在「集群密码」一栏填错位置就连不上。另外集群模式下SELECT命令是被禁用的如果你工具界面上还有 db 选择框说明它没做集群适配趁早换。2.3 一个能跑的连接验证脚本在把工具连上生产之前我习惯先用脚本确认网络和权限没问题避免在界面上瞎试。下面这段用 Python 的 redis 库做连通性验证import redis from redis.cluster import RedisCluster # 单机验证 def check_standalone(host, port, password): r redis.Redis(hosthost, portport, passwordpassword, socket_timeout3, socket_connect_timeout3) # ping 确认连通 print(ping:, r.ping()) # 确认能否执行 scan而不是 keys cursor, keys r.scan(count10) print(scan sample:, keys[:5]) # 看下内存信息 print(used_memory_human:, r.info(memory)[used_memory_human]) # 集群验证 def check_cluster(nodes, password): rc RedisCluster(startup_nodesnodes, passwordpassword, socket_timeout3) print(cluster ping:, rc.ping()) # 集群下 scan 要指定目标节点这里只做连通性演示 print(cluster info:, rc.cluster_info()[cluster_state]) if __name__ __main__: check_standalone(127.0.0.1, 6379, your_password) # check_cluster([{host: 127.0.0.1, port: 7000}], your_password)逻辑说明socket_timeout和socket_connect_timeout一定要设默认不设的话网络不通时脚本会挂很久。scan的count参数是提示值不是精确值Redis 可能一次返回多于或少于 count 的 key这是正常的。集群验证里cluster_state返回ok才说明集群健康。参数说明count10适合验证生产巡检可以调到 1000 左右太大单次阻塞时间长太小往返次数多。socket_timeout3是秒内网可以设 2跨机房设 5。3. 把 key 看明白数据结构展示、内存分析和慢查询定位3.1 五种基础类型在界面上到底怎么呈现Redis 有 string、list、hash、set、zset 五种基础类型加上后来的 stream、bitmap、hyperloglog、geo。可视化工具的价值就在于把这些类型用合适的控件展示出来。string 直接显示文本或十六进制list 显示成有序列表hash 显示成键值表格set 显示成无序集合zset 显示成带 score 的排序表。这里有个坑string 类型如果存的是序列化后的对象比如 Java 的JdkSerializationRedisSerializer或者 Protobuf界面上直接显示就是乱码。好的工具会提供 hex 视图和多种编码切换。我一般会先在工具里看OBJECT ENCODING key的结果如果是embstr或raw说明是普通字符串如果是int说明存的是整数如果是listpack或quicklist那是 list 的底层编码。下面这段脚本可以批量看一个前缀下所有 key 的类型和编码配合工具界面用import redis r redis.Redis(host127.0.0.1, port6379, passwordyour_password) def inspect_keys(pattern, limit50): cursor 0 count 0 while True: cursor, keys r.scan(cursorcursor, matchpattern, count100) for k in keys: ktype r.type(k).decode() encoding r.object(encoding, k).decode() ttl r.ttl(k) # memory usage 在 4.0 可用 try: mem r.memory_usage(k) except Exception: mem -1 print(f{k.decode():40s} type{ktype:8s} enc{encoding:12s} ttl{ttl:6d} mem{mem}) count 1 if count limit: return if cursor 0: return inspect_keys(user:*)逻辑说明scan用游标迭代不会阻塞 Redis。type返回 key 的类型object encoding返回底层编码ttl返回剩余过期时间-1 表示永不过期-2 表示 key 不存在memory_usage返回该 key 占用的字节数。参数说明match支持 glob 通配符user:*匹配所有 user 开头的 key。count100是每次迭代的提示数量。limit50是脚本自己限制的输出条数避免刷屏。3.2 内存分析怎么找出那些偷偷吃内存的大 key大 key 是 Redis 最常见的性能杀手。一个几百万元素的 hash 或者 list单次操作就可能阻塞主线程几百毫秒。可视化工具一般都有内存排序功能但你要知道它底层调的是什么。MEMORY USAGE key是精确计算单个 key 的内存但它是 O(N) 的对超大 key 会慢。DEBUG OBJECT key能看序列化长度但生产环境通常禁用了DEBUG命令。我一般用两段式排查先用SCAN采样对每个 key 调MEMORY USAGE按大小排序再对 top N 的 key 用TYPE和STRLEN、LLEN、HLEN、SCARD、ZCARD看元素数量。import redis import heapq r redis.Redis(host127.0.0.1, port6379, passwordyour_password) def find_big_keys(pattern*, sample1000, topn20): cursor 0 seen 0 heap [] # 小顶堆存 (mem, key) while seen sample: cursor, keys r.scan(cursorcursor, matchpattern, count100) for k in keys: try: mem r.memory_usage(k) or 0 except Exception: mem 0 if len(heap) topn: heapq.heappush(heap, (mem, k)) elif mem heap[0][0]: heapq.heapreplace(heap, (mem, k)) seen 1 if cursor 0: break # 从大到小输出 for mem, k in sorted(heap, reverseTrue): ktype r.type(k).decode() size 0 if ktype string: size r.strlen(k) elif ktype list: size r.llen(k) elif ktype hash: size r.hlen(k) elif ktype set: size r.scard(k) elif ktype zset: size r.zcard(k) print(f{k.decode():50s} mem{mem:10d} type{ktype:8s} elements{size}) find_big_keys(user:*, sample2000, topn10)逻辑说明用小顶堆维护 top N避免把所有 key 都存内存里。memory_usage返回字节数strlen、llen等返回元素个数。两者结合能判断是「元素多」还是「单元素大」。参数说明sample2000是采样上限生产库 key 多的时候不要全量扫。topn10输出前 10 个大 key。count100控制每次 SCAN 的批量。3.3 慢查询和实时命令监控怎么配合工具用可视化工具一般有「慢查询」面板和「命令监控」面板。慢查询读的是 Redis 的SLOWLOG需要 Redis 配置了slowlog-log-slower-than阈值默认是 10000 微秒也就是 10 毫秒。命令监控用的是MONITOR命令会把所有执行的命令实时推给你。这里有个血泪经验MONITOR在生产环境慎用。它会显著增加 Redis 的 CPU 和网络开销因为每条命令都要复制一份发给监控客户端。我一般只在排查特定问题时短时间开比如 30 秒抓完就关。工具界面上如果有「实时监控」开关用完一定记得关掉。慢查询的配置可以动态改# 设置慢查询阈值为 5 毫秒 redis-cli config set slowlog-log-slower-than 5000 # 慢查询日志最多保留 128 条 redis-cli config set slowlog-max-len 128 # 查看慢查询 redis-cli slowlog get 10逻辑说明slowlog-log-slower-than单位是微秒设成负数会关闭慢查询设成 0 会记录所有命令。slowlog-max-len控制日志条数太大会占内存。slowlog get后面跟条数。参数说明生产环境阈值一般设 5000 到 10000 微秒也就是 5 到 10 毫秒。低于这个值的命令通常不值得关注。slowlog-max-len设 128 到 256 够用。4. 避坑排查连不上、显示乱码、刷新卡死的真实原因4.1 现象工具显示连接成功但 key 列表是空的原因最常见的是连到了错误的 db。单机模式下 Redis 有 16 个 db默认连 db 0但你的数据可能在 db 1 或 db 2。集群模式下没有 db 概念如果工具还让你选 db说明它没适配集群实际连的还是某个单机节点。解决先用redis-cli info keyspace看各个 db 的 key 数量确认数据在哪个 db。集群模式下确认工具版本支持CLUSTER SLOTS并且连接时不要指定 db。redis-cli -h 127.0.0.1 -p 6379 -a your_password info keyspace # 输出示例 # db0:keys1000,expires100,avg_ttl3600000 # db1:keys5000,expires0,avg_ttl04.2 现象key 显示出来了但 value 是乱码原因value 被序列化了。Java 项目常用 JDK 序列化或 ProtobufPython 项目可能用了 pickle这些序列化后的字节流在界面上直接按 UTF-8 解码就是乱码。解决确认工具支持 hex 视图或自定义解码器。如果工具不支持就先用脚本反序列化看内容。JDK 序列化的数据可以用redis-cli --raw get key拿到原始字节再用对应语言的库反序列化。import redis import pickle r redis.Redis(host127.0.0.1, port6379, passwordyour_password) raw r.get(user:1001:profile) # 如果是 pickle 序列化 try: obj pickle.loads(raw) print(obj) except Exception as e: print(not pickle, raw hex:, raw.hex()[:100])逻辑说明get返回的是 bytespickle.loads尝试反序列化。失败就打印 hex 前 100 字节人工判断序列化格式。参数说明raw.hex()把字节转成十六进制字符串方便看魔数。JDK 序列化的魔数是ACEDProtobuf 没有固定魔数但通常有字段编号。4.3 现象打开工具后 Redis 响应变慢甚至超时原因工具在后台跑了KEYS *或者高频INFO。KEYS *是 O(N) 的百万 key 的库上执行会阻塞主线程好几秒。有些工具为了显示 key 总数每隔几秒发一次DBSIZE虽然DBSIZE是 O(1)但高频调用也有开销。解决抓包或者用MONITOR短时间看工具发了什么命令。如果是KEYS *在工具设置里找「使用 SCAN」选项找不到就换工具。如果是高频INFO把刷新间隔从 1 秒调到 10 秒以上。# 短时间监控抓 5 秒 timeout 5 redis-cli -h 127.0.0.1 -p 6379 -a your_password monitor | head -100逻辑说明timeout 5让命令 5 秒后自动结束避免一直挂着。head -100只看前 100 条。输出里如果看到大量KEYS或INFO就是工具的问题。参数说明monitor本身有性能开销只用于短时排查。生产环境建议在从库上做或者业务低峰期做。4.4 现象集群模式下部分 key 看不到原因工具只连了集群的一个节点没有做槽位路由。集群的 key 按 CRC16 分散在多个节点只连一个节点只能看到那个节点上的 key。解决确认工具支持集群模式连接时填多个种子节点。如果工具不支持可以用redis-cli -c的集群模式手动查或者用支持集群的客户端库自己写脚本遍历所有节点。from redis.cluster import RedisCluster rc RedisCluster(startup_nodes[ {host: 127.0.0.1, port: 7000}, {host: 127.0.0.1, port: 7001}, ], passwordyour_password) # 遍历所有主节点 for node in rc.get_primaries(): print(node:, node.host, node.port) # 在该节点上 scan cursor 0 while True: cursor, keys node.scan(cursorcursor, count100) for k in keys[:5]: print( key:, k) if cursor 0: break逻辑说明get_primaries返回所有主节点每个主节点单独scan。这样能覆盖所有槽位。参数说明startup_nodes至少填两个避免单点。count100控制批量。4.5 现象TLS 连接报证书错误原因Redis 6 以后支持 TLS但工具可能没加载正确的 CA 证书或者证书里的 CN/SAN 和连接地址不匹配。解决确认工具支持 TLS并且填了 CA 证书路径。如果是自签证书需要把 CA 证书导入工具的信任库。连接地址要用证书里签的域名不能用 IP除非证书里签了 IP SAN。# 用 redis-cli 测试 TLS 连接 redis-cli -h redis.example.com -p 6379 --tls \ --cacert /path/to/ca.crt \ --cert /path/to/client.crt \ --key /path/to/client.key \ ping逻辑说明--tls开启 TLS--cacert指定 CA 证书--cert和--key是客户端证书如果启用了双向认证。ping返回PONG说明连接成功。参数说明如果服务端只要求单向认证--cert和--key可以省略。--cacert必须是 PEM 格式。5. 进阶技巧用 SCAN 游标做增量导出和 key 空间分析5.1 为什么不要用 KEYS 而要用 SCAN 做导出KEYS *会一次性遍历所有 key 并返回时间复杂度 O(N)在百万级 key 的库上会阻塞 Redis 主线程数秒甚至数十秒。SCAN用游标分批返回每次只处理一小部分虽然总时间可能更长但不会造成长时间阻塞。做 key 导出、key 空间分析、批量 TTL 检查都应该用SCAN。SCAN有几个特性要记住游标从 0 开始返回 0 表示迭代结束COUNT是提示值不是精确值在迭代过程中如果有 key 增删可能漏掉或重复这是允许的。所以导出场景下如果要求精确需要在业务低峰期做或者接受少量误差。5.2 一个完整的增量导出脚本下面这个脚本把匹配前缀的 key 分批导出到文件支持断点续传记录游标import redis import json import os r redis.Redis(host127.0.0.1, port6379, passwordyour_password) def export_keys(pattern, out_file, cursor_file, batch200): # 读取上次的游标支持断点续传 cursor 0 if os.path.exists(cursor_file): with open(cursor_file) as f: cursor int(f.read().strip()) total 0 with open(out_file, a) as out: while True: cursor, keys r.scan(cursorcursor, matchpattern, countbatch) for k in keys: ktype r.type(k).decode() ttl r.ttl(k) # 只导出元信息不导出 value避免大 value 撑爆文件 record { key: k.decode(errorsreplace), type: ktype, ttl: ttl, } out.write(json.dumps(record, ensure_asciiFalse) \n) total 1 # 每批写一次游标 with open(cursor_file, w) as f: f.write(str(cursor)) if cursor 0: break print(fexported {total} keys) export_keys(user:*, keys_export.jsonl, scan_cursor.txt)逻辑说明scan的游标每次迭代后更新写入cursor_file。如果脚本中断下次从上次游标继续。导出的是 JSONL 格式每行一个 JSON 对象方便后续用jq或 Python 处理。参数说明batch200是每次 SCAN 的提示数量可以根据网络延迟调整内网可以到 1000。errorsreplace处理非 UTF-8 的 key 名避免解码报错。只导出元信息不导出 value是因为 value 可能很大导出后文件会爆炸。5.3 用导出的数据做 key 空间分析导出后可以用 pandas 做分析看 key 的类型分布、TTL 分布、前缀分布import json import pandas as pd from collections import Counter records [] with open(keys_export.jsonl) as f: for line in f: records.append(json.loads(line)) df pd.DataFrame(records) print(总 key 数:, len(df)) print(\n类型分布:) print(df[type].value_counts()) print(\nTTL 分布:) # 把 ttl 分桶 df[ttl_bucket] pd.cut(df[ttl], bins[-2, -1, 0, 3600, 86400, 604800, float(inf)], labels[不存在, 永不过期, 1小时内, 1天内, 7天内, 7天以上]) print(df[ttl_bucket].value_counts()) print(\n前缀分布 top10:) prefixes df[key].str.split(:).str[0] print(Counter(prefixes).most_common(10))逻辑说明value_counts统计各类型数量。pd.cut把 TTL 分桶-2表示 key 不存在导出和查询之间被删了-1表示永不过期。前缀分布用split(:)取第一段。参数说明分桶边界按业务调整比如会话类 key 可能 1 小时内居多缓存类可能 1 天居多。most_common(10)取前 10 个前缀。5.4 一个我常犯的错误有次我在生产库上直接跑导出脚本忘了加match参数结果SCAN遍历了整个库虽然没阻塞但导出了几十万条记录文件几百 MB分析脚本跑了十分钟。从那以后我每次跑SCAN类脚本都强制先加match前缀并且先count10试跑一次看输出量。另外游标文件一定要写不然中断了就得从头来。希望这些能帮到你少走点我踩过的弯路。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进