This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hhs/Redis/03-基本命令速查.md
T
2026-05-21 23:50:11 +08:00

419 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [Redis, 缓存, 命令]
create time: 2026-05-15 18:12
---
# Redis 基本命令速查
## 概述
按使用频率排序的常用命令,标注时间复杂度、典型场景和避坑点。完整命令参考官方文档。
### 数据类型选型速查
遇到业务需求时,按以下流程选择合适的 Redis 数据结构:
```mermaid
flowchart TD
A["需要存什么?"] --> B{"单值 / 键值对?"}
B -- 是 --> C["String<br/>缓存, 计数器, 分布式锁"]
B -- 否 --> D{"多个字段 / 对象?"}
D -- 是 --> E["Hash<br/>用户信息, 配置, 表单数据"]
D -- 否 --> F{"需要排序 / 排名?"}
F -- 是 --> G["ZSet<br/>排行榜, 延迟队列, 范围查询"]
F -- 否 --> H{"需要去重 / 集合运算?"}
H -- 是 --> I["Set<br/>标签, 共同关注, UV 统计"]
H -- 否 --> J{"需要队列 / 栈?"}
J -- 是 --> K["List<br/>消息队列, 最新列表, 栈"]
J -- 否 --> L["Pub/Sub<br/>广播, 实时通知"]
```
## String — 字符串操作
| 命令 | 返回值 | 复杂度 | 说明 |
|------|--------|--------|------|
| `SET key value [EX s] [PX ms] [NX\|XX]` | OK / nil | O(1) | **万能设置**,EX/PX 设过期,NX 仅不存在时写,XX 仅存在时写 |
| `GET key` | value / nil | O(1) | 获取值 |
| `MGET key [key...]` | values array | O(N) | 批量获取 |
| `SETRANGE key offset value` | length | O(1)* | 覆盖指定偏移量字节(*近似) |
| `APPEND key value` | new-length | O(1)* | 追加到末尾 |
| `INCR key` | incremented val | O(1) | 原子 +1 |
| `INCRBY key increment` | incremented val | O(1) | 原子 +N |
| `DECRBY key decrement` | decremented val | O(1) | 原子 -N |
| `STRLEN key` | length | O(1) | 获取字符串长度 |
| `GETSET key value` | old value | O(1) | **已废弃**,用 `SET key value NX` 替代 |
| `GETRANGE key start end` | substring | O(N) | 截取子串(end 为 -1 表示末尾) |
| `SETEX key seconds value` | OK | O(1) | **已废弃**,用 `SET key value EX seconds` 替代 |
| `PSETEX key milliseconds value` | OK | O(1) | **已废弃**,用 `SET key value PX milliseconds` 替代 |
| `MSET key value [key value ...]` | OK | O(N) | 原子设置多个键 |
```go
// SET 条件写入——分布式锁的核心
rdb.Set(ctx, "lock:order:"+orderId, "1", 3*time.Second, redis.KeepTTL|redis.OnlyIfAbsent)
// INCR 做限流计数器
rdb.IncrBy(ctx, "rate:user:"+userID, 1)
```
> [!WARNING] MGET ≠ 事务保证
> MGET 是并发执行 N 次 GET,不保证原子性。需要原子读多个字段请用 `MULTI` 或 Lua 脚本。
## Hash — 哈希操作
| 命令 | 返回值 | 复杂度 | 说明 |
|------|--------|--------|------|
| `HSET key field value [field value ...]` | added / changed count | O(N) | 设置一个或多个字段 |
| `HGET key field` | value / nil | O(1) | 获取单个字段 |
| `HMGET key field [field ...]` | values array | O(N) | 批量获取多个字段 |
| `HGETALL key` | all fields | O(N) | 获取全部键值对 ⚠️ 大 hash 慎用 |
| `HDEL key field [field ...]` | deleted count | O(N) | 删除字段 |
| `HEXISTS key field` | 1 or 0 | O(1) | 判断字段是否存在 |
| `HINCRBY key field increment` | new value | O(1) | 原子递增字段值 |
| `HSCAN key cursor MATCH pattern COUNT n` | cursor, entries | O(N) | 安全遍历,不用 HKEYS/HVALS 全量扫 |
```go
// 用户信息缓存——部分更新
pipe := rdb.Pipeline()
pipe.HSet(ctx, "user:42", "email", "new@email.com")
pipe.HSet(ctx, "user:42", "phone", "1380000")
pipe.Exec(ctx) // 管道内原子执行
```
> [!TIP] HGETALL vs HSCAN
> `HGETALL` 一步到位获取全部字段,但大 hash(>10K 字段)会阻塞主线程。生产环境推荐 `HSCAN` 分片读取。
## List — 列表操作
| 命令 | 返回值 | 复杂度 | 说明 |
|------|--------|--------|------|
| `LPUSH key elem [elem ...]` | list length | O(N) | 从左侧推入 |
| `RPUSH key elem [elem ...]` | list length | O(N) | 从右侧推入 |
| `LPOP key` | element / nil | O(1) | 弹出左侧元素 |
| `RPOP key` | element / nil | O(1) | 弹出右侧元素 |
| `LRANGE key start stop` | subarray | O(S+N) | 范围查询 S=start, N=stop-start |
| `LLEN key` | length | O(1) | 列表长度 |
| `LINDEX key index` | element | O(N) | 按索引获取 |
| `LTRIM key start stop` | OK | O(N) | 只保留指定范围(固定队列长度必备) |
| `BLPOP key [key ...] timeout` | element / nil | O(timeout) | **阻塞式** LPOP |
| `BRPOP key [key ...] timeout` | element / nil | O(timeout) | **阻塞式** RPOP |
| `RPOPLPUSH src dst` | element | O(1) | **已废弃**,用 LMOVE 替代 |
| `LMOVE key where1 where2` | element | O(1) | 弹出一端、push 到另一端(支持 LEFT/RIGHT) |
```go
// BRPOP 消费者模型
val, err := rdb.BRPop(ctx, 5*time.Second, "task_queue").Result()
if err == redis.Nil {
return nil // 超时,无消息
}
```
> [!QUESTION] LPUSH + RPOP 还是 RPUSH + LPOP?
> 两者性能相同(都是两端操作)。选择取决于语义——"先到的先处理"用 LPUSH+RPOP(FIFO),反之则相反。
## Set — 集合操作
| 命令 | 返回值 | 复杂度 | 说明 |
|------|--------|--------|------|
| `SADD key member [member ...]` | added count | O(N) | 添加成员 |
| `SREM key member [member ...]` | removed count | O(N) | 移除成员 |
| `SMEMBERS key` | all members | O(N) | 获取全部 ⚠️ |
| `SISMEMBER key member` | 1 or 0 | O(1) | 判断成员 |
| `SCARD key` | cardinality | O(1) | 元素个数 |
| `SRANDMEMBER key [count]` | member(s) | O(N) | 随机取(不删除) |
| `SPOP key [count]` | member(s) | O(N) | 随机取并删除 |
| `SINTER key [key ...]` | intersection | O(N*M) | 交集 ⚠️ 跨节点成本高 |
| `SUNION key [key ...]` | union | O(N*M) | 并集 |
| `SDIFF key [key ...]` | difference | O(N*M) | 差集 |
| `SINTERSTORE dst key [key ...]` | result count | O(N*M) | 交集结果存入新 key |
```go
// 共同关注——用户 A 和用户 B 都关注的人
common := rdb.SInter(ctx, "follow:userA", "follow:userB").Val()
// 抽奖系统——随机取 N 个不重复中奖者
winners := rdb.SPopN(ctx, "entry:lottery", 5).Val()
```
> [!TIP] Set 的典型应用场景
> - **去重**:UV 统计(`SADD` + `SCARD`)
> - **交集/并集**:共同好友、标签聚合
> - **随机抽样**:`SPOP` / `SRANDMEMBER` 做抽奖或推荐
## ZSet —— 有序集合
| 命令 | 返回值 | 复杂度 | 说明 |
|------|--------|--------|------|
| `ZADD key score member [score member ...]` | added/changed | O(N log N) | 添加/更新 |
| `ZSCORE key member` | score / nil | O(1) | 获取分数 |
| `ZINCRBY key increment member` | new score | O(log N) | 增加分数 |
| `ZRANGE key start stop [WITHSCORES]` | members | O(log N+M) | 正序范围 |
| `ZREVRANGE key start stop [WITHSCORES]` | members | O(log N+M) | 倒序范围 |
| `ZRANGEBYSCORE key min max [WITHSCORES]` | members | O(log N+M) | 分数区间 |
| `ZRANK key member` | rank / nil | O(log N) | 升序排名 |
| `ZREVRANK key member` | reverse rank / nil | O(log N) | 降序排名 |
| `ZCARD key` | count | O(1) | 元素数 |
| `ZCOUNT key min max` | count | O(log N) | 分数范围内元素数 |
| `ZREM key member [member ...]` | removed | O(M log N) | 移除 |
| `ZREMRANGEBYRANK key start stop` | removed | O(log N+M) | 按排名删 |
| `ZREMRANGEBYSCORE key min max` | removed | O(log N+M) | 按分数删 |
| `ZMSCORE key member [member ...]` | scores | O(N) | 批量获取分数(7.0+) |
| `ZPOPMAX key [count]` | top members | O(log N+M) | 弹出最高分 |
| `ZPOPMIN key [count]` | bottom members | O(log N+M) | 弹出最低分 |
| `ZINTERSTORE dst numkeys key [numkeys...]` | cardinality | O(N×K+K log K) | 多 ZSet 求交集存新 key |
| `ZUNIONSTORE dst numkeys key [numkeys...]` | cardinality | O(N×K+K log K) | 多 ZSet 求并集存新 key |
| `ZRANDMEMBER key [count] [WITHSCORES]` | members | O(N) | 随机取(7.0+) |
| `ZRANGEBYLEX key min max [LIMIT offset count]` | members | O(log N+M) | 同分数按 member 字典序排 |
| `ZSCAN key cursor MATCH pattern COUNT n` | cursor, entries | O(N) | 安全遍历 |
```go
// 排行榜——获取 TOP 10
top10 := rdb.ZRevRangeWithScores(ctx, "leaderboard", 0, 9).Val()
for _, z := range top10 {
fmt.Printf("%s: %.1f\n", z.Member, z.Score)
}
// 分数区间查询——80~100 分的学生
students := rdb.ZRangeByScore(ctx, "score:classA", redis.ZRangeBy{
Min: "80", Max: "100",
}).Val()
```
> [!TIP] 分数边界写法
> `[` 包含、`(` 不包含。例如 `"[80"` 表示 ≥80,`"(100"` 表示 <100。`-inf` 和 `+inf` 表示无穷大。
## 通用命令
| 命令 | 返回值 | 复杂度 | 说明 |
|------|--------|--------|------|
| `DEL key [key ...]` | deleted count | O(N) | N = key 的平均大小,大 key 会阻塞 |
| `EXPIRE key seconds` | 1 or 0 | O(1) | 设过期时间(秒) |
| `PEXPIRE key milliseconds` | 1 or 0 | O(1) | 毫秒级过期 |
| `TTL key` | seconds / -1 / -2 | O(1) | 剩余存活秒数 |
| `PTTL key` | milliseconds | O(1) | 剩余存活毫秒 |
| `EXPIREAT key timestamp` | 1 or 0 | O(1) | 按 Unix 时间戳过期 |
| `KEYS pattern` | matching keys | **O(N)** | ⛔ 全库扫描,生产禁用! |
| `SCAN cursor MATCH pattern COUNT n` | cursor, keys | **增量迭代 O(1)** | ✅ 安全的模糊查找 |
| `DBSIZE` | count | O(1) | 当前数据库 key 总数 |
| `SELECT index` | OK | O(1) | 切换数据库(0~15) |
| `FLUSHDB` | OK | O(N) | 清空当前库 |
| `FLUSHALL` | OK | O(N) | 清空所有库 |
| `TYPE key` | string/hash/list... | O(1) | 查看数据类型 |
| `EXISTS key [key ...]` | existing count | O(N) | 判断 key 是否存在,支持批量 |
| `OBJECT ENCODING key` | encoding name | O(1) | 查看底层编码(排查性能利器) |
| `UNLINK key [key ...]` | deleted count | O(N) | 异步删除,不阻塞主线程 ✅ |
| `MOVE key db` | 1 or 0 | O(1) | 移动到其他数据库(集群不支持) |
| `RENAME key newkey` | OK | O(1) | 重命名(不管类型) |
| `RANDOMKEY` | key / nil | O(1) | 随机返回一个 key(集群不支持) |
```go
// 优雅删除大 key——首选 UNLINK(异步删除,不阻塞主线程)
rdb.Unlink(ctx, "large:key1", "large:key2")
// 或者用 SSCAN/HSCAN 分批删除字段
iter := rdb.HScan(ctx, "big:hash", 0, "", 100).Iterator()
for iter.Next(ctx) {
rdb.HDel(ctx, "big:hash", iter.Val()...)
}
```
> [!TIP] DEL vs UNLINK
> `DEL` 是同步删除,大 key(如百万元素的 Set)会阻塞主线程。`UNLINK`(4.0+)在后台线程异步释放内存,**生产环境优先用 UNLINK**。
> [!WARNING] TTL 的陷阱
> - `TTL` 返回 `-1` 表示 key 存在但无过期时间,`-2` 表示 key 不存在
> - Redis 的过期键删除是**惰性删除 + 定期删除**混合策略,不能保证设置 EXPIRE 后立即清理
> - 集群模式下 `MOVE`、`RANDOMKEY`、`FLUSHALL` **不可用**
## SCAN vs KEYS 深度对比
```bash
# ⛔ KEYS — 阻塞整个服务
KEYS user:*
# 100 万 key 中匹配 1000 个 → 主线程阻塞数百毫秒甚至秒级
# ✅ SCAN — 增量迭代,每次 O(1)
SCAN 0 MATCH user:* COUNT 100
# 返回游标 + 一小批匹配 key → 用游标继续,直到游标回到 0
```
```go
// Go 中的 SCAN 用法
iter := rdb.Scan(ctx, 0, "user:prefix:*", 100).Iterator()
for iter.Next(ctx) {
fmt.Println(iter.Val())
}
// 最后检查 iter.Err()
```
> [!WARNING] SCAN 的不确定性
> - 某个 key 可能返回多次(中间被修改或删除)
> - 某些匹配的 key 可能不返回
> - 因此 **不要用 SCAN 做统计计数**,只能用于清理或迁移等容错场景
## 管道 Pipeline vs 事务 Transaction
Redis 协议本身就是 request/response,Pipeline 把多个命令打包一次发送,大幅减少网络往返;而事务通过 `MULTI` / `EXEC` 保证一组命令的**原子执行**。
### Pipeline —— 批量发送,减少 RTT
```go
pipe := rdb.Pipeline()
pipe.Set(ctx, "k1", "v1", 0)
pipe.Set(ctx, "k2", "v2", 0)
pipe.Set(ctx, "k3", "v3", 0)
results, err := pipe.Exec(ctx) // 一次性发送,一次性接收
```
```bash
# CLI 模式
redis-cli --pipeline <<< $'SET k1 v1\nSET k2 v2\nSET k3 v3'
```
> [!TIP] Pipeline 不是银弹
> 单次 batch 控制在 50~200 条为佳。过大反而会增加 RTT 等待时间,且占用服务端协程。
### MULTI / EXEC —— 原子化执行
```go
pipe, cancel := rdb.TxPipeline()
defer cancel()
pipe.Set(ctx, "acc:A", "900", 0)
pipe.Set(ctx, "acc:B", "1100", 0)
_, err := pipe.Exec(ctx) // 全部命令依次执行(不被其他命令打断)
```
```bash
# Redis CLI 中的事务
MULTI
> SET acc:A 900
> SET acc:B 1100
EXEC # 原子提交
```
> [!NOTE] Pipeline vs Transaction 对比
> | 维度 | Pipeline | Transaction (MULTI/EXEC) |
> |------|----------|------------------------|
> | **目的** | 减少网络往返(性能) | 保证原子性(一致性) |
> | **命令失败处理** | 单个失败不影响其他命令 | `EXEC` 前语法错误会取消整个事务 |
> | **WATCH 支持** | 不支持 | 支持乐观锁 (`WATCH key`) |
> | **适用场景** | 批量写入、批量查询 | 转账、库存扣减等需要原子性的操作 |
> [!WARNING] Redis 事务不是"数据库事务"
> Redis 事务只保证**不中断**(没有回滚),中间执行的命令如果出错,后续命令仍会继续。如果需要"出错就回滚"的行为,请使用 Lua 脚本。
## Pub/Sub — 发布订阅
Pub/Sub 是 Redis 内置的消息广播模型:发布者向 channel 发消息,所有订阅者**实时**收到。注意它是 **fire-and-forget**——不持久化、不确认、离线消息会丢失。
| 命令 | 返回值 | 复杂度 | 说明 |
|------|--------|--------|------|
| `SUBSCRIBE channel [channel ...]` | messages | O(N) | 订阅一个或多个 channel |
| `UNSUBSCRIBE channel [channel ...]` | OK | O(N) | 取消订阅 |
| `PUBLISH channel message` | receivers count | O(N+M) | 发布消息,N=channel 订阅者数,M=pattern 匹配数 |
| `PSUBSCRIBE pattern [pattern ...]` | messages | O(N) | 按 glob 模式订阅(如 `news.*`) |
| `PUNSUBSCRIBE pattern [pattern ...]` | OK | O(N) | 取消模式订阅 |
| `PUBSUB CHANNELS [pattern]` | channels | O(N) | 列出活跃 channel |
| `PUBSUB NUMSUB [channel ...]` | channel-count pairs | O(N) | 查询各 channel 订阅者数量 |
```go
// 订阅端——监听消息
sub := rdb.Subscribe(ctx, "order:events")
ch := sub.Channel()
for msg := range ch {
fmt.Printf("channel=%s payload=%s\n", msg.Channel, msg.Payload)
}
// 发布端——广播事件
receivers, _ := rdb.Publish(ctx, "order:events", `{"orderId":"1001","status":"paid"}`).Result()
fmt.Printf("delivered to %d subscribers\n", receivers)
```
> [!QUESTION] Pub/Sub vs Stream,怎么选?
> - **Pub/Sub**:实时广播,不关心历史,用完即丢(适合通知、缓存失效广播)
> - **Stream**:持久化消息队列,支持消费组、ACK、回溯(适合业务事件、任务队列)
> - 简单规则:**需要"至少一次"保证就用 Stream,否则 Pub/Sub 足够**
## Geo — 地理位置
Redis 3.2+ 引入的地理位置类型,底层用 **ZSet** 实现(GeoHash 编码为 score),支持范围查询和距离计算。
| 命令 | 返回值 | 复杂度 | 说明 |
|------|--------|--------|------|
| `GEOADD key longitude latitude member [lon lat member ...]` | added count | O(log N) | 添加地理位置 |
| `GEOPOS key member [member ...]` | coordinates | O(N) | 获取坐标 |
| `GEODIST key member1 member2 [m\|km\|ft\|mi]` | distance | O(1) | 两点间距离 |
| `GEORADIUS key lon lat radius m\|km\|ft\|mi [WITHCOORD] [WITHDIST] [ASC\|DESC] [COUNT n]` | members | O(N+log N) | 以坐标为中心搜索 ⚠️ 6.2+ 已废弃,用 GEOSEARCH |
| `GEORADIUSBYMEMBER key member radius m\|km\|ft\|mi` | members | O(N+log N) | 以成员为中心搜索 ⚠️ 6.2+ 已废弃 |
| `GEOSEARCH key FROMLONLAT lon lat BYRADIUS radius m\|km [ASC\|DESC] [COUNT n] [WITHCOORD] [WITHDIST]` | members | O(N+log N) | ✅ 6.2+ 推荐,统一搜索命令 |
| `GEOSEARCH key FROMMEMBER member BYRADIUS radius m\|km` | members | O(N+log N) | 以成员为中心搜索 |
| `GEOSEARCHSTORE dst src ...` | stored count | O(N+log N) | 搜索结果存入新 key(6.2+) |
| `GEOHASH key member [member ...]` | geohash strings | O(N) | 返回 GeoHash 编码字符串 |
```go
// 门店入库——添加地理位置
rdb.GeoAdd(ctx, "stores:shanghai",
&redis.GeoLocation{Name: "store-001", Longitude: 121.4737, Latitude: 31.2304},
&redis.GeoLocation{Name: "store-002", Longitude: 121.4897, Latitude: 31.2397},
)
// 附近 3km 的门店——GEOSEARCH
stores, _ := rdb.GeoSearchLocation(ctx, "stores:shanghai", &redis.GeoSearchLocationQuery{
GeoSearchQuery: redis.GeoSearchQuery{
Longitude: 121.4800,
Latitude: 31.2350,
Radius: 3,
RadiusUnit: "km",
Sort: "ASC",
Count: 10,
},
WithCoord: true,
WithDist: true,
}).Result()
```
> [!TIP] GEO 底层就是 ZSet
> 可以用 `ZRANGE`、`ZREM` 等 ZSet 命令操作 Geo key。`GEODIST` 计算的是球面距离(Haversine 公式),精度在 ~0.5% 以内。
## Lua 脚本速查
Redis 2.6+ 内置 Lua 解释器,脚本在服务端**原子执行**,是实现 CAS、限流器、排行榜原子操作的利器。
| 命令 | 返回值 | 说明 |
|------|--------|------|
| `EVAL "script" numkeys key [key ...] arg [arg ...]` | script result | 执行 Lua 脚本 |
| `EVALSHA sha1 numkeys key [key ...] arg [arg ...]` | script result | 按 SHA1 执行(脚本已缓存) |
| `SCRIPT LOAD "script"` | SHA1 | 缓存脚本,返回 SHA1 |
| `SCRIPT EXISTS sha1 [sha1 ...]` | 1 or 0 array | 检查脚本是否已缓存 |
| `SCRIPT FLUSH` | OK | 清空脚本缓存 |
| `SCRIPT KILL` | OK | 终止正在执行的脚本(只限无写操作时) |
```go
// 原子扣减库存——Lua 实现 check-and-set
script := redis.NewScript(`
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock and stock >= tonumber(ARGV[1]) then
redis.call('DECRBY', KEYS[1], ARGV[1])
return 1
end
return 0
`)
result, _ := script.Run(ctx, rdb, []string{"stock:sku:1001"}, 1).Int()
// result == 1 表示扣减成功,0 表示库存不足
```
> [!WARNING] Lua 脚本的限制
> - 脚本执行期间**阻塞整个 Redis**,严禁写耗时逻辑(循环上限建议 < 5000 次迭代)
> - 脚本中不能访问外部网络或文件系统
> - 集群模式下所有 KEYS 必须在同一个 slot(用 `{hash_tag}` 保证)
> - `SCRIPT KILL` 只能终止**未执行写操作**的脚本;已写数据的脚本只能等它跑完或重启 Redis
> [!NOTE] EVAL vs EVALSHA
> `EVAL` 每次发送完整脚本源码,`EVALSHA` 只发送 40 字节的 SHA1 摘要。高并发场景**务必用 EVALSHA + SCRIPT LOAD**,减少网络开销。go-redis 的 `NewScript` 会自动处理 EVALSHA 失败后回退 EVAL。
## 关联笔记
- [[hhs/Redis/02-核心数据类型]] — 每种类型的底层编码原理
- [[hhs/Redis/05-AOF持久化]] — 持久化策略对性能的影响
- [[hhs/Redis/09-高级特性]] — Pipeline / Lua / 事务详解