ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

redis-py服务控制与监控函数实战:从ping到slowlog的巡检指南

redis-py服务控制与监控函数实战:从ping到slowlog的巡检指南 我用 redis-py 写了快五年的业务代码坦白说真正让我觉得这个客户端“像一个成熟工具箱”的不是 get/set 那套基本操作而是它那批专门做服务控制与状态监控的辅助函数。日常开发里大家把redis.Redis(host..., decode_responsesTrue)当成一个能存能取的缓存桶但 Redis 其实是一台自带体检报告、客户端台账、慢查询日志和配置后台的小型服务器而 redis-py 把这些能力全部映射成了一个个可以直接调用的方法。这篇内容就是围绕这些辅助函数展开适合已经能熟练读写 Redis、但还没正式接触过连接诊断、资源统计和动态配置的同学我会把每个核心函数怎么用、返回什么、有什么坑以及我整理的一套巡检脚本全部摊开讲。1. 先搞懂 redis-py 的辅助函数到底对应哪些服务端语义1.1 业务读写和服务管理是两码事很多刚接触 redis-py 的人会有一个错觉client.keys(*)就能遍历所有 keyclient.info()好像也能用但从来没认真研究过返回值里到底有什么。这里面的核心问题是Redis 的服务端命令本身分成两大类一类是处理 KV 数据的读写命令另一类是处理服务器自身状态的管理命令。redis-py 并没有像某些 ORM 那样把管理命令单独藏到一个“控制面板”对象里而是很朴素地全部放在同一个Redis实例上只是命名上保持了服务端命令的原样比如ping、dbsize、config_get、client_list、slowlog_get。从工程角度看这种设计很符合 Redis 一贯的“命令即接口”理念。你不需要额外引入一个 SDK也不需要换连接方式来执行管理操作。但代价是如果你不知道服务端有哪些管理命令你就根本不知道 redis-py 里还有这些方法。我经常看到线上出了问题同事第一反应是翻业务代码却忘了用一条redis-cli info memory就能看到内存碎片的趋势其实用 redis-py 写个定时采集脚本也一样能拿到。Redis 的官方命令文档中管理类命令主要分散在连接管理、服务端信息、键空间通知、慢查询、客户端管理、配置管理这几组。redis-py 对这些命令的封装程度不太一样有的返回原始字符串有的已经帮你解析成字典或列表所以理解映射关系就变得特别重要。1.2 redis-py 的命令调用通用规则在使用这些辅助函数之前有一个基础规则值得刻进脑子里redis-py 里所有方法名基本就是把服务端命令改成小写下划线形式。服务端的CONFIG GET对应config_getCLIENT LIST对应client_listSLOWLOG GET对应slowlog_getINFO MEMORY在早期版本里直接传参info(memory)新版本还支持info(sectionmemory)细节略有一点变化但总体规律非常明显。另外要理解返回值的解析逻辑。redis-py 底层的响应回调response callback会在连接对象初始化时注册一批命令的“自定义解析器”。比如info方法拿到服务端返回的纯文本后会按# Section和key:value切分组装成嵌套字典client_list会把每行按空格拆成字段对再变成一个字典列表。这意味着你直接用这些方法时拿到的是结构化数据而不是一行一行手写的字符串这比原始命令行体验好很多。不过有一点要特别留意如果decode_responsesFalse返回的字典 key 和 value 都是 bytes 类型。很多人在info的结果里取stats然后比较大小直接报错就是因为 bytes 和 int 做不了大小比较。真实项目里我几乎总是建议在创建 Redis 连接时显式设置decode_responsesTrue除非你有极特殊的二进制存储需求否则管理类函数的数据可读性完全是两回事。2. 服务控制核心函数逐个上手2.1 轻量探活ping、echo、dbsizeping应该是这些函数里出场率最高的一个它对应服务端PING命令主要用来确认连接链路是否正常。redis-py 里client.ping()在正常时会返回True如果连接断开会抛出ConnectionError或TimeoutError因此它经常被选为健康检查的心跳命令。不过这里有个小细节很多人在 Redis 的哨兵或集群模式里也只会对某个固定节点做 ping这可能漏掉其他节点的问题。我在做健康检查时通常会让每个业务节点都 ping 一次自己连接的那个节点而不是只依赖外层负载均衡的探测。echo方法是对应ECHO命令的它会把传入的消息原样返回。这个函数说实话在生产里用得不多但有一个妙用你可以在两个客户端连接里发同一个随机字符串如果返回一致就能确认两个连接确实落在同一台 Redis 实例上。这在排查多实例、多数据库环境时会非常方便比看 ip 更直接。dbsize返回当前数据库中 key 的总数。它不像keys那样会阻塞主线程也不会因为 key 太多导致网络响应变大因为它只是从字典表里取一个计数器。我见过不少团队排查“Redis 到底存了多少 key”时还在用keys * | wc -l这显然是反模式。正确的姿势是用dbsize()秒回不阻塞语义上也更准确。但注意dbsize返回的是当前select选中的数据库编号里的 key 数量如果你连的是 Redis 默认的 db0那看到的就是 db0 的统计这符合直觉但不要把它当作整台实例的总 key 数。如果还需要了解 key 的具体结构memory_usage(key)也是一个很实用的函数。它对应MEMORY USAGE命令可以估算某个 key 占用的内存字节数。我不建议频繁调用它因为每个 key 的内存估算都需要遍历对象的编码结构调用太多在高吞吐场景下会有轻微开销但对“哪个大 key 撑爆了内存”这种问题它比盲目debug object key更直观、更安全。2.2 配置与运行参数探查config_get、config_set、config_rewrite服务控制里最有“后台管理”感的一组函数当属config_get和config_set。config_get(pattern)对应服务端CONFIG GET你可以传一个通配符模式比如config_get(maxmemory*)或config_get(save)返回结果是一个把配置名映射到配置值的字典。需要注意就算只匹配到一个配置项返回的也是{maxmemory: 1073741824}这样的 dict而不是字符串这是 redis-py 的固定习惯初用者很容易被搞蒙。config_set(name, value)则对应CONFIG SET可以在不重启 Redis 的情况下动态修改部分配置。比如遇到线上内存不够用先通过config_get(maxmemory-policy)看看淘汰策略再通过config_set(maxmemory-policy, allkeys-lru)临时调整。这里一定要强调动态修改配置虽然方便但并不持久Redis 没有自动把CONFIG SET的结果写回配置文件如果你希望重启后依然生效需要再调用config_rewrite()。config_rewrite对应CONFIG REWRITE会把当前运行配置尽量写回原来的 redis.conf但它并非万能某些由启动参数强约束的配置项可能无法重写所以执行前最好先确认一下当前配置文件是否有写权限。另一个容易踩坑的点是部分配置项是不能通过CONFIG SET修改的比如appendonly在有些版本里必须启动时指定。当你遇到这类限制时redis-py 会直接抛ResponseError消息类似ERR Unsupported CONFIG parameter。遇到这种错误不要硬绕先到官方文档里查这个配置的“可动态修改”标记或者干脆考虑重启维护窗口而不是和 Redis 的配置权限体系硬碰硬。2.3 运维级操作flushdb、flushall、shutdown如果说前面这些函数是“查看”那flushdb和flushall就是真正的“手术刀”。flushdb清空当前库flushall清空所有库它们在 redis-py 里都有对应的flushdb()和flushall()方法。这两个函数我一年可能都调不到一次但每次调用前都必须想清楚三件事第一当前连接选中的是不是目标库第二业务方是否已经切流量清空会不会影响线上第三是否有异步持久化和备份机制。正因为它们在生产环境太危险我强烈建议不要把这两个方法暴露在通用脚本里哪怕只是内部工具也一定要加白名单和二次确认参数。shutdown对应服务端SHUTDOWNredis-py 里有shutdown()方法它会关闭 Redis 服务进程。这个方法我在日常开发中几乎不用因为它一执行就是真真切切地把实例停了。如果只是想平滑重启应该先通过config rewrite保存配置再用系统服务管理工具去重启而不是在代码里直接调shutdown()。某些版本里shutdown还支持save参数但这属于底层命令细节我建议普通项目不要碰避免误操作导致数据丢失。还有一个冷门但有用的函数是select。redis-py 里select(index)可以切换当前连接的数据库编号。要注意在多线程或连接池环境下连接状态是复用的你在这个连接里 select 到 db1归还连接池后再从池里取出的连接可能仍然是 db1。这非常容易造成“换库后读到预期之外数据”的问题。我一般只会在一次性诊断脚本里用select业务代码里宁愿为不同数据库建立独立的 Redis 连接对象也不要去依赖连接内切换上下文。3. 状态监控函数把 INFO 变成能用的现场指标3.1 INFO 信息的分类与关键指标怎么取info是状态监控的绝对主力它对应服务端INFO命令。redis-py 里client.info()不传参数会返回全部信息传section可以只拿某一部分。支持的分段很多常用的有server、clients、memory、persistence、stats、replication、cpu、commandstats、cluster、keyspace。用info(memory)拿到的是一个字典比如{ memory: { used_memory: 104857600, used_memory_human: 100.00M, used_memory_rss: 209715200, used_memory_peak: 157286400, used_memory_lua: 0, maxmemory: 0, maxmemory_policy: noeviction, mem_fragmentation_ratio: 2.0 } }注意 redis-py 对info的返回结构做过处理如果你传了section它返回的字典里通常仍然会保留一个以该 section 名为 key 的外层至少我长期使用的几个版本都是这个行为。为了兼容性我习惯先判断返回类型info client.info(memory) mem_info info.get(memory, info)这样即使在某个版本里返回结构不同也不会因为多套一层或少套一层直接崩溃。真正做内存监控时我通常会盯几个指标used_memory表示逻辑分配的内存used_memory_rss表示进程实际占用的物理内存mem_fragmentation_ratio表示 RSS 与逻辑内存的比值。如果碎片率长期大于 1.5说明内部碎片严重可以考虑重启或迁移如果小于 1可能是发生了 swap这个往往比高碎片率更危险。info(clients)里藏着另一组很有价值的数据比如connected_clients、blocked_clients、client_recent_max_input_buffer等。我排查连接数爆涨问题时第一件事就是看connected_clients是否需要拆分为多个实例接着看client_recent_max_input_buffer有没有超大请求把内存打上去。用 redis-py 把这些指标采集到监控系统后我通常会再写一个简单的报警阈值比如连接数超过预期值的 80% 就开始告警而不是等到 Redis 彻底拒绝连接。3.2 客户端台账client_list 和 client_killclient_list对应服务端CLIENT LIST它返回一个列表列表里每个元素是一个 dict字段包括id、addr、name、db、cmd、age、idle等。我第一次用client_list()时最惊喜的点是它不需要你去解析文本输出直接拿到了结构化数据可以直接做统计。一个实战场景是线上发现 Redis 连接数持续上涨用client_list可以把所有客户端地址提取出来按addr的 ip 部分做聚合看看到底是哪个服务的连接没有释放干净。代码大概长这样def top_client_ips(client, top_n10): infos client.client_list() counters {} for item in infos: # item[addr] 形如 192.168.1.100:52344取 ip 部分 ip item.get(addr, ).rsplit(:, 1)[0] counters[ip] counters.get(ip, 0) 1 return sorted(counters.items(), keylambda x: x[1], reverseTrue)[:top_n]如果发现某个 ip 是异常连接client_kill可以直接把它踢掉。redis-py 里client_kill有两种用法按地址杀和按过滤条件杀。按地址杀需要传addr按过滤条件杀可以结合id、type、skipme等参数。不过要注意杀掉连接只是临时止血如果对方是一个坏掉的服务且连接池没做超时回收断开后它很可能马上重连所以根因排查才是重点。client_getname和client_setname这对函数也值得提一句。通过client_setname给连接起一个有意义的名字比如order-service:worker-01之后再在client_list里看到的就是一个能一眼认出的名字而不是一堆冷冰冰的 ip。这个习惯对大型分布式系统特别重要我所在的团队在初始化连接池时会统一设置连接名排查问题时效率提升非常明显。3.3 慢查询监控slowlog_get 与 slowlog_lenRedis 的慢查询日志是一个环形缓冲区只记录执行时间超过slowlog-log-slower-than阈值单位微秒的命令。redis-py 里对应的方法是slowlog_get(num)、slowlog_len()和slowlog_reset()。slowlog_get(n)返回最近 n 条慢查询默认是 10 条每条记录是一个列表或字典包含日志标识符、时间戳、执行耗时、命令参数数组、客户端地址和客户端名称。我用slowlog_get的习惯是写一个定时采样脚本每 1 分钟拉取一次最近 10 条慢查询把耗时和命令内容结构化后存入另一个存储系统再配合一个“执行超过 100ms 的命令”的报警规则。这样比只靠命令行人工看要可靠得多。还有一个细节slowlog_get返回的命令参数是一个 list比如[BGSAVE]或[GET, user:10001]。如果你之前设置了decode_responsesTrue这里拿到的就是字符串直接就能拼日志如果没有就要记得把 bytes 转成字符串再输出否则日志文件里会是一堆b...。慢查询日志的缓冲区默认只保留最后 128 条slowlog-max-len这个配置可以调大。我通常建议生产环境设置到 1024 或 2048因为慢查询日志本身不占太多内存但保留更长的历史能显著提升问题回溯能力。如果发现慢查询堆积很快那说明 Redis 确实存在性能瓶颈而不是日志缓冲区不够的问题这种时候优先找慢命令本身而不是盲目调大缓冲区。另外slowlog_reset()对应SLOWLOG RESET会清空当前实例的所有慢查询记录。它最好只在明确知道“接下来我们要做一次压力测试”的时候用比如你想只统计压测期间的慢查询先在压测前 reset 一次压测结束后再slowlog_get拿到的数据就非常干净。4. 实战整理一个可直接复用的巡检脚本4.1 巡检脚本的模块划分理论知识讲完我给你看一套我自己一直在用的巡检脚本结构。它的目标不是替代 Prometheus 这类专业监控系统而是当你只有一台跳板机、一个 Python 环境时可以快速拿到 Redis 实例的“体检报告”。脚本整体分为四步连接检查、内存快照、客户端台账、慢查询采样。我习惯把它放在一个普通 Python 文件里直接运行依赖只需要redis和datetime这样在大多数 Linux 服务器上都能直接跑不需要额外装很多东西。import datetime import redis r redis.Redis( host127.0.0.1, port6379, db0, passwordNone, socket_connect_timeout3, socket_timeout5, decode_responsesTrue, ) def check_connection(): try: pong r.ping() print(f[{datetime.datetime.now()}] ping - {pong}) return True except redis.RedisError as e: print(f[{datetime.datetime.now()}] ping failed - {e}) return False def snapshot_memory(): mem r.info(memory).get(memory, {}) keys [ used_memory, used_memory_rss, used_memory_peak, mem_fragmentation_ratio, maxmemory, maxmemory_policy, ] for k in keys: print(f{k:28}: {mem.get(k)}) def snapshot_clients(): infos r.client_list() total len(infos) print(fconnected_clients(total in list): {total}) printed 0 for item in infos[:10]: print( f addr{item.get(addr)} name{item.get(name)} fdb{item.get(db)} cmd{item.get(cmd)} fage{item.get(age)} idle{item.get(idle)} ) printed 1 if printed total: print(f ... and {total - printed} more) def snapshot_slowlog(): slow_len r.slowlog_len() print(fslowlog total: {slow_len}) for item in r.slowlog_get(10): # item 可能是 dict也可能是 list取决于版本 if isinstance(item, dict): ts item.get(start_time) duration item.get(duration) cmd_args item.get(args) addr item.get(client_addr) else: # 较老版本返回 list元素顺序为 id, ts, duration, args 等 ts item[1] duration item[2] cmd_args item[3] addr item[4] if len(item) 4 else - print( f ts{ts} duration_ms{duration / 1000:.2f} faddr{addr} cmd{ .join(cmd_args)} ) if __name__ __main__: if check_connection(): snapshot_memory() snapshot_clients() snapshot_slowlog()这套脚本非常简单但已经能覆盖“Redis 是否还活着”“内存有没有告警风险”“连接数有没有上涨”“有没有异常慢查询”这四个高频问题。我一般把它写到服务器的一个定时任务里每 5 分钟跑一次输出重定向到日志文件。真要出问题时第一件事就是拉最近几条巡检日志能省很多翻监控面板的时间。4.2 参数选择与采集频率的取舍用 redis-py 做巡检时最容易忽视的是采集频率和命令执行本身的开销。虽然INFO、CLIENT LIST这类管理命令通常都很快但在高并发实例上如果每秒钟轮询一次也可能在请求调度上产生不必要的争用。我个人的经验是内存和连接数这样变化较慢的指标每 10 秒到 1 分钟采集一次足够了SLOWLOG这种本身就记录异常的日志数据每 1 分钟拉一次完全够用没必要更频繁。哨兵和集群模式下巡检脚本还必须注意“采集的是哪个节点”。如果你是在主从架构里运行info(replication)里会明确标出role是master还是slave。假设你从从节点采集到的connected_clients很小、used_memory却和主节点接近它依然是合理现象因为从节点需要复制全量数据。不要拿从节点的 RSS 去和主节点的used_memory比这是单位不统一比不出任何意义。另外使用config_get(*)把全部配置拉出来再做过滤在某些配置项特别多的版本里会带来不必要的内存分配。我建议尽量用精确的模式比如config_get(maxmemory*)既能覆盖maxmemory和maxmemory-policy又不会把一整个配置表都拉到客户端。这个道理和 SQL 查询里不要SELECT *是一样的控制返回体量在任何系统里都是一种美德。5. 常见坑与排查技巧实录5.1 decode_responses 不一致导致的类型错乱这是我在帮助排查同事代码时遇到最多的一类问题。假设你的业务连接创建时没有设置decode_responsesTrue那么你拿到info后所有的字符串字段其实都是 bytes。比如mem_info[used_memory_human]是b100.00M你直接把它拼到日志里会变成b100.00M非常难看。更隐蔽的坑是在做字典键值对比时bytes 类型的 key 和字符串类型的 key 永远不会相等导致你以为某个配置缺失实际只是类型对不上。解决方案很直接在创建连接时统一加decode_responsesTrue。但如果某个历史项目已经有几百处代码临时改连接参数担心影响二进制数据那么可以在巡检脚本里单独构造一个只用于管理的连接对象。管理连接不参与业务读写开了decode_responsesTrue基本不影响现有业务是最稳妥的收尾方式。5.2 只读实例和权限限制下的兜底错误如果 Redis 开启了--replica-read-only或通过 ACL 限制了用户权限很多辅助函数会直接抛出ResponseError。比如只读从节点本来就不允许config_set、flushdb、flushall你在脚本里调用这些函数时必须有异常兜底。我通常会给巡检脚本加一个错误收集器把单步失败的任务标记为failed而不是让整个脚本因为一个ResponseError终止这样即使某个实例不支持某个命令其他监控数据仍然能采集到。ACL 用户权限出现后还要注意设置连接时用的用户名。redis-py 里创建连接时可以传入username参数默认其实是默认用户default。如果服务端给这个用户只开放了部分命令的权限那么config_get也可能直接失败。排查是否需要重新授权别动不动就去改 Redis 的启动参数。5.3 不要在业务线程里调用阻塞型管理函数这里必须点名monitor命令。redis-py 里虽然有monitor()方法它可以流式输出 Redis 的每一条执行命令但这是强烈的阻塞型操作一旦开启它会持续从连接上接收数据流和普通命令完全不是一种模式。很多人在排查线上问题时兴奋地往业务代码里塞一个monitor()结果把整个连接池的连接都占住了。我的建议是monitor只适合在单独的诊断进程中使用并且要设置socket_timeout以防忘记退出时连接悬挂在那里。5.4 批量删 key 时用 scan别用 keyskeys不是辅助函数但在做状态监控时很多人会顺手用它来统计 key 的分布情况。问题在于keys会阻塞 Redis 主线程在 key 数量大的实例上极容易造成大量命令超时。更稳妥的做法是用scan_iter。redis-py 里scan_iter是生成器它一次只返回一批 key不会长时间阻塞。我在写“大 key 巡检”脚本时通常会结合scan_iter和memory_usagedef find_large_keys(client, threshold10 * 1024 * 1024, max_count20): large [] count 0 for key in client.scan_iter(count500): try: mem client.memory_usage(key) except redis.ResponseError: continue if mem and mem threshold: large.append((key, mem)) count 1 if count max_count: break return large这个脚本在实际生产里有很高的实用性。它不会卡死 Redis还能快速把占用超过 10MB 的大 key 列出来。不过要注意的是即使使用了scan_iter每个 key 都调用memory_usage带来的网络开销仍然不可小视所以建议放在业务低峰期执行并且适当调大count参数减少 RTT 次数。6. 辅助函数的扩展价值与我的个人体会6.1 把这些函数接到监控系统里redis-py 的辅助函数不只适合手工巡检也可以用来搭建轻量监控。比如你不想引入额外的 redis_exporter又想在一个自定义监控面板里看到 Redis 的核心指标完全可以用infoclient_listslowlog_get这三组数据每隔一段时间推送到时序数据库。我实际做过的一个小项目里用config_get动态读取maxmemory再结合当前used_memory算出使用率直接在内部面板里画成一条曲线效果比很多商业监控还直观。如果你在用 Docker 环境只需要注意连接参数里不能写localhost而要写容器的服务名或宿主机网关地址。这个和 Redis 本身无关却是我在“docker 搭建主从 redis-py 做状态检查”时踩过最多的坑。连接串改成正确地址后同一套巡检脚本在主从数据库上都能跑非常顺手。6.2 少用全量导出多用增量视图还有一些和监控相关的辅助函数比如object(encoding, key)可以查看 key 的底层编码类型object(idletime, key)可以看 key 多久没被访问了。它们对分析热点数据和冷数据分布很有帮助但千万别在循环里对全量 key 做object检查那样性能代价极高。更合理的做法是先用scan_iter抽样一部分 key对抽样集合做object检查推断总体的编码分布。我自己的习惯是每隔一段时间在业务低峰期跑一次抽样分析看有多少 key 还是embstr编码的短字符串、有多少已经变成raw或hashtable。如果发现某个类型的占比异常升高往往意味着业务写入模式出了问题比如短字符串被反复修改导致编码膨胀或者 hash 的hash-max-ziplist-entries配置被调得过大。这些数据结构层面的细节普通监控根本不会告诉你但用 redis-py 的辅助函数却能快速洞察到。6.3 把辅助函数当成“可编程运维”的起点最后聊一点个人体会。很多团队对 Redis 的运维操作还停留在“用 redis-cli 手动敲命令”的阶段。不是说 redis-cli 不好而是你一旦有了自动化巡检、自动告警、自动恢复的需求命令行就会变得非常不顺手。redis-py 的这批辅助函数价值恰恰在于把 Redis 的管理能力变成了一个 Python 世界里普普通通的函数库你可以随意组合、包装成脚本、对外开放成内部工具、甚至集成进 CI/CD 流水线。比如我会把ping和client_list组合成一个简单的“连接池健康检查器”在服务发布前执行一次避免把流量发到一个已经连接数爆满的 Redis 实例上。我也会把config_get和config_set封装成一个内部小工具支持在灰度环境里临时调整淘汰策略而不用登到每台服务器上敲命令。这套思路是不分行业、不分项目的只要你正在用 redis-py那它就是你做可编程运维最顺手的抓手。我在实际使用中还有个很深的感受这些辅助函数真正值得学习的地方不是它们本身有多复杂而是它们把 Redis 的“黑盒”部分打开了。只要掌握了它们的用法Redis 变成了一台可以随时对话、随时体检、随时调优的服务而不只是一个只能读写的缓存桶。如果你也想深入玩转 Redis 运维建议先从这篇里的ping、info、client_list、slowlog_get这四个函数开始写一个最简单巡检脚本跑上一周你就能明显感受到“看得见”比“猜得到”靠谱太多。
返回列表