尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Redis命令体系详解:服务端与客户端实战与排查指南

Redis命令体系详解:服务端与客户端实战与排查指南 这篇是Redis实战系列的第二篇上一回我讲了Redis的部署、配置和持久化这一回集中聊命令——服务端命令和客户端命令。为什么要单独把命令拎出来说因为我发现很多朋友用Redis的思路还停留在“装好、连上、点一点客户端工具”这一步遇到问题只能搜一下搜完也不敢确定该不该用。实际上Redis的命令体系非常规整服务端命令负责管Redis这个进程本身客户端命令负责管Redis里的数据两者分工明确。把这条线拆开理解你排查连接异常、内存暴涨、慢查询、误删数据的思路都会顺很多。这篇适合谁后端开发、测试工程师、运维以及准备Redis面试的求职者。篇幅有点长但我尽量用实际踩过的坑来讲每条命令而不是照着官方文档念一遍。老规矩先讲服务端再讲客户端最后把命令背后的逻辑和典型的排查经验打包奉上。1. 服务端命令先把Redis“盘”清楚1.1 从启动到状态检查redis-server 与 INFO 的正确用法不管你怎么装RedismacOS上用brew install redisLinux上用apt、yum或者Docker拉镜像也不管你是源码编译还是下载预编译包最终跑起来的进程始终是redis-server。这一步看起来简单但有几个细节会坑到新手。很多教程会让你直接敲redis-server然后终端就被占住了感觉像卡死。这不是bug是因为默认配置里daemonize是no进程在前台运行日志直接往终端打。你CtrlC一按Redis就停了。生产环境一定要在配置文件里把daemonize yes打开或者用systemd之类的进程托管。临时调试倒是无所谓前台跑反而更容易看到报错。如果只是想临时起一个实例做测试可以不写配置文件直接命令行传参redis-server --port 6380 --maxmemory 128mb这样起一个端口6380、内存上限128MB的Redis很适合做实验。连上之后第一件事一定是ping。用redis-cli连进去输入ping通了会返回PONG。这是最小粒度的健康检查。真正要看状态用INFO。INFO输出的内容是按段落排的很多人被那一大屏英文吓住其实只需要挑重点段看。我日常最常看的是这几个段落段落关键字段看什么memoryused_memory_human、mem_fragmentation_ratio内存用了多少、碎片率是否偏高clientsconnected_clients、blocked_clients连接数是否异常、有没有阻塞连接statstotal_commands_processed、expired_keys命令吞吐、过期key数量replicationrole、master_link_status、connected_slaves主从角色、链路是否正常persistencerdb_last_save_time、rdb_changes_since_last_save最近一次快照时间、尚未落盘的变更量INFO命令还支持只查某一段比如INFO memory、INFO clients不用每次刷全量。我遇到过Redis变慢的情况redis-cli还能进INFO里mem_fragmentation_ratio到了3.5明显是内存碎片过高后来调了maxmemory和内存分配策略才缓解。这种问题不看INFO是定位不到的。关机也有讲究。直接kill -9杀Redis进程很可能丢失最近一段时间写入的数据。正确的做法是用SHUTDOWN命令它会先触发持久化保存再退出。如果数据无所谓也可以用SHUTDOWN NOSAVE跳过保存直接关闭适合测试环境。我见过有人维护生产Redis用kill -9的结果RDB快照还没落盘启动后丢了几万条数据这种事故能避免就避免。1.2 运行时改配置也要讲边界CONFIG 家族的实操心得Redis不是所有配置都必须改完重启才能生效。运行时查看和修改配置用CONFIG GET和CONFIG SET。举个真实场景线上有个临时活动要跑数据量突然涨了内存压力很大。我不想重启Redis直接执行CONFIG SET maxmemory 1gb内存上限立刻生效不用等服务窗口。这个操作在排查问题时极其好用。但要注意边界。CONFIG SET能改的参数是有限制的不是所有东西都能热改。比如port、bind这类网络层参数修改之后必须重启进程才生效。还有requirepass虽然能动态改但改了之后当前连接不会断开新的连接要立刻用新密码如果脚本里还写着旧密码就会全部连接失败。CONFIG GET看当前配置也很常用。比如怀疑有人改了逐出策略CONFIG GET maxmemory-policy一下就能确认。我记得有一次生产Redis“假死”ping得通但写不进去查了半天才发现maxmemory被设成了1mb所有写操作都在触发键淘汰。不看配置根本想不到是这个原因。还有一个新手容易忽略的单位问题。CONFIG SET maxmemory 512mb这个写法没问题但写成512m或者512M有些版本会直接报错有些版本会认为是字节数。单位只认b、kb、mb、gb这种完整写法条件允许的话改完立刻CONFIG GET确认一下别看都不看就关终端。1.3 连接管理CLIENT 命令帮你把连接看得明明白白Redis作为服务端客户端连接它就是TCP连接。连接多了、异常了就得靠CLIENT家族来管。CLIENT LIST能列出当前所有连接每行一个客户端关键字段包括id、addr客户端地址、laddr服务端地址、name、age、idle、flags、db、cmd。其中name字段平时很多人忽略其实很有用。代码里连接Redis之后可以执行CLIENT SETNAME order-service连接就有了名字CLIENT LIST时一眼就能看出是哪个业务。我记得有一次同事在测试环境写了个循环每轮都新建连接访问Redis结果连接数冲到了几万INFO clients里connected_clients高得吓人。用CLIENT LIST查出来一堆来自同一IP的短连接再用CLIENT KILL把这些连接清掉系统才恢复。CLIENT KILL可以指定客户端地址比如CLIENT KILL ADDR 192.168.1.10:60001也可以按连接ID杀。正常业务里连接数异常增加往往意味着代码没走连接池或者是连接泄漏卡掉几个连接只是临时止血找到源头才是关键。还有几个CLIENT子命令也值得知道CLIENT GETNAME看当前连接名字CLIENT PAUSE可以暂停所有客户端命令若干毫秒这在做故障切换时偶尔能用上。这些都是服务端维度管理连接的手段属于“管进程”的范畴。1.4 实时监控怎么用才安全MONITOR 与 SLOWLOG 的配合MONITOR命令能做到实时打印Redis收到每一条命令看起来非常直观。但我必须泼一盆冷水它把命令流持续推给客户端非常消耗CPU和内存生产环境开着MONITOR看几个小时QPS能掉一半。这跟mysql的general log一样开着就是自残。正确用法是临时定位问题时开个几秒看一眼现场就CtrlC关掉。比如线上突然出现大量某个前缀的key你怀疑是哪个业务在写用MONITOR盯几秒钟就能看到命令来源然后用CLIENT LIST反查连接IP。比MONITOR更“安全”的是SLOWLOG。Redis会把执行时间超过阈值的命令记录到慢查询日志阈值由slowlog-log-slower-than控制单位是微秒默认10000微秒也就是10毫秒。查看命令SLOWLOG GET 10 查看最近10条慢查询SLOWLOG LEN 查看慢查询总数SLOWLOG RESET 清空慢查询日志慢查询日志里能看到到底是KEYS还是SORT还是别的命令慢了每条记录还带时间戳和执行时长做故障复盘非常有用。排查“Redis每隔一段时间卡一下”这种问题SLOWLOG是首选工具因为它不侵入线上比MONITOR温和得多。2. 客户端命令数据操作基本功要这样记2.1 字符串与键先学会这几条再谈别的服务端命令管的是Redis进程客户端命令管的是Redis里的数据。这两个概念经常有人混淆以为redis-cli就是服务端。其实redis-cli只是官方自带的命令行客户端底层和你代码里的Redis客户端没区别服务端永远是redis-server这一个进程。字符串类型是最基础的常用命令就几条SET、GET、DEL、EXPIRE、TTL。这几个看着简单组合起来的坑可不少。先记住SET的扩展参数这是Redis 2.6.12开始支持的非常重要SET key value EX seconds设置键并指定秒级过期时间SET key value PX milliseconds指定毫秒级过期时间SET key value NX键不存在时才设置SET key value XX键存在时才设置为什么说重要因为以前写分布式锁标准姿势是SETNX key value拿到锁之后再EXPIRE设置过期时间。但这是两条命令不是原子的如果SETNX成功之后、EXPIRE执行之前程序崩了锁就永远不释放所有线程都会卡死。后来官方推荐一句话SET lock_key uuid NX EX 30。NX保证不存在才设置EX保证30秒自动过期一个命令完成两步。这个知识点后来成了Redis面试题关于分布式锁的高频考点我在第四节还会详细说释放锁的坑。TTL用来查剩余过期时间返回-1表示永不过期返回-2表示键不存在。写运维脚本时靠这两条判断逻辑非常方便。有一点要提醒很多新手以为DEL会返回是否删除成功实际上DEL删除不存在的键返回0不报错。批量删除多个键可以一次DEL key1 key2 key3原子生效。2.2 哈希与列表对象缓存与轻量队列的实战场景哈希类型适合存“对象”。命令主要有HSET、HGET、HGETALL、HDEL、HINCRBY。实际业务里用户信息、商品详情、配置项这类结构化的数据我很少直接SET一个大JSON字符串原因很简单哈希可以只更新某一个字段比如只改用户的levelHSET user:1001 level 12就行不用把整个JSON读出来再写回去省带宽也省内存。另一个原因是HINCRBY做计数器非常顺手比如用户积分、文章阅读量HINCRBY user:1001 points 5天然原子性不会丢更新。HGETALL会把所有字段和值一次性取出字段多的时候数据量就大要小心。如果需要批量取多个字段用HMGET只取需要的字段比HGETALL省流量。列表类型更适合做“队列”或“时间线”。常用命令LPUSH、RPUSH、LPOP、RPOP、LRANGE、LLEN。Redis 2.x时代很多人直接用List做轻量消息队列生产者RPUSH消息进去消费者LPOP取出来处理。注意LPOP是非阻塞的列表为空时立刻返回空结果。如果业务里写成while(true)LPOPCPU会空转烧掉。正确姿势是用阻塞版BRPOP、BLPOP没有数据时客户端会等待直到超时或新数据到来。Redis 2.x虽然老但BRPOP这个能力非常实用现在很多轻量任务队列依然这么干。LRANGE key 0 -1能取出列表全部元素这个命令也要注意列表特别长的时候会一次性拉取大量数据在客户端造成内存压力。日常取最近几条用LRANGE key 0 9就够了别手滑写0 -1。2.3 集合与有序集合去重、标签和排行榜集合类型Set用在一组不重复的元素上。常用命令SADD、SREM、SMEMBERS、SISMEMBER、SCARD。典型的场景是“每日活跃用户”。用SADD user:20250427 uid1 uid2收集当天访问的用户SCARD直接返回总数。再比如给用户打标签SISMEMBER user:1001 tag:vip判断他是不是VIP这个命令复杂度O(1)性能非常好适合放在访问链路里频繁调用。SMEMBERS会返回全部成员这点要小心。集合特别大的时候SMEMBERS和KEYS一样会一次性拉全量数据阻塞网络传输。如果只是想“抽样看看里面有什么”可以用SRANDMEMBER或者SCAN游标分批扫。Redis 2.x就有SSCAN命令专门扫集合成员和SCAN同理。有序集合ZSet是Redis命令里含金量最高的一类。常用命令ZADD、ZRANGE、ZREVRANGE、ZRANK、ZSCORE、ZINCRBY以及ZCARD。排行榜是ZSet最经典的应用。分数就是排行依据ZADD leaderboard 1000 user1把user1的分数设成1000同步更新排行。某个用户得了赞用ZINCRBY leaderboard 1 user1给他加1分不用先读出来再写回去。要取前十名ZREVRANGE leaderboard 0 9 WITHSCORES直接拿到按分数从高到低排序的结果。ZRANK查某个用户排第几ZSCORE查当前分数。ZSet底层是跳跃表加哈希表增删改查都是O(logN)级别比直接对List排序再取前几条要靠谱得多。面试时聊到排行榜能把ZSet底层结构说明白是很大的加分项。2.4 键扫描与对象体检KEYS、SCAN 与 TYPE 的取舍新手最常犯的错误是执行KEYS pattern。KEYS会全表扫描所有键匹配过滤。几百万key的时候一次KEYS *就能把Redis拖到“请求排队”所有客户端全部变慢。这个教训我记了十年生产环境禁止KEYS。替代方案是SCAN这是Redis 2.8开始提供的游标式扫描命令。用法是SCAN 0从0开始返回一批键和一个新的游标SCAN 新游标继续扫描直到返回游标为0表示扫完SCAN每次返回的数量有限不会一次堵死实例。但要注意SCAN迭代过程中返回的键有可能会重复它不保证不重复所以你不应该拿SCAN的结果做去重统计适合的是分批扫描、批量处理。比如按前缀清理旧数据SCAN配合DEL就是标准的清理姿势。还有一个命令容易被忽略TYPE。想知道某个key是什么类型TYPE key会返回string、hash、list、set、zset。再进阶一点OBJECT ENCODING能看内部编码比如string可能是int、embstr或rawhash在2.x时代可能是ziplist或hashtable。这些信息看起来鸡肋但遇到“为什么这个key占的内存特别少/特别多”之类问题时就派上用场了。3. 命令背后的设计逻辑值得细品3.1 单线程模型为什么一条命令就能拖垮整个实例Redis 2.x到现在的核心执行模型没什么变化一个主线程处理所有客户端请求命令一条条排队执行。这就意味着每条命令都必须快一旦某条命令执行时间过长后面所有请求都被堵住。我习惯用一个类比来理解Redis是一个单行道收费站所有车排成一队。只要有一辆车停下来打开后备箱仔细检查后面全部堵死。KEYS就是那个“停下来检查”的命令。所以在Redis里命令的算法复杂度不是理论问题而是直接影响线上可用性的问题。O(1)命令比如GET、SET、SISMEMBER、HGET可以放心用。O(N)命令就要掂量N有多大比如KEYS、SMEMBERS、LRANGE 0 -1、SORT数据量大时必须换思路要么改SCAN分批要么限定返回范围要么换数据结构。面试官问“为什么Redis不推荐使用KEYS”背后考的就是这个单线程模型加复杂度意识。3.2 从命令到业务一次登录缓存的完整调用链光记命令没用还得知道命令怎么组合。我拿一个标准场景走一遍用户登录后要缓存用户信息。假设用户ID是1001登录请求来了先查Redis缓存HGETALL user:1001如果返回空说明缓存没命中去MySQL查用户表拿到name、age、level等字段查回来之后回填到RedisHSET user:1001 name 张三 age 29 level 12设置过期时间EXPIRE user:1001 3600把HSET和EXPIRE分开执行是两条命令中间有极小的概率进程崩溃导致“数据写进去了但不带过期时间”。工程上更稳妥的做法是用Lua脚本把这两步包在一个原子操作里。Redis 2.6开始支持EVAL执行Lua脚本代码如下redis.call(HMSET, KEYS[1], name, ARGV[1], age, ARGV[2], level, ARGV[3]) redis.call(EXPIRE, KEYS[1], ARGV[4])用EVAL 上面脚本 1 user:1001 张三 29 12 3600执行。Lua脚本在Redis里是原子执行的中间不会插入其他命令这比两条命令分开发安全得多。“缓存雪崩”和“缓存击穿”这类问题也跟命令的过期时间设计有关。比如几千个key同时设置了同一时刻过期那一瞬间大量请求同时穿透到数据库就是雪崩。实操里我会在过期时间上加上随机偏移比如3600加一个0到300的随机秒数避免大量key同时失效。3.3 版本演进Redis 2.x 的命令为什么至今不过时Redis 2.x年代技术圈还很朴素。2.6版本带来了Lua脚本也带来了SET命令的NX和EX选项把分布式锁的“原子性”补上了。2.8版本引入了SCAN游标式扫描解决了KEYS阻塞问题还带来了PSYNC部分同步主从复制效率大幅提升。3.0才有集群7.x才有ACL和多线程。但你回头看会发现核心数据结构命令的变化很小。今天你在Redis 7上用的HSET、ZADD、RPUSH跟2.x时代几乎没有差别。变化的是外层能力不是数据操作命令本身。所以把2.x时代的命令吃透遇到更高版本根本不用重学顶多是新版本加了几个新命令和数据类型。面试的时候尤其如此。与其背一堆新功能名字不如把SET NX、SCAN、SLOWLOG、主从复制这几个“老知识点”讲清楚。面试官问“Redis怎么实现分布式锁”你如果能从2.6.12的SET扩展参数讲到Lua脚本释放锁再讲为什么Redis单线程模型适合做锁基本功就立住了。4. 常见问题排查与实用技巧实录4.1 连接失败和密码问题怎么快速定位Unix哲学连不上Redis第一反应不要卸载重装按顺序问自己三个问题。第一个redis-server这个进程还活着吗ps -ef | grep redis-server看进程在不在。如果不在看日志确认是崩了还是被系统杀了。第二个端口真的在监听吗ss -lntp | grep 6379。如果没看到监听检查配置文件里的bind和port。bind只写了127.0.0.1那外部IP肯定连不上这是最常见的坑。第三个密码对不对有密码的环境先redis-cli进去再执行AUTH 密码看返回OK还是错误。用redis-cli -a 密码这种方式会在shell历史里留下明文密码测试环境无所谓生产环境不建议这么干容易泄露。还有一个安全习惯要养成。Redis 2.x时代默认没有密码管理端口一旦暴露到外网分分钟会被扫描工具写入恶意key。巡检的时候重点检查config get requirepass如果为空且bind不是127.0.0.1要立刻处理。4.2 内存暴涨、Redis变慢怎么自查我遇到“Redis变慢”的次数不算少排查路径基本固定。先看内存INFO memory。看两个数used_memory_human是实际占用mem_fragmentation_ratio是碎片率。碎片率超过1.5要警惕超过2.0基本是内存碎片严重或者内存分配器有问题。碎片率高最直接的影响是内存被白占可能触发maxmemory淘汰。再看配置CONFIG GET maxmemory和CONFIG GET maxmemory-policy。如果maxmemory设得过小Redis会频繁触发逐出所有写请求都在淘汰键表现为“写入很慢”。如果maxmemory-policy是noeviction写请求直接报错业务侧会看到OOM错误。然后找大key。如果你的redis-cli版本支持--bigkeys执行redis-cli --bigkeys会扫描全实例并统计最大的key这是定位内存问题最直观的工具。它会按str、hash、list、set、zset分类列出最大的key和大小。如果没有--bigkeys也可以用SCAN配合TYPE、STRLEN等命令手工扫。最后查慢查询SLOWLOG GET 10。如果看到大量KEYS、SMEMBERS之类的命令就说明有业务代码在滥用全量操作要马上改。MONITOR这个命令我前面说过能不碰就不碰尤其是排查慢问题的时候开MONITOR会让Redis更慢。4.3 误删数据后能做什么以及怎么预防“删库跑路”只是段子真实情况更常见的是手滑DEL写错了key或者FLUSHALL/FLUSHDB清空了整个库。Redis 2.x没有回收站误删之后能不能救取决于你的持久化配置。如果开了RDB快照找到dump.rdb文件用CONFIG GET dir确认保存目录从备份文件恢复。如果没开持久化那内存里的数据丢了就是丢了。这个教训让我把几条规则固化下来生产环境禁止FLUSHALL、FLUSHDB危险命令可以在配置文件里用rename-command改名比如rename-command FLUSHALL 直接把命令禁用扫描键一律用SCAN代替KEYS。更稳妥的做法是定时备份。比如每天凌晨用BGSAVE手动触发一次快照再把dump.rdb复制到异地。BGSAVE是异步的不会阻塞主线程但会占用磁盘IO和内存注意别在业务高峰期执行。维护时如果想让最新数据尽快落盘可以用SHUTDOWN它会主动做一次持久化再退出适合低峰期维护。4.4 面试与实战高频命令场景分布式锁、管道、日常运维分布式锁的加锁命令前面聊过了SET lock_key uuid NX EX 30。但释放锁才是真正的坑。很多人释放锁直接DEL这有问题。如果锁到了过期时间自动释放其他线程拿到了锁此时第一个线程的DEL就把别人的锁删了。正确做法是先判断value是不是自己写入的UUID再删除。这两步要保证原子性就得用Lua脚本if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end用EVAL执行这个脚本Redis会保证整个脚本原子运行中间不会插入别的命令锁才不会误删。批量写入大量数据用redis-cli --pipe。它会批量发送命令不用等每条命令的返回吞吐量能比逐条SET高出几个量级。临时灌测试数据时非常好用。准备好RESP协议格式的文本文件一行一条命令然后用管道喂进去几十万条数据几秒钟就能搞定。日常运维我还会配几个顺手的东西。给redis-cli起个别名比如alias rcliredis-cli -h 127.0.0.1 -p 6379 alias rfreeredis-cli -h 127.0.0.1 -p 6379 info memory | grep used_memory需要清理指定前缀的key用SCAN配合DELredis-cli --scan --pattern order:* | xargs -n 100 redis-cli DEL这条命令会分批删除order前缀的键每次100个不会一次性阻塞Redis。注意如果Redis有密码xargs调用的redis-cli也要带上密码参数。多敲几次就会形成肌肉记忆比打开可视化界面点半天高效得多。最后分享一个我个人的习惯不管用不用可视化客户端心里一定要清楚那些RDM之类的工具只是把客户端命令包了一层你点按钮和敲命令没有本质区别。真正支撑你排查问题的还是服务端命令管进程、客户端命令管数据这条主线。把这条线理清了Redis的绝大多数问题都能找到下手的地方。
返回列表