Redis 完全指南 / 04 - 基础数据类型
基础数据类型
Redis 提供五大基础数据类型,它们覆盖了 90% 以上的使用场景。
4.1 数据类型总览
| 类型 | 底层编码 | 典型场景 | 最大容量 |
|---|---|---|---|
| String | int / embstr / raw | 缓存、计数器、分布式锁 | 512 MB |
| List | listpack / quicklist | 消息队列、最新列表 | 2³² - 1 个元素 |
| Hash | listpack / hashtable | 对象存储、购物车 | 2³² - 1 个字段 |
| Set | intset / hashtable | 标签、好友关系、去重 | 2³² - 1 个元素 |
| ZSet | listpack / skiplist+hashtable | 排行榜、延迟队列 | 2³² - 1 个元素 |
4.2 String(字符串)
String 是 Redis 最基础的数据类型,其他所有类型都依赖它。一个 Key 对应一个 Value,Value 可以是字符串、整数或浮点数。
基本操作
# 设置值
SET name "Redis"
SET counter 100
# 获取值
GET name # "Redis"
# 设置并返回旧值
SET name "NewRedis" GET # "Redis"(Redis 6.2+)
# 不存在才设置(分布式锁基础)
SETNX lock:order:123 "owner-uuid" # 1(成功)或 0(已存在)
# 设置值+过期时间(原子操作)
SET session:abc "user-data" EX 3600 # 3600 秒后过期
SET token:xyz "jwt-token" PX 60000 # 60000 毫秒后过期
# 不存在才设置 + 过期时间(分布式锁推荐写法)
SET lock:order:123 "owner-uuid" NX EX 30
# 批量操作
MSET k1 "v1" k2 "v2" k3 "v3"
MGET k1 k2 k3 # ["v1", "v2", "v3"]
# 追加字符串
SET greeting "Hello"
APPEND greeting " World" # "Hello World"
# 获取字符串长度
STRLEN greeting # 11
数值操作
# 自增(原子操作,线程安全)
SET page:views:home 0
INCR page:views:home # 1
INCR page:views:home # 2
INCRBY page:views:home 10 # 12
# 自减
DECR stock:sku001 # 999
DECRBY stock:sku001 5 # 994
# 浮点数自增
SET price:product:1 99.9
INCRBYFLOAT price:product:1 -10.0 # "89.9"
位操作(Bit Operations)
# 设置位
SETBIT user:login:2026-05-10 12345 1 # 用户 12345 今天登录
SETBIT user:login:2026-05-10 12346 1 # 用户 12346 今天登录
# 获取位
GETBIT user:login:2026-05-10 12345 # 1
# 统计位(统计今天有多少用户登录)
BITCOUNT user:login:2026-05-10 # 2
内部编码详解
# 查看 String 的底层编码
SET num 12345
OBJECT ENCODING num # "int"(整数,<= 20位整数)
SET short "hello"
OBJECT ENCODING short # "embstr"(短字符串,<= 44 字节)
SET long "aaaa...(超过44字节)"
OBJECT ENCODING long # "raw"(长字符串)
# embstr 只读,修改后转为 raw
APPEND short " world"
OBJECT ENCODING short # "raw"
| 编码 | 条件 | 内存布局 |
|---|---|---|
int | 值为 <= 20 位的整数 | RedisObject + long |
embstr | 字符串 ≤ 44 字节 | RedisObject + SDS(一次分配) |
raw | 字符串 > 44 字节 | RedisObject + SDS(两次分配) |
💡 技巧:embstr 只需要一次内存分配,性能最优。对于短字符串,尽量保持在 44 字节以内。
String 的典型应用场景
1. 缓存
# 缓存用户信息(JSON 序列化)
SET user:1001 '{"name":"张三","age":25,"city":"北京"}' EX 3600
# 查询
GET user:1001
2. 计数器
# 文章阅读量
INCR article:1001:views
# 获取阅读量
GET article:1001:views
3. 分布式锁
# 加锁
SET lock:order:create "uuid-1234" NX EX 30
# 解锁(Lua 脚本保证原子性)
EVAL "
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
" 1 lock:order:create "uuid-1234"
4. 限流
# 简单限流:每分钟最多 100 次请求
SET rate:limit:user:1001 0 NX EX 60
INCR rate:limit:user:1001
# 如果返回值 > 100 则拒绝请求
4.3 List(列表)
List 是一个有序的字符串列表,底层使用 quicklist(Redis 7.0+ 使用 listpack 作为 quicklist 的节点)。
基本操作
# 从左端推入
LPUSH queue:task "task1" "task2" "task3" # 3
# 从右端推入
RPUSH queue:task "task4" # 4
# 弹出(阻塞版本,适合消费者)
BLPOP queue:task 30 # 等待最多 30 秒弹出一个元素
BRPOP queue:task 30
# 查看列表
LRANGE queue:task 0 -1 # 查看全部元素
LRANGE queue:task 0 2 # 查看前 3 个
LLEN queue:task # 列表长度
# 按索引获取
LINDEX queue:task 0 # 第一个元素
# 修剪列表(只保留指定范围)
LTRIM queue:task 0 99 # 只保留前 100 个元素
# 在指定元素前后插入
LINSERT queue:task BEFORE "task2" "task1.5"
LINSERT queue:task AFTER "task2" "task2.5"
# 移除元素
LREM queue:task 1 "task1" # 移除 1 个值为 "task1" 的元素
LREM queue:task -2 "task1" # 从尾部移除 2 个值为 "task1" 的元素
LREM queue:task 0 "task1" # 移除所有值为 "task1" 的元素
# 移动元素到另一个列表
RPOPLPUSH queue:task queue:processing # 原子地弹出并推入
阻塞弹出详解
# 非阻塞:列表为空时返回 nil
LPOP empty_list # nil
# 阻塞:列表为空时等待
BLPOP empty_list 30 # 阻塞 30 秒,期间如果有新元素则立即返回
# 阻塞多个列表(任一有数据就返回)
BLPOP list1 list2 list3 30
# 无限等待(0 表示不超时)
BLPOP queue:task 0
List 应用场景
1. 消息队列(简单版)
# 生产者
LPUSH mq:order '{"order_id":1001,"action":"create"}'
LPUSH mq:order '{"order_id":1002,"action":"pay"}'
# 消费者(阻塞等待)
BRPOP mq:order 0
2. 最新列表
# 保存最新 10 条动态
LPUSH user:1001:feed '{"content":"新动态","time":"2026-05-10"}'
LTRIM user:1001:feed 0 9 # 只保留最新 10 条
# 获取动态
LRANGE user:1001:feed 0 -1
3. 栈与队列
# 栈(LIFO):LPUSH + LPOP
LPUSH stack "a" "b" "c"
LPOP stack # "c"(后进先出)
# 队列(FIFO):LPUSH + RPOP
LPUSH queue "a" "b" "c"
RPOP queue # "a"(先进先出)
⚠️ 注意:List 作为消息队列不支持消息确认(ACK)机制。如需可靠消息队列,推荐使用 Stream(第 05 章)。
4.4 Hash(哈希)
Hash 是一个键值对集合,适合存储对象。
基本操作
# 设置单个字段
HSET user:1001 name "张三"
HSET user:1001 age 25
HSET user:1001 city "北京"
# 设置多个字段(原子操作)
HSET user:1002 name "李四" age 30 city "上海"
# 获取单个字段
HGET user:1001 name # "张三"
# 获取多个字段
HMGET user:1001 name age # ["张三", "25"]
# 获取全部字段和值
HGETALL user:1001
# 1) "name"
# 2) "张三"
# 3) "age"
# 4) "25"
# 5) "city"
# 6) "北京"
# 字段不存在才设置
HSETNX user:1001 email "zhangsan@example.com" # 1(成功)
# 数值操作
HINCRBY user:1001 age 1 # 26
HINCRBYFLOAT user:1001 score 0.5
# 获取所有字段名 / 值
HKEYS user:1001 # ["name", "age", "city", "email"]
HVALS user:1001 # ["张三", "26", "北京", "zhangsan@example.com"]
# 字段数量
HLEN user:1001 # 4
# 删除字段
HDEL user:1001 email
# 检查字段是否存在
HEXISTS user:1001 name # 1(存在)
HEXISTS user:1001 phone # 0(不存在)
# 增量遍历(大 Hash 推荐)
HSCAN user:1001 0 MATCH "na*" COUNT 10
Hash vs String 存储对象
| 对比维度 | String (JSON) | Hash |
|---|---|---|
| 读取全部字段 | ✅ 一次 GET | ✅ HGETALL |
| 读取单个字段 | ❌ 需反序列化全部 | ✅ HGET(O(1)) |
| 更新单个字段 | ❌ 需读-改-写 | ✅ HSET(O(1)) |
| 部分字段读取 | ❌ 不可能 | ✅ HMGET |
| 内存效率 | 较好(紧凑 JSON) | 略高(有字段名开销) |
| 原子计数 | 需解析后 INCR | ✅ HINCRBY |
💡 技巧:如果只需要读写对象的部分字段,使用 Hash;如果总是读写整个对象,使用 String JSON。
Hash 应用场景
1. 用户信息存储
HSET user:1001 \
name "张三" \
email "zhangsan@example.com" \
avatar "https://cdn.example.com/avatar/1001.jpg" \
level "vip" \
created_at "2026-01-15"
# 只读取需要的字段
HMGET user:1001 name avatar
2. 购物车
# 用户 1001 的购物车
HSET cart:1001 sku:001 2 # 商品 sku:001 数量 2
HSET cart:1001 sku:002 1 # 商品 sku:002 数量 1
# 增加数量
HINCRBY cart:1001 sku:001 1 # sku:001 数量变为 3
# 删除商品
HDEL cart:1001 sku:002
# 查看购物车
HGETALL cart:1001
# 购物车商品种类数
HLEN cart:1001
3. 配置管理
# 存储应用配置
HSET config:app \
max_upload_size "10485760" \
allowed_origins "https://example.com,https://admin.example.com" \
maintenance_mode "false"
# 读取单个配置
HGET config:app maintenance_mode
4.5 Set(集合)
Set 是一个无序、不重复的字符串集合。
基本操作
# 添加元素
SADD tags:article:1001 "redis" "database" "nosql" "cache"
SADD tags:article:1002 "redis" "python" "tutorial"
# 查看所有元素(无序)
SMEMBERS tags:article:1001
# 判断元素是否存在
SISMEMBER tags:article:1001 "redis" # 1(存在)
SISMEMBER tags:article:1001 "mysql" # 0(不存在)
# 批量判断
SMISMEMBER tags:article:1001 "redis" "mysql" "cache" # [1, 0, 1]
# 集合大小
SCARD tags:article:1001 # 4
# 随机获取元素
SRANDMEMBER tags:article:1001 2 # 随机返回 2 个(不删除)
SPOP tags:article:1001 1 # 随机弹出 1 个(删除)
# 删除元素
SREM tags:article:1001 "nosql"
集合操作
SADD user:1001:friends "A" "B" "C" "D"
SADD user:1002:friends "B" "C" "E" "F"
# 交集(共同好友)
SINTER user:1001:friends user:1002:friends
# {"B", "C"}
# 并集(所有好友)
SUNION user:1001:friends user:1002:friends
# {"A", "B", "C", "D", "E", "F"}
# 差集(你有 ta 没有的好友)
SDIFF user:1001:friends user:1002:friends
# {"A", "D"}
# 差集存储结果
SDIFFSTORE result user:1001:friends user:1002:friends
SMEMBERS result # {"A", "D"}
# 交集存储
SINTERSTORE common user:1001:friends user:1002:friends
# 并集存储
SUNIONSTORE all user:1001:friends user:1002:friends
Set 应用场景
1. 标签系统
# 给文章打标签
SADD tag:redis "article:1001" "article:1002" "article:1005"
SADD tag:python "article:1002" "article:1003"
# 同时有 redis 和 python 标签的文章
SINTER tag:redis tag:python # {"article:1002"}
2. 社交关系
# 关注关系
SADD follow:1001 2001 2002 2003 # 1001 关注了这些人
SADD follow:2001 1001 2003 2005 # 2001 关注了这些人
# 互关(共同关注)
SINTER follow:1001 follow:2001 # {2003}
# 我关注的人也关注了谁(推荐关注)
SDIFF follow:2001 follow:1001 # {2005}
3. 去重统计
# 统计独立访客 UV
SADD uv:2026-05-10 "user:1001" "user:1002" "user:1001" # 自动去重
SCARD uv:2026-05-10 # 2
4.6 Sorted Set(有序集合)
Sorted Set(ZSet)是 Redis 最强大的数据类型之一,每个元素关联一个分数(score),按分数排序。
基本操作
# 添加元素(分数,成员)
ZADD leaderboard 100 "Alice"
ZADD leaderboard 85 "Bob"
ZADD leaderboard 92 "Charlie"
ZADD leaderboard 88 "David"
# 批量添加
ZADD leaderboard 95 "Eve" 78 "Frank"
# 按分数从高到低查询(排名)
ZREVRANGE leaderboard 0 -1 WITHSCORES
# 1) "Alice"
# 2) "100"
# 3) "Eve"
# 4) "95"
# 5) "Charlie"
# 6) "92"
# ...
# 按分数从低到高查询
ZRANGE leaderboard 0 -1 WITHSCORES
# 查询 Top 3
ZREVRANGE leaderboard 0 2 WITHSCORES
# 获取分数
ZSCORE leaderboard "Alice" # 100
# 获取排名(从 0 开始,分数最高排名 0)
ZREVRANK leaderboard "Alice" # 0
ZREVRANK leaderboard "Bob" # 4
# 分数自增
ZINCRBY leaderboard 5 "Bob" # Bob 的分数变为 90
# 集合大小
ZCARD leaderboard # 6
# 分数区间计数
ZCOUNT leaderboard 80 100 # 分数在 80-100 之间的元素数量
# 删除元素
ZREM leaderboard "Frank"
# 按排名范围删除
ZREMRANGEBYRANK leaderboard 0 0 # 删除排名最低的
# 按分数范围删除
ZREMRANGEBYSCORE leaderboard 0 70 # 删除分数 0-70 的
分数范围查询
# 分数区间查询
ZRANGEBYSCORE leaderboard 80 100 WITHSCORES # 分数 80-100
ZRANGEBYSCORE leaderboard -inf +inf WITHSCORES # 全部
# 分数区间计数
ZCOUNT leaderboard 80 100 # 4
# 逆序分数区间
ZREVRANGEBYSCORE leaderboard 100 80 WITHSCORES
# 按字典序(score 相同时)
ZADD names 0 "Alice" 0 "Bob" 0 "Charlie" 0 "Dave"
ZRANGEBYLEX names "[A" "[C" # 按字典序查询 A-C
ZRANGEBYLEX names "[B" "+" # B 及之后的
ZSet 内部结构
ZSet 当元素少时(≤ 128 个,且 value ≤ 64 字节):
┌──────────────────┐
│ listpack │ 紧凑存储,内存友好
│ [member|score] │
│ [member|score] │
│ ... │
└──────────────────┘
ZSet 当元素多时:
┌─────────────────┐ ┌───────────────┐
│ skiplist │ │ hashtable │
│ (排序+范围查询) │ │ (O(1) 查找) │
└─────────────────┘ └───────────────┘
双结构设计:skiplist 提供高效的范围查询(O(log N + M)),hashtable 提供 O(1) 的单元素查找。
ZSet 应用场景
1. 排行榜
# 游戏排行榜
ZADD game:rank 15000 "player:1001"
ZADD game:rank 12500 "player:1002"
ZADD game:rank 18000 "player:1003"
# 更新分数
ZINCRBY game:rank 500 "player:1001"
# 查看 Top 10
ZREVRANGE game:rank 0 9 WITHSCORES
# 查看某玩家排名
ZREVRANK game:rank "player:1001"
# 查看某玩家分数
ZSCORE game:rank "player:1001"
# 查看分数在 10000-20000 之间的玩家
ZRANGEBYSCORE game:rank 10000 20000 WITHSCORES
2. 延迟队列
# 任务入队(score 为执行时间戳)
ZADD delay:queue 1715318400 '{"task":"send_email","to":"user@example.com"}'
ZADD delay:queue 1715318460 '{"task":"generate_report","id":123}'
# 消费者轮询:取出到期任务
# 当前时间戳 = 1715318401
ZRANGEBYSCORE delay:queue 0 1715318401 LIMIT 0 10
# 取出并删除(原子操作配合 Lua 脚本)
3. 带权重的标签
# 按热度排序的热搜
ZADD hot:search 1500 "Redis教程"
ZADD hot:search 2300 "Python入门"
ZADD hot:search 800 "Go语言"
# 热搜 Top 10
ZREVRANGE hot:search 0 9 WITHSCORES
# 搜索量 +1
ZINCRBY hot:search 1 "Redis教程"
4.7 类型选择决策树
需要存储什么?
│
├── 单个值/计数器 → String
│
├── 对象(多个字段)
│ ├── 经常读写部分字段 → Hash
│ └── 总是整体读写 → String (JSON)
│
├── 有序列表(可重复)
│ ├── 需要两端操作 → List
│ └── 需要按分数排序 → ZSet
│
├── 不重复的集合
│ ├── 无需排序 → Set
│ └── 需要排序 → ZSet
│
└── 消息队列
├── 简单需求 → List (BRPOP)
└── 需要确认/回溯 → Stream
📌 业务场景
场景一:电商商品详情缓存(String + Hash 混合)
# 基本信息(变动少)→ String JSON
SET product:1001:info '{"name":"iPhone","price":5999}' EX 7200
# 库存和销量(频繁变动)→ Hash
HSET product:1001:stock stock 500 sales 1200
HINCRBY product:1001:stock sales 1
HINCRBY product:1001:stock stock -1
场景二:实时排行榜(ZSet)
# 直播间礼物排行榜
ZADD room:1001:gifts 500 "user_A"
ZINCRBY room:1001:gifts 100 "user_A"
ZREVRANGE room:1001:gifts 0 9 WITHSCORES
场景三:共同兴趣推荐(Set 交集)
# 找到和你有相同兴趣标签的用户
SINTER tag:redis:users tag:python:users