强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Redis 完全指南 / 04 - 基础数据类型

基础数据类型

Redis 提供五大基础数据类型,它们覆盖了 90% 以上的使用场景。

4.1 数据类型总览

类型底层编码典型场景最大容量
Stringint / embstr / raw缓存、计数器、分布式锁512 MB
Listlistpack / quicklist消息队列、最新列表2³² - 1 个元素
Hashlistpack / hashtable对象存储、购物车2³² - 1 个字段
Setintset / hashtable标签、好友关系、去重2³² - 1 个元素
ZSetlistpack / 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

🔗 扩展阅读