我见过太多人学Redis第一件事是收藏一份Redis基础常用命令清单然后照着背。背了一个月set和get倒是记得很牢但你问他列表到底该用LPUSH还是RPUSH线上环境为什么不能用KEYS *他多半就答不上来了。这暴露了一个真相Redis基础常用命令从来不是背出来的而是用出来的——每条命令背后都对应着一个业务场景和一套取舍逻辑。这篇文章我不会给你抄一份几百条的命令大全而是按我实际干活时对Redis的理解把最常用的命令按场景拆开讲。每个命令我都尽量说清楚三件事它解决什么问题、为什么这么设计、实际用的时候有哪些坑。适合刚接触Redis的读者建立框架也适合准备面试或者已经在项目里用Redis但总觉得命令没学透的兄弟查漏补缺。另外很多人喜欢先装一个可视化客户端比如Redis Desktop Manager我的建议是可视化工具只配当辅助命令行才是你真正的主战场因为线上排查问题的时候你多半只有命令行可用。1. 先搞懂命令的分类逻辑别再对着命令手册死记硬背1.1 Redis命令其实只有五大类Redis官方文档里命令是按字母排序的看起来又多又杂新手很容易被吓到。但只要你按用途重新划分命令清单瞬间就清晰了。在我看来日常工作中真正高频的Redis命令无非五类连接管理类ping、auth、select、quit负责建立会话、选库、身份校验。数据操作类围绕五大数据类型String、Hash、List、Set、ZSet的增删改查这部分是Redis的绝对核心占了日常操作的八成以上。键空间管理类del、exists、expire、type、keys、scan处理的是key本身而不是key里的数据。事务与脚本类multi、exec、watch、eval解决多条命令需要一起执行的问题。服务器运维类info、slowlog、dbsize、flushdb日常体检和故障排查靠它们。你发现没有这个分类和Redis的内存模型、单线程执行模型是强相关的。比如为什么会有expire这类键空间命令因为Redis是内存数据库内存不释放就会爆所以键的过期管理是头等大事。为什么会有slowlog因为Redis是单线程执行命令一条慢命令会把所有请求堵住所以必须有慢查询日志机制。1.2 从安装完成后的第一个动作看命令的存在感很多教程讲Redis上来就是下载、解压、make然后直接开写数据操作命令。实际上你装完Redis第一个要面对的问题是怎么进去、怎么确认它活着。# 本机默认端口连接 redis-cli # 指定IP和端口连接 redis-cli -h 127.0.0.1 -p 6379 # 进入之后先测活 127.0.0.1:6379 ping PONG看到PONG心里就有底了。别小看这一步我见过不少新人用可视化工具连半天连不上最后发现服务根本没启动。命令行下先ping再操作这是肌肉记忆。2. 连接、信息查看与日常体检从启动那一刻就该会的命令2.1 进对门select、auth、dbsize连接进来之后第一件事是搞清楚你身在哪个库。Redis默认有16个库配置里的databases参数控制默认在db0。127.0.0.1:6379 select 1 OK 127.0.0.1:6379 dbsize (integer) 0select切换库dbsize看当前库里有多少个key。我强烈建议生产环境尽量只用db0其他库要么不创建要么测试专用。为什么因为select一切换你后面所有命令的操作范围都变了一旦脚本里写死了库号排查起来非常痛苦。我就踩过这坑——有次排查线上缓存不生效查了半天发现数据其实写在了db1而业务代码读的是db0。再说auth。现在稍微正规一点的Redis都会设置密码auth yourpassword是输入密码的命令。这里有个小技巧# 在命令行直接带密码容易进history不推荐 redis-cli -a yourpassword # 推荐用环境变量 REDISCLI_AUTHyourpassword redis-cli用-a虽然方便但密码会出现在shell历史记录里安全性很差。REDISCLI_AUTH这个环境变量能让你的密码不出现在命令行参数中是我日常比较推荐的方式。2.2 信息查看组合拳info、client list真正遇到Redis出问题的时候你会特别感激info这条命令。它是一个大分类查询后面可以接不同的子参数127.0.0.1:6379 info server # 版本、启动时间、进程号 127.0.0.1:6379 info memory # 内存使用、碎片率、峰值 127.0.0.1:6379 info clients # 连接数相关 127.0.0.1:6379 info stats # 总命令数、每秒请求数、命中率 127.0.0.1:6379 info replication # 主从复制状态 127.0.0.1:6379 info persistence # RDB/AOF相关我自己接到线上Redis告警时的操作习惯是先info memory看内存水位再info clients看连接数有没有打满对比maxclients然后dbsize确认数据量是否在同一量级一分钟内就能大致判断是哪一类问题。很多人面试被问到线上Redis出问题你先查什么其实答案就是这套组合拳。client list也是排查利器它会列出所有客户端连接的IP、端口、状态、空闲时间、最后执行的命令。有次线上连接数飙高我就是靠client list定位到某台业务服务器的Redis连接池没释放一堆连接处于空闲状态占着不还。2.3 flushdb与flushall急救工具也是危险品# 清空当前库 flushdb # 清空所有库 flushall这两个命令简单到令人发指但危险程度也令人发指。我的建议是生产环境的配置文件中直接禁用它们在redis.conf里加一行rename-command FLUSHALL 让命令不存在或者改名成一个很长的随机字符串只有自己知道。不然哪天手滑一条flushdb发到生产库后果不敢想。即便不禁用我也养成了一个铁律执行破坏性命令之前先敲info server或client list确认自己连的是测试环境还是生产环境。别觉得啰嗦多花三秒钟能救命。3. 五大数据类型的常用命令每个类型我最常用的是哪几条这一节是Redis命令的重头戏。五种数据类型是Redis使用频率最高、面试问得最深的部分。我不打算把每个类型的命令都列一遍那样和文档没区别。我只挑最常用的几条讲清楚使用场景就行。3.1 String类型不只是set/getset key value get key mset key1 value1 key2 value2 mget key1 key2 setnx key value # 只有key不存在时才设置成功 setex key seconds value # 设置值并指定过期时间 incr key decr keyString最经典的应用是计数器。incr是原子自增做访问量统计、秒杀库存扣减都非常合适。这里必须提醒一句不要用get加set自己拼先读后写的逻辑会存在并发覆盖的问题。比如你想给库存扣减1用get拿到当前值减1后再set回去两个客户端同时操作就会超卖。incr一条命令解决因为它本身就是原子的。setnx和setex也值得单独说。setnx常被用来实现分布式锁的雏形某个key不存在时才能设置成功谁设置成功谁就拿到了锁。setnx lock_key client_id但setnx加锁有个经典坑如果客户端拿到锁之后崩溃了锁永远不会释放其他客户端就只能干等。所以必须给锁加上过期时间。Redis 2.6.12往后set命令支持一次完成赋值NX过期时间set lock_key client_id NX EX 30这个组合比先setnx再expire更稳因为两条命令之间存在时间窗口中间进程挂了会导致锁没设过期时间。这个细节也是面试里考察分布式锁实现的常见考点。3.2 Hash类型对象存储的最优解hset user:1001 name zhangsan age 28 hget user:1001 name hgetall user:1001 hmset user:1002 name lisi age 30 city beijing hmget user:1002 name city hdel user:1001 age hincrby user:1002 age 1 hlen user:1002Hash适合存储对象结构比如用户信息、商品信息。它最大的价值是支持字段级别的读取和更新。如果你用String存一个JSON序列化后的对象要改其中一个字段就得整个读出来、反序列化、改字段、再序列化写回去既费网络带宽又费CPU。用Hashhget取一个字段、hset改一个字段代价小得多。Hash还有一个容易被忽视的优势小对象的内存优化。当Hash里的字段数量小于hash-max-ziplist-entries默认128、字段值长度小于hash-max-ziplist-value默认64时Redis内部用ziplist压缩存储比直接存一个JSON字符串省不少内存。当然这个细节属于内部编码优化生产环境我一般不会手动干预但理解它有助于你明白为什么用Hash存对象是个好选择。3.3 List类型队列与栈的一体两面rpush task_queue task_1 lpop task_queue llen task_queue lrange task_queue 0 -1 lpush task_queue high_priority_task blpop task_queue 5 brpop task_queue 5List最常见的用途是实现一个轻量队列rpush往右端塞任务lpop从左端取任务。lpush可以往头部插入结合lpop就变成了栈结合rpop就变成了双向队列。lrange做分页查询很好用比如拉取最近几条操作记录lrange operation_log 0 9blpop和brpop是List命令里比较特殊的存在——阻塞式读取。如果队列为空它会一直等待直到有新任务进来或者等到超时时间结束。这个特性做任务队列比轮询省资源不用每秒钟lpop一次空转。但List做队列有个硬伤消息不保证不丢。rpush成功但lpop执行前客户端崩了这条消息就永远待在队列里没人管消费者lpop拿到消息后还没处理完就挂了消息也丢了。所以List只适合对可靠性要求不高的内部任务队列真正的消息中间件场景还是用Stream类型或者外部MQ更稳妥。3.4 Set类型去重和集合运算sadd tags:user:1001 java redis mysql srem tags:user:1001 mysql smembers tags:user:1001 sismember tags:user:1001 redis scard tags:user:1001 sinter set1 set2 sunion set1 set2 sdiff set1 set2 spop lottery_pool srandmember lottery_poolSet的核心价值是自动去重和集合运算。比如用户标签系统、推荐系统的共同好友功能用sinter求交集一行命令就出来了。抽奖场景用spop随机弹出一个元素会删除如果只是随机展示一个不删除用srandmember。很多人容易把spop和srandmember搞混我这里说一下区别spop是取出并移除适合抽奖这种不允许重复中奖的场景srandmember是随机看一眼但不拿走适合推荐位随机展示。别用错否则业务逻辑会出大问题。3.5 ZSet类型排序是它的核心价值zadd leaderboard user_a 100 zadd leaderboard user_b 200 zscore leaderboard user_a zincrby leaderboard 50 user_a zrevrange leaderboard 0 9 zrangebyscore delay_queue 0 1699999999 zrem leaderboard user_c zcard leaderboardZSet是Redis里最具杀手锏气质的数据结构它比Set多了一个score分数并且按分数自动排序。最经典的应用是排行榜。游戏里玩家分数变化用zincrby加分要展示前十名直接zrevrange按分数从高到低取。另一个非常经典的场景是延迟队列把score设成任务执行的时间戳比如5分钟后执行就是now 300用一个线程每隔几秒执行zrangebyscore delay_queue 0 now取出所有到期任务处理完zrem删掉。这个模式简单、靠谱用来做订单超时关闭、定时任务调度都很好使。五大数据类型的命令到这里就覆盖得差不多了。我给你整理成一个速查表实际用的时候回来找就行类型常用命令核心场景Stringset/get/incr/setnx/setex缓存、计数器、分布式锁Hashhset/hget/hgetall/hdel对象存储、部分字段更新Listrpush/lpop/lrange/blpop队列、栈、最新列表Setsadd/sismember/sinter/spop去重、标签、抽奖ZSetzadd/zincrby/zrevrange/zrangebyscore排行榜、延迟队列4. key管理、过期与淘汰策略容易忽略但决定生产环境命运的命令4.1 别用KEYS用SCAN很多人学习时第一个学会的命令可能不是set而是keys *因为想看看库里有什么key。这个命令在本地玩没问题但生产环境绝对不能用。原因在于Redis是单线程执行命令的keys *需要对全库的key做一次线性扫描当key数量达到百万级别一次keys *可能阻塞Redis好几秒钟。Redis被卡住的这几秒里所有读写请求全部排队服务对外表现是卡死。生产事故往往就是这么来的。正确姿势是scan它采用游标方式分批迭代# 第一次调用返回游标和一批key scan 0 count 100 # 第二次把返回的游标继续传进去直到游标变成0 scan 1234 count 100scan每次只返回一小部分key不会阻塞Redis。count 100是一个提示告诉Redis大概给我100个key但不是严格的返回条数保证。还有一个细节scan过程中如果有key新增或删除结果可能不完整这是游标迭代的固有特性所以scan天生不适合做全量精确查询更适合做分批扫描然后交给业务逻辑处理。以scan为基础Redis 6.0还支持了scan 0 count 100 type string这种按类型过滤的语法统计大key、清理特定类型的数据都非常方便。4.2 过期时间的正确使用姿势Redis缓存之所以能控制内存靠的是过期机制。expire key seconds # 给已有key设置过期时间 ttl key # 查看剩余过期时间 pttl key # 精确到毫秒 persist key # 移除过期时间 setex key seconds value # 创建key时直接指定过期时间这中间有几个坑必须知道第一个坑ttl返回-1表示key不存在过期时间返回-2表示key不存在。很多人分不清这两个负数面试里也是高频考点。你可以这样记-1是永久有效-2是查无此key。第二个坑重新赋值会让过期时间失效。执行一次set key newvalue之后原来设置的过期时间就没了key会变成永久key。我见过有的业务方用set更新缓存时没注意这点导致缓存越积越多最后内存打爆。正确做法是用setex或者在set之后重新expire一下。第三个坑过期时间不等于主动删除。Redis删除过期key是惰性删除 定期删除双策略每次访问key时检查它是否过期过期就删同时定期抽一批设置了过期时间的key出来检查。你设置了过期时间不代表过期的key会立刻从内存里消失。当大量key同时过期、又没有及时被清理时内存可能短期内不降这也是缓存雪崩的诱因之一设计过期时间时要加入随机偏移量来错开。4.3 DEL、UNLINK与类型元信息del删除key是日常工作里很常见的操作但删除一个大key比如一个百万成员的ZSet、一个几十MB的String时非常危险因为它要回收大量内存同样会阻塞Redis。Redis 4.0以后提供了unlinkdel big_key # 同步删除可能阻塞 unlink big_key # 异步删除后台线程慢慢释放内存大key清理、批量删除场景我推荐优先用unlink。当然普通小key用del还是unlink差别不大但养成习惯之后遇到大key也顺手了。还有几个元信息命令也很常用type key # 查看key的数据类型 object encoding key # 查看内部编码方式面试常问 randomkey # 随机返回一个key快速抽样用 exists key # 判断key是否存在object encoding能看出一个key底层的真实编码结构比如String是embstr还是rawHash是ziplist还是hashtableList是quicklist等等。面试里问到Redis为什么节省内存时拿这个命令去验证编码转换规则说服力很强。5. 事务、管道与脚本批量操作的正确姿势5.1 事务没那么神秘MULTI、EXEC、WATCHRedis的事务和关系型数据库事务完全是两码事。multi开启事务之后后续命令不会立即执行而是进入一个队列直到exec才一次性按顺序发出去执行discard则是放弃事务。multi set user:1 age 30 incr counter execRedis事务的核心特性是打包执行、按顺序执行但没有回滚机制。如果事务中间某条命令执行时报错它只是跳过这条命令后面的命令照常执行已经执行的结果不会撤销。这和MySQL的ACID完全不同所以别拿关系型数据库的逻辑去看待它。watch是事务的一个补充能力实现乐观锁watch user:1 # 其他客户端改了user:1 multi set user:1 age 31 exec # 如果user:1被改过exec会返回nil并拒绝执行应用场景是先检查后更新比如你要根据某个key的当前值决定下一步操作用watch锁住它执行事务前如果key被其他人改了事务就会失败需要重试。这个机制类似CASCompare And Swap但实际业务里用它的人已经很少了因为更优雅的方案是用Lua脚本后面会讲。5.2 管道技术与发布订阅pipeline不是Redis服务端的命令而是客户端提供的一种模式把多条命令一次性发给RedisRedis执行完后再一次性把结果返回给客户端。# 以Python为例 import redis r redis.Redis() pipe r.pipeline() pipe.set(key1, value1) pipe.set(key2, value2) pipe.execute()为什么管道快因为省掉了每一条命令的网络往返时间RTT。Redis服务器就在本机可能感受不明显但客户端和服务端在跨机房、跨地域的网络环境下RTT可能高达几十毫秒批量操作几百条命令时管道能省出几百倍的耗时。但注意管道不是事务。管道只是把命令打包成一次网络发送服务端接收到之后还是逐条执行的中间某条命令出错并不会影响其他命令执行也不保证要么全成功要么全失败。想原子性还是找multi或Lua想吞吐量大就用管道两者解决的问题不一样。发布订阅是另一个容易被忽略的能力subscribe channel:news # 订阅频道 publish channel:news hello # 发布消息一条publish所有订阅了这个频道的客户端都会收到消息非常轻量。它的限制是消息不持久化如果订阅者不在线消息就永久错过了。所以它只适合实时通知、内部事件广播这类场景。真正要求不丢消息的队列场景还是需要用Stream类型或者外部消息队列。5.3 一条EVAL让数据操作原子化上面提到的事务和分布式锁都存在多条命令之间不是原子操作的问题。Redis的终极解决思路是Lua脚本把多条Redis命令写进一个Lua脚本里整段脚本在Redis服务端执行执行期间不会被其他命令插入天然原子。eval return redis.call(set, KEYS[1], ARGV[1]) 1 mykey helloeval的格式是脚本内容、1表示有1个key、然后是key列表和参数列表。脚本里用redis.call执行Redis命令KEYS数组取keyARGV数组取参数。redis.call和redis.pcall的区别很关键call执行出错会把异常抛给Redis并在客户端报错pcall则是protected call把错误捕获返回成Lua的table方便在脚本内部做错误处理。写复杂脚本时用pcall兜底更稳。举个经典的分布式锁实现-- 加锁key不存在时设置value并且设置过期时间 if redis.call(set, KEYS[1], ARGV[1], NX, EX, ARGV[2]) then return 1 else return 0 end这段脚本把setnx expire合并成一步从根本上消除了中间步骤失败的风险。很多开源分布式锁框架包括Redisson的某些底层实现都是类似思路要保证原子性就交给Lua。这也是为什么我说会写Lua脚本是Redis进阶的一道分水岭。6. 生产环境问题排查慢日志、scan与大key扫描命令6.1 slowlog定位谁拖慢了RedisRedis是单线程的一条慢命令会让所有后面的请求排队。所以排障的第一件事就是看慢日志。slowlog get 10 # 拿到最近10条慢命令 slowlog len # 慢日志总数 slowlog reset # 清空慢日志慢日志记录的是执行时间超过阈值的命令阈值由配置项slowlog-log-slower-than控制单位是微秒默认10000也就是10毫秒。我一般会把线上配置调成slowlog-log-slower-than 10001毫秒这样能捕捉到更多潜在问题。有一次我排查线上Redis偶发卡顿抓到的慢日志里全是keys *后续顺藤摸瓜发现是运营后台写了个全量查询所有用户缓存key的接口。这类问题不借助slowlog很难定位因为keys *执行时Redis已经卡了但卡完日志也恢复了只有慢日志忠实记录了凶手。6.2 monitor开发环境调试神器生产慎用monitormonitor会实时打印Redis收到的每一条命令联调阶段排查某个key为什么被莫名其妙修改了非常有用。比如你怀疑有代码在改某个缓存开monitor几分钟所有来源命令看得一清二楚。但生产环境要非常谨慎monitor会把所有命令实时输出到客户端本身就是高消耗操作会拖慢Redis性能还可能因为刷屏把session给冲爆。我的习惯是生产环境只开一两分钟用monitor配合grep过滤条件查到线索立刻关掉。6.3 大key扫描一步到位redis-cli --bigkeys大key的危害前面已经说了——删除阻塞、内存不均衡、查询超时。那怎么快速找出大keyRedis官方提供了一条现成的命令redis-cli --bigkeys它会利用scan遍历整个实例的所有key然后分别统计出每种类型里最大的几个key输出包括key名、类型、大小。这个命令在生产环境相对安全因为是分批次扫描但频繁跑也会占资源建议在业务低峰期执行。redis-cli --bigkeys只能粗略看每个类型最大的key想精确知道某个key占了多少内存Redis 4.0以后可以用memory usagememory usage user:1001它会精确估算出指定key消耗的字节数输出结果可以用来和人内存涨了但不知道涨在哪的问题较劲。再配合info memory看全局内存水位、info commandstats看哪个命令调用次数最多一套完整的Redis缓存治理排查链路就有了。6.4 持久化相关命令SAVE与BGSAVE顺带说一下和运维强相关的两个命令。save是同步执行RDB快照会阻塞主进程生产环境直接禁用bgsave是fork一个子进程在后台做RDB快照不阻塞主进程日常需要手动触发快照时用bgsave。bgsave lastsavelastsave可以确认最近一次快照时间配合检查备份文件是否正常更新。这一块和游戏存档一个道理存档操作不能卡住你玩游戏bgsave就是后台存档save就是存盘期间你什么都干不了。生产环境务必用bgsave。写到这里我脑子里还浮现出几年前的一个小插曲测试环境里我手滑执行了一次flushdb当时觉得反正是测试环境没关系结果当天所有联调用的测试数据全没了同事们一上午都在重建数据。后来我给自己立了规矩——所有标注危险的命令在使用前必须多敲一次命令确认当前连的是哪台机器比如先执行info server看run_id或者启动时间。这招看起来傻但它确确实实拦住过我无数次手滑。命令本身都是几行字母真正值钱的永远是使用时的判断力希望这篇文章能帮你把判断力往前提一步。