☰
Redis常用命令实战:从五大数据类型到生产环境避坑指南
2026/9/26 7:41:59 网站建设 项目流程

我见过太多人学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) 0

select切换库,dbsize看当前库里有多少个key。我强烈建议:生产环境尽量只用db0,其他库要么不创建,要么测试专用。为什么?因为select一切换,你后面所有命令的操作范围都变了,一旦脚本里写死了库号,排查起来非常痛苦。我就踩过这坑——有次排查线上缓存不生效,查了半天发现数据其实写在了db1,而业务代码读的是db0。

再说auth。现在稍微正规一点的Redis都会设置密码,auth yourpassword是输入密码的命令。这里有个小技巧:

# 在命令行直接带密码容易进history,不推荐 redis-cli -a 'yourpassword' # 推荐用环境变量 REDISCLI_AUTH=yourpassword 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/get

set key value get key mset key1 value1 key2 value2 mget key1 key2 setnx key value # 只有key不存在时才设置成功 setex key seconds value # 设置值并指定过期时间 incr key decr key

String最经典的应用是计数器。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:1002

Hash适合存储对象结构,比如用户信息、商品信息。它最大的价值是支持字段级别的读取和更新。如果你用String存一个JSON序列化后的对象,要改其中一个字段就得整个读出来、反序列化、改字段、再序列化写回去,既费网络带宽又费CPU。用Hash,hget取一个字段、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 5

List最常见的用途是实现一个轻量队列:rpush往右端塞任务,lpop从左端取任务。lpush可以往头部插入,结合lpop就变成了栈;结合rpop就变成了双向队列。

lrange做分页查询很好用,比如拉取最近几条操作记录:

lrange operation_log 0 9

blpop和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_pool

Set的核心价值是自动去重和集合运算。比如用户标签系统、推荐系统的"共同好友"功能,用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 leaderboard

ZSet是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 100

scan每次只返回一小部分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以后提供了unlink:

del 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还是raw,Hash是ziplist还是hashtable,List是quicklist等等。面试里问到"Redis为什么节省内存"时,拿这个命令去验证编码转换规则,说服力很强。

5. 事务、管道与脚本:批量操作的正确姿势

5.1 事务没那么神秘:MULTI、EXEC、WATCH

Redis的事务和关系型数据库事务完全是两码事。multi开启事务之后,后续命令不会立即执行,而是进入一个队列,直到exec才一次性按顺序发出去执行,discard则是放弃事务。

multi set user:1 age 30 incr counter exec

Redis事务的核心特性是"打包执行、按顺序执行",但没有回滚机制。如果事务中间某条命令执行时报错,它只是跳过这条命令,后面的命令照常执行,已经执行的结果不会撤销。这和MySQL的ACID完全不同,所以别拿关系型数据库的逻辑去看待它。

watch是事务的一个补充能力,实现乐观锁:

watch user:1 # 其他客户端改了user:1 multi set user:1 age 31 exec # 如果user:1被改过,exec会返回nil并拒绝执行

应用场景是"先检查后更新":比如你要根据某个key的当前值决定下一步操作,用watch锁住它,执行事务前如果key被其他人改了,事务就会失败,需要重试。这个机制类似CAS(Compare And Swap),但实际业务里用它的人已经很少了,因为更优雅的方案是用Lua脚本,后面会讲。

5.2 管道技术与发布订阅

pipeline不是Redis服务端的命令,而是客户端提供的一种模式:把多条命令一次性发给Redis,Redis执行完后再一次性把结果返回给客户端。

# 以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 "hello"

eval的格式是:脚本内容、"1"表示有1个key、然后是key列表和参数列表。脚本里用redis.call执行Redis命令,KEYS数组取key,ARGV数组取参数。

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:定位"谁拖慢了Redis"

Redis是单线程的,一条慢命令会让所有后面的请求排队。所以排障的第一件事就是看慢日志。

slowlog get 10 # 拿到最近10条慢命令 slowlog len # 慢日志总数 slowlog reset # 清空慢日志

慢日志记录的是执行时间超过阈值的命令,阈值由配置项slowlog-log-slower-than控制,单位是微秒,默认10000(也就是10毫秒)。我一般会把线上配置调成slowlog-log-slower-than 1000(1毫秒),这样能捕捉到更多潜在问题。

有一次我排查线上Redis偶发卡顿,抓到的慢日志里全是keys *,后续顺藤摸瓜发现是运营后台写了个"全量查询所有用户缓存key"的接口。这类问题不借助slowlog很难定位,因为keys *执行时Redis已经卡了,但卡完日志也恢复了,只有慢日志忠实记录了"凶手"。

6.2 monitor:开发环境调试神器,生产慎用

monitor

monitor会实时打印Redis收到的每一条命令,联调阶段排查"某个key为什么被莫名其妙修改了"非常有用。比如你怀疑有代码在改某个缓存,开monitor几分钟,所有来源命令看得一清二楚。

但生产环境要非常谨慎:monitor会把所有命令实时输出到客户端,本身就是高消耗操作,会拖慢Redis性能,还可能因为刷屏把session给冲爆。我的习惯是:生产环境只开一两分钟,用monitor配合grep过滤条件,查到线索立刻关掉。

6.3 大key扫描一步到位:redis-cli --bigkeys

大key的危害前面已经说了——删除阻塞、内存不均衡、查询超时。那怎么快速找出大key?Redis官方提供了一条现成的命令:

redis-cli --bigkeys

它会利用scan遍历整个实例的所有key,然后分别统计出每种类型里最大的几个key,输出包括key名、类型、大小。这个命令在生产环境相对安全,因为是分批次扫描,但频繁跑也会占资源,建议在业务低峰期执行。

redis-cli --bigkeys只能粗略看每个类型最大的key,想精确知道某个key占了多少内存,Redis 4.0以后可以用memory usage:

memory usage user:1001

它会精确估算出指定key消耗的字节数,输出结果可以用来和人"内存涨了但不知道涨在哪"的问题较劲。再配合info memory看全局内存水位、info commandstats看哪个命令调用次数最多,一套完整的"Redis缓存治理"排查链路就有了。

6.4 持久化相关命令:SAVE与BGSAVE

顺带说一下和运维强相关的两个命令。save是同步执行RDB快照,会阻塞主进程,生产环境直接禁用;bgsave是fork一个子进程在后台做RDB快照,不阻塞主进程,日常需要手动触发快照时用bgsave。

bgsave lastsave

lastsave可以确认最近一次快照时间,配合检查备份文件是否正常更新。这一块和游戏存档一个道理:存档操作不能卡住你玩游戏,bgsave就是后台存档,save就是存盘期间你什么都干不了。生产环境务必用bgsave。

写到这里,我脑子里还浮现出几年前的一个小插曲:测试环境里我手滑执行了一次flushdb,当时觉得"反正是测试环境没关系",结果当天所有联调用的测试数据全没了,同事们一上午都在重建数据。后来我给自己立了规矩——所有标注"危险"的命令,在使用前必须多敲一次命令确认当前连的是哪台机器,比如先执行info server看run_id或者启动时间。这招看起来傻,但它确确实实拦住过我无数次手滑。命令本身都是几行字母,真正值钱的永远是使用时的判断力,希望这篇文章能帮你把判断力往前提一步。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询