Redis的命令看起来多,真要分门别类记下来,常用到的基本不超过三十条。搞后端这些年,我给不少新人讲过Redis,最深的感受是:大多数人学命令的方式不对,一上来就背命令行,背完就忘。这篇我打算换个思路,从自己平时实际使用、排查、面试的角度,把redis基础常用命令重新捋一遍:从环境安装、连接自检,到五大数据类型怎么推导命令,再到过期持久化、线上排查、分布式锁这些实操场景。适合刚接触Redis的入门读者,也适合用了一段时间但命令体系还比较乱的同学。
1. 装完Redis先别急着敲命令:环境准备和连接自检
1.1 Windows:zip解压版比安装版好用在哪里
很多人在Windows上装Redis,第一反应是去官网找exe安装包。实际上Redis官方并不提供Windows版本,大家常用的Windows版是开源社区维护的移植版。我的建议是直接用zip解压版,别折腾exe安装版。
操作不复杂:下载zip压缩包后解压到某个目录,比如D:\redis,目录下就能看到redis-server.exe和redis-cli.exe这两个关键文件。启动服务就直接执行:
redis-server.exe默认监听6379端口,跑起来就完事。新开一个终端窗口,用redis-cli.exe连上去:
redis-cli.exe -h 127.0.0.1 -p 6379能看到127.0.0.1:6379>这个提示符,说明连接成功。
这里有个经验:测试环境我一般直接redis-server.exe redis.windows.conf指定配置文件启动,生产环境则很少用Windows跑Redis,基本都是Linux服务器或者Docker容器,Windows上更多是本地开发调试用。
1.2 Linux和macOS:一行命令装好
Linux上主流发行版用包管理器装很快:
# Debian/Ubuntu系 apt install redis-server -y # CentOS/RHEL系 yum install redis -ymacOS用户推荐用Homebrew:
brew install redis装完以后先用redis-server --version看下版本,确认装的是不是太老的版本。我遇到过一些老服务器上自带的Redis版本还是3.x,很多新命令和特性用不了,这时候建议直接升级或者用Docker跑新版本。
1.3 Docker:一条命令拉起一个干净实例
现在我最推荐的是Docker方式,尤其是想快速起一个隔离环境做测试的时候:
docker run -d --name myredis -p 6379:6379 redis:7要带密码就加个启动参数:
docker run -d --name myredis -p 6379:6379 redis:7 redis-server --requirepass 123456连容器里的redis-cli:
docker exec -it myredis redis-cli -a 123456为什么推荐Docker?因为它把配置、数据、版本都隔离得干干净净,删了重建也就几秒钟。网上那些问"docker安装redis主从"的,说到底也是基于同一套思路,先起多个容器,再设置主从关系,后面配置一个replicaof就行。
1.4 连接后的三个自检命令
连上Redis之后,先别急着敲业务命令,用三条命令确认环境健康:
# 1. 服务存活 ping # 返回 PONG # 2. 服务基础信息 info server # 能看到 redis_version、运行端口、进程ID等 # 3. 当前库有多少key dbsizeping返回PONG说明服务正常;info server让你确认版本和配置而不是靠猜;dbsize是看当前库是否真的有数据,如果是空库,后面排查问题时要先想到"是不是压根没写入"这个方向。这三条命令加起来花不了两秒钟,但能给后面的操作定个基准线。
2. 命令这么多,先抓住五大数据类型这根主线
2.1 五大数据类型到底差在哪
Redis基础常用命令看着多,根源就在于它有五种数据类型。类型不同,命令的前缀就不同。先把类型差异记清楚,命令表基本就记住一大半了。
这个道理就像家里有五个抽屉,分别放钥匙、杂物、文件、票据、卡片,你要拿东西之前,脑子里先反应的是"从哪个抽屉拿",而不是记每个物品的精确位置。Redis的类型就是这些抽屉。
| 数据类型 | 底层存储结构 | 最大元素数量 | 典型场景 |
|---|---|---|---|
| String | 二进制安全的字符串/整数 | 512MB个值 | 缓存、计数器、分布式锁 |
| Hash | 字段-值映射表 | 2^32 - 1 个字段 | 对象存储、购物车 |
| List | 双向链表 | 2^32 - 1 个元素 | 消息队列、时间线 |
| Set | 无序不重复集合 | 2^32 - 1 个成员 | 去重、共同好友 |
| ZSet | 带分数的有序集合(跳表+哈希表) | 2^32 - 1 个成员 | 排行榜、限流权重 |
2.2 从数据类型推导出命令的关键规律
五大数据类型对应的命令前缀非常有规律:String对应s开头的操作(set、get),Hash对应h开头(hset、hget),List对应l开头(lpush、lpop),Set对应s开头(sadd、srem),ZSet对应z开头(zadd、zrange)。
这里有个小坑:Set和ZSet都是s系列,但ZSet固定是z开头,看到zadd就知道是带排序的集合,看到sadd就是普通集合。String也是s开头,不过String的命令一般更多是set/get这种两两配对,和集合的sadd/srem不冲突,多敲几次就能自然分开。
2.3 一个实用记忆框架:先想业务再想命令
我给新人的建议是:不要按字母序去背命令,要按场景去问自己这几个问题:
- 存储一个简单值?用String。
- 存储一个对象/字典?用Hash。
- 存储有顺序的列表?用List。
- 存储不重复的集合?用Set。
- 存储需要按分数排序的集合?用ZSet。
想清楚数据类型后,再动手查这个类型下的具体命令,比如"插入"是lpush还是rpush,"读取"是lrange还是zrange,全部有迹可循。这就是我记Redis命令的核心方法,后面所有具体命令都建立在这个框架上。
3. 通用命令:不管存什么都绕不开的那几条
3.1 keys的坑和scan的正确姿势
新手最容易上来就敲keys *,想看看库里有啥。公司环境里千万别这么干,keys *在key数量大时会阻塞整个Redis服务,因为它是全量扫描。线上Redis阻塞几秒钟是什么后果,做过高并发接口的人都懂。
正确做法是scan,它是游标式迭代,每次返回一批key和下一个游标位置:
# 从游标0开始,每次最多返回20个key scan 0 match user:* count 20 # 返回结果里第一部分是下一次的游标,第二部分才是key列表scan的核心特点是分批返回,不会一口气把所有key都翻一遍,所以对服务影响小。实际排查问题的时候,我更倾向于直接用scan 0 match user:*这种带pattern的方式,也比keys user:*放心得多。
3.2 生命周期四件套:exists、del、expire、ttl
这四个命令是排查问题的老搭档:
exists key # 1 存在 / 0 不存在 del key1 key2 # 删除成功后返回删除数量 expire key 300 # 设置key 300秒后过期 ttl key # 查看剩余过期时间,-1表示永久,-2表示key已不存在ttl返回-2和返回-1经常让新人懵。记住:-1是key还在但没有过期时间,-2是key根本没找到。排查问题时这俩差别很大,一个是"这个key不会自动消失",一个是"这个key压根没写进去"。
再补一个很实用的:set命令本身就能带过期时间,比如set token abc123 ex 7200。很多人不知道这个简化写法,习惯用set再接着expire,其实一步就能完成,还能避免两条命令之间服务崩溃导致过期时间没设置成功的隐患。
3.3 确认类型的type和object encoding
不知道某个key是什么类型,不要瞎猜,直接敲:
type key # string / hash / list / set / zsettype只回答数据类型本身,想知道更多内部编码方式,用object encoding key,它会告诉你这个key底层到底用了什么编码,比如string类型的key有的用int编码,有的用embstr,有的用raw。这个命令在排查内存问题时很有用,能帮你看清楚是不是某些key因为过大而转换了编码,导致内存翻倍。
3.4 flushdb和flushall这种危险命令要心里有数
flushdb清空当前库所有key,flushall清空所有库。这俩命令一旦执行,数据直接没了。我在测试环境用它们清理数据没问题,但在生产环境,除非你心里很清楚马上会发生什么,否则别碰。
真要用,至少给它们加个safe意识:先搞清楚自己连的是哪台机器哪个库,用info keyspace确认一下,别连错了再执行。有些团队会在配置里直接禁用flushdb和flushall,这也是行业里很常见的做法。
4. String和Hash:项目里最频繁的两类命令
4.1 set/get/mset/mget:别忽略批量版本
String最基础的一组命令:
set user:name "zhangsan" get user:name但如果要操作多个key,我更推荐批量命令:
mset user:name "zhangsan" user:age "30" user:city "beijing" mget user:name user:age user:city # 返回 "zhangsan" "30" "beijing"为什么推荐批量?Redis是单线程模型,每条命令都有网络往返开销。批量操作把多次网络往返合并成一次,性能提升非常明显。举个例子:循环100次get和1次mget,后者的耗时可能只有前者的十分之一。这个优化在接口性能瓶颈排查时经常用到。
set命令本身的参数也要掌握,特别是这两组:
# NX: 仅当key不存在时设置成功 set coupon:2024001 "used" nx # EX: 设置过期时间,单位秒 set session:token "abc123" ex 1800nx和ex组合使用,就是后面讲分布式锁的基础。
4.2 计数器:incr/decr和过期时间的组合
String的整数值可以直接做自增自减:
incr article:read:1001 decr article:read:1001 incrby article:read:1001 10 decrby article:read:1001 5这个设计太常用了。点赞数、播放量、库存扣减,全部可以用一个incr解决。注意两点:incr对一个不存在的key会自动从0开始,所以不需要先初始化;但incr只能操作能被解析为整数的字符串,如果你存了"abc"再去incr,会报value is not an integer or out of range错误。
计数器经常跟过期时间搭配,比如每篇文章的日阅读量,可以用expire设置当天结束自动清零,第二天重新计数,省去定时任务的麻烦。
4.3 setnx:分布式锁最初的样子
setnx key value的全称是SET if Not eXists,只有key不存在时才能设置成功,这个特性天然适合做互斥锁:
setnx lock:order "uuid-1234" # 返回1:拿到锁;返回0:没拿到锁但这里有个经典坑:早期分布式锁的实现都是setnx加expire两条命令,如果setnx成功后还没来得及设过期时间,服务就挂了,锁永远不会释放,后面所有请求全部卡死。所以现在正确的姿势是用一条命令完成加锁和续期,这个放到第8章详细说。
4.4 Hash操作:对象缓存的最佳选择
Hash适合存对象,一个key对应一个对象的多个字段:
hset user:1001 name "lisi" age "28" city "shanghai" hget user:1001 name # 返回 "lisi" hmget user:1001 name age hgetall user:1001 hdel user:1001 city hexists user:1001 name hincrby user:1001 score 50为什么对象缓存用Hash不用String拼接?比如把整个用户对象序列化成一个大JSON存到String里,每次只改其中某个字段,就得整个JSON读出来反序列化再写回去,浪费资源。用Hash,改年龄就hset user:1001 age 29,改积分就hincrby user:1001 score 10,简单直接,性能也好得多。
实际项目中我很常用hgetall,但要注意:字段特别多且频繁访问时,hgetall返回的数据量不小,可能出现"大key"问题。线上如果哈希很大,要么只取需要的字段用hmget,要么把hash拆成多个小key。
5. List、Set、ZSet:按业务场景去记命令最省脑力
5.1 List就是消息队列和最新列表
List底层是双向链表,左进右出、右进左出都很顺手:
# 左端写入、右端读取 = 队列 lpush queue:task task-a task-b task-c rpop queue:task # 左端写入、左端读取 = 栈 lpush stack:op "undo1" lpop stack:op做队列时有个注意点:如果列表空了你还在rpop,返回的是nil,不是说错了,是表示当前没数据。高并发环境下很多人会写个循环去rpop空列表,白白消耗CPU,更合理的做法是用brpop阻塞读取,列表一旦有数据就立刻返回:
brpop queue:task 0 # 0表示永久等待这个差异在消息队列场景里很关键。
List还有几个常配组合:llen查看队列长度,lrange key 0 -1取全部元素,ltrim key 0 99只保留前100个,实现"最近100条记录"这类功能非常合适。
5.2 Set负责去重和集合关系运算
Set的命令套路很清晰,主要分两类:
# 基本增删查 sadd user:tags "java" "redis" "mysql" srem user:tags "mysql" smembers user:tags sismember user:tags "redis" scard user:tags# 集合关系运算 sinter key1 key2 # 交集 sunion key1 key2 # 并集 sdiff key1 key2 # 差集集合运算在业务里非常常用,比如"共同关注好友"就是用sinter算两个关注列表的交集,"推荐给A但B已经关注过的人"用sdiff。抽奖系统里用sadd把每个用户ID塞进集合,中奖时用spop随机弹出一个,天然去重,还不用自己写随机逻辑。
5.3 ZSet是排行榜的唯一选择
ZSet = Set + 分数排序。每个成员都带一个score,Redis会根据score自动排序:
zadd rank:game 1000 "player_1" zadd rank:game 950 "player_2" zadd rank:game 1200 "player_3" # 按分数升序取成员 zrange rank:game 0 -1 withscores # 按分数降序取成员(排行榜常用) zrevrange rank:game 0 2 withscores # 查看某个成员分数 zscore rank:game player_1 # 增加分数 zincrby rank:game 50 player_1排行榜分页也方便,zrevrange rank:game 0 9 withscores就是取前10名,zrevrange rank:game 10 19 withscores就是取第11到20名。
ZSet还有一个很少被提到但很好用的点:zrangebyscore按分数范围取成员,适合做时间线或者按分数筛选的排名。比如订单金额排行里,筛选出分数在1000到5000之间的所有会员,一个命令就查出来,MySQL可能还得写一堆条件。
5.4 三种结构在缓存里的典型用法
搞缓存架构的时候,这三种类型的发挥空间特别大。List适合存时序型数据,比如用户的操作日志、消息通知列表;Set适合存标签、白名单、关注关系;ZSet适合存带有权重或时间戳的数据,比如热门文章按浏览量排名、直播间活跃用户按在线时长排序。
我之前做过一个推荐系统,用户看了哪些视频用Set存,视频热度用ZSet按播放量排序,用户最近观看记录用List存最近50条。查询推荐结果时,zrevrange拿热门榜,再sdiff掉用户已看过的,一个请求就能组装出推荐列表,这套方案比频繁查库轻太多。
6. 数据安全相关命令:过期、持久化和恢复
6.1 key过期机制:主动过期和惰性过期
Redis的过期删除策略是两种结合:惰性删除 + 定期主动删除。惰性删除指每次访问key时检查是否过期,过期才删除;主动删除指周期性地从过期字典里抽样删除一部分已过期的key。这两种机制组合下来,保证了过期key不会一直占着内存,但也不会实时消失。
这就解释了一个现象:ttl key明明已经过期了,但dbsize里它还在,内存也没立刻释放。别慌,等下一轮主动删除或者下次访问这个key时它就会消失。理解这个机制,排查内存问题时才不会误判。
6.2 save、bgsave和RDB快照
Redis默认启用了RDB持久化,把某个时间点的数据全量快照到磁盘上的dump.rdb文件。手动触发有两种方式:
# 阻塞式保存,不推荐生产环境用 save # 后台保存,fork子进程去写快照 bgsavesave会阻塞Redis主线程,数据量大时服务直接卡住,生产环境千万别手动执行。bgsave在Redis里很常见,执行后可以用lastsave查看最近一次成功保存的时间戳:
lastsaveRedis还可以配置自动快照,比如save 900 1表示900秒内至少有1次写入就执行bgsave。RDB的优点是恢复速度快,缺点是两次快照之间的数据可能丢失。理解这个特性,你就知道为什么有些场景必须依赖AOF了。
6.3 AOF持久化和bgrewriteaof
AOF(Append Only File)记录的是每一条写命令本身,类似MySQL的binlog。Redis重启时把这些命令重放一遍,就能最大化地恢复数据。相关配置和命令:
# 开启AOF,同时设置同步策略 appendonly yes # 同步策略可以选 everysec / always / no,常见用 everysec # 手动触发AOF文件重写 bgrewriteaofAOF文件会随着写入不断增加,所以需要重写压缩,bgrewriteaof就是干这个的。它会把内存中的状态重新生成一份最小的命令集合写进新文件,避免AOF无限膨胀。实际经验是,不要让AOF文件长得太大才去重写,可以配auto-aof-rewrite-percentage和auto-aof-rewrite-min-size自动触发。
| 持久化方式 | 恢复速度 | 数据丢失风险 | 文件大小 |
|---|---|---|---|
| RDB | 快 | 两次快照间可能丢 | 小,二进制压缩 |
| AOF | 慢一些 | 最多丢一秒(everysec) | 大,文本日志 |
6.4 我自己验证持久化恢复的一次实操
很多教程讲持久化讲得很玄,其实验证起来很简单。我当时的步骤是:先启动一个开了AOF的Redis实例,写入几个key,然后用kill -9直接杀掉进程模拟断电,再重新启动Redis,看数据还在不在。
流程是这样的:
redis-cli > set user:1001 "zhangsan" > set user:1002 "lisi" # 强行杀掉进程 kill -9 PID # 重新启动Redis redis-server redis.conf # 再查数据 redis-cli > get user:1001 # "zhangsan"如果AOF配置正确,重启后数据能恢复;如果只开了RDB且最后一次快照发生在写入之前,数据就丢了。这种实操成本很低,建议大家都亲手试一次,比背十遍配置都记得牢。
7. 线上运维必须会看的命令:info、slowlog和client
7.1 info输出怎么看:内存、连接数、命中率
info命令可以说是Redis的体检报告,直接执行info会输出一大段分段信息,常见分段有:server、clients、memory、persistence、stats、replication、keyspace。
我排查问题时重点看这几个指标:
# 内存 info memory # 注意看 used_memory_human 和 maxmemory # 客户端连接 info clients # connected_clients 要是异常高,就要查是不是有连接泄漏 # 命中率 info stats # keyspace_hits 和 keyspace_misses,两者一起看命中率的计算是hits / (hits + misses),这个值低于90%就要审视缓存策略设计是否合理。很多团队报"缓存命中率低",我第一件事都是让他们先跑一下info stats,命中率数据一目了然。
7.2 config命令:运行中修改参数的正确姿势
config命令可以在不重启Redis的情况下查看和修改运行参数:
config get maxmemory config set maxmemory 512mb config get appendonlyconfig set适合临时调整,比如内存告警时先紧急调大maxmemory。但要注意,并非所有参数都支持config set热修改,部分参数改了也不会立刻生效或者根本不支持。调整完之后,建议执行:
config rewrite这个命令会把当前运行配置写回配置文件,保证Redis重启后新配置依然生效。否则等进程一重启,你又回到旧配置,之前的操作等于白做。
7.3 slowlog定位慢命令
Redis是单线程的,一旦某个命令执行很慢,后面的请求全得排队。定位慢命令用slowlog:
# 查看最近的10条慢命令 slowlog get 10 # 慢命令的数量 slowlog len # 清空慢日志 slowlog reset慢命令的阈值由slowlog-log-slower-than配置,默认是10000微秒,执行超过10毫秒的命令就会被记录。生产环境建议调低一点,比如5000微秒(5毫秒),更敏感地发现问题。拿到慢命令后,常见元凶是keys *、hgetall大hash、smembers大set,解决方案一般是改用scan分批查或把大key拆分。
7.4 client list和monitor的排查作用
连接数异常、有客户端卡死时,用client list看所有客户端连接:
client list # 会显示每个连接的地址、状态、执行的命令等发现问题连接可以用client kill ip:port杀掉。monitor命令更厉害,它会把Redis收到每条命令实时打印出来,相当于把服务内部的操作全部暴露给你看。但正因如此,生产环境在高流量下跑monitor会极大消耗性能,我只在问题复现场景临时开一小会儿,用完立刻关闭。
这里顺带说一句,很多人问"Redis端口通不通怎么看",如果不想开客户端工具,直接telnet ip 6379试试,能通则说明端口通。或者redis-cli -h ip -p 6379 ping,返回PONG就说明服务可达。这个排查思路和client list结合起来,能快速区分是网络问题还是服务端连接数问题。
8. 面试和实战常考的命令组合:分布式锁、缓存模式和限流
8.1 分布式锁:从setnx+expire到set nx px
分布式锁几乎是Redis面试必问的点,也是实际开发里绕不开的场景。前面提过,setnx加expire的经典写法有锁永不释放的隐患。现在正规做法是用一条命令同时完成加锁和过期:
set lock:order "uuid-1234" nx ex 30 # 返回OK表示加锁成功,返回nil表示加锁失败加锁没问题了,释放锁也要注意:不能直接del,因为你可能把自己的锁删掉了别人的(业务执行超过30秒,锁自动过期,别人拿到新锁,这时你执行del会把别人的锁删掉)。释放锁要用Lua脚本验证value是不是自己的再删除:
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end这套组合理解透了,面试时可以顺手点出"锁的过期时间要大于业务最大执行时间,避免业务没跑完锁就失效"这个细节,比只背命令强得多。
8.2 缓存穿透、击穿、雪崩对应的命令姿势
这三个缓存的经典问题,本质都靠命令组合来缓解:
- 缓存穿透:查询一个一定不存在的key,每次都打到DB。解决姿势是缓存空值,用
set key "" ex 300给空结果也加缓存。 - 缓存击穿:某个热点key过期瞬间大量请求打到DB。解决姿势是互斥锁,用
set lock:key "1" nx ex 10,抢到锁的线程去查DB回填缓存,没抢到的先等一会儿再查缓存。 - 缓存雪崩:大量key同一时间过期。解决姿势是过期时间加随机值,比如
expire key 3600 + random(0,300),让过期时间错开。
这些方案每个都建立在基础命令之上,没有额外的中间件。
8.3 用incr+expire做简单限流
限流是另一个高频场景,用Redis实现最简版本只需要两条命令:
# 每来一个请求就自增 incr api:limit:user:1001 # 如果是第一次请求,设置过期时间固定窗口 expire api:limit:user:1001 60逻辑就是:同一个用户ID在60秒内,incr返回的值超过阈值就拒绝请求。这个方案简单归简单,但它是个固定窗口限流,临界点可能出现双倍流量,比如第59秒和第61秒交界处各放行一批。真要做的严谨,可以用ZSet的zremrangebyscore和zrangebyscore记录时间戳实现滑动窗口,命令复杂度会高一些,但对多数业务场景,固定窗口的incr+expire已经够用。
9. 安全相关的最后一块:auth密码和危险命令防护
9.1 设置密码与auth命令
Redis默认没有密码,任何能连到6379端口的人都能操作你的数据,这非常危险。设置密码有两种方式:
# 方式一:配置文件里写 requirepass requirepass yourpassword # 方式二:运行时设置 config set requirepass yourpassword设置了密码后,每次连接都需要认证:
redis-cli -a yourpassword # 或者在交互窗口里 auth yourpassword这里有个我踩过的坑:config set requirepass之后,当前连接不会立刻退出,而是下次执行命令时才发现没认证。如果你正在排查生产环境,改完密码要记着重新认证,不然排查途中所有命令突然报NOAUTH,容易吓一跳。
9.2 重命名或禁用危险命令
生产环境的Redis安全防护,除了密码,还建议在配置里把危险命令改名或禁用。比如flushall、flushdb这类可能清空数据的命令,keys这类可能阻塞服务的命令,以及config这类可能篡改配置的命令:
rename-command flushall "" rename-command flushdb "" rename-command keys "deny_keys" rename-command config "deny_config"注意rename-command不能同时设置成空字符串和同名配置混淆,实际用的团队要么直接禁用要么改成只有运维知道的自定义名字,例如echo测试命令保留,flushall禁用。另外,Redis 6.0以后引入了ACL(Access Control List),可以给不同用户分配不同权限,比全局一个密码更精细,比如只让某些客户端读、不许写,有需要可以进一步了解。
最后分享一个我自己的习惯:平时用redis-cli交互,绝大多数时间反复敲的其实就set、get、expire、ttl、incr、hgetall、zrange那么十来个命令。Redis命令体系看起来庞大,但落到日常开发和排查,核心路径很短。这篇是按我自己实际使用顺序整理的,装好Redis之后,建议你也照着这个顺序在本地跑一遍,遇到需要深挖的命令再用command info 命令名去查官方语义。命令这东西,用得多了自然就熟了,关键是第一步先把数据类型这条主线建立起来。