Skip to content
Redis 数据类型完整参考

Redis 数据类型完整参考

数据类型选型速查表

需求场景 推荐类型 备选方案 选型理由
简单键值缓存 String 最通用,单 key 单 value
缓存整个对象(JSON) String(序列化) Hash 整体读写用 String,部分字段更新用 Hash
用户信息/对象属性 Hash String(JSON) 按字段读写,节省网络开销
消息队列 List(BRPOP/LPUSH) Stream(5.0+) 简单场景用 List,需消费组/确认用 Stream
最新动态/时间线 List(LPUSH + LTRIM) ZSet 简单时间序用 List,需按权重/时间范围查用 ZSet
排行榜/积分排名 ZSet 唯一支持按 score 排序的类型
去重集合/标签系统 Set 无序去重,支持交并差集
点赞/共同好友 Set 交集/并集/差集运算
附近的人/LBS GEO 底层 ZSet,支持半径/距离查询
用户签到/活跃统计 Bitmap String 极省内存,位运算批量统计
UV 统计/大数据去重估算 HyperLogLog 12KB 统计上亿数据,误差 0.81%
发布/订阅 Pub/Sub(不在本文档范围) Stream 广播用 Pub/Sub,持久化/回溯用 Stream
分布式锁 String(SET NX PX) SETNX + 过期时间原子操作
计数器/限流 String(INCR) 原子自增,天然线程安全
延迟队列 ZSet(score=时间戳) Stream 简单场景用 ZSet,复杂场景用 Stream

各类型时间复杂度速查表

命令类别 String Hash List Set ZSet GEO Bitmap HyperLogLog
单元素读 O(1) O(1) O(1) (LINDEX: O(N)) O(1) O(1) O(log N) O(1)
单元素写 O(1) O(1) O(1) O(1) O(log N) O(log N) O(1)
全量获取 O(N) O(N) O(S+N) LRANGE O(N) SMEMBERS O(log N + M) ZRANGE O(N) O(N)
批量操作 O(N) MGET/MSET O(N) HMGET/HMSET
集合运算 O(N*M) 最差 O(NK)+O(Mlog M) O(N)
添加元素 O(N) 指定位置 O(log N) 每次 O(1) 每个
统计数量 O(1) O(1) O(1) 区间 O(log N) O(1)
N = 元素数量, M = 返回元素数量, K = 参与运算的集合数量

各类型内部编码总结表

数据类型 内部编码 触发条件 优势 劣势
String int 值是整数,能用 long 表示 O(1) INCR/DECR,内存极省 仅限整数
embstr 长度 <= 44 字节 一次内存分配,缓存友好 只读,修改即转 raw
raw 长度 > 44 字节 支持任意长度 两次内存分配
Hash listpack (7.0+) 元素 < 512 个,且每个 value < 64 字节 连续内存,省内存 查询 O(N)
hashtable 超过阈值 查询 O(1) 内存开销大
List quicklist (3.2+) 所有情况 linkedlist + listpack 混合
Set intset 全整数且元素 < 512 个 连续内存,省内存 查询 O(log N)
hashtable 超过阈值或有非整数 查询 O(1) 内存开销大
ZSet listpack (7.0+) 元素 < 128 个,且每个 member < 64 字节 连续内存,省内存 查询 O(N)
skiplist + dict 超过阈值 查询 O(log N),有序性 内存开销大
GEO zset(skiplist+dict) 所有情况 继承 ZSet 操作 精度有限
Bitmap raw(String) 所有情况 最大 2^32 bits 底层是 String
HyperLogLog 稀疏/稠密 自动切换 固定 12KB 误差 0.81%

一、String(字符串)

1.1 概述

String 是 Redis 最基础的类型,一个 key 对应一个 value。value 最大 512MB。虽然是"字符串",但底层的 SDS(Simple Dynamic String)使其能存储文本、整数、浮点数、二进制数据(图片、序列化对象等)。

1.2 内部编码

编码 条件 结构 说明
int value 是整数,且在 long 范围内(64 位有符号) 直接存整数 内存极省,INCR/DECR 是 O(1)
embstr value 长度 <= 44 字节(Redis 5.0+;3.2 以前是 39) 一次内存分配,SDS 与 RedisObject 连续 只读优化;任何修改都会转为 raw
raw value 长度 > 44 字节 两次内存分配,SDS 与 RedisObject 分离 支持 append 等动态扩展

编码验证命令:

OBJECT ENCODING key

🔬 深入原理:SDS(Simple Dynamic String)

Redis 不直接使用 C 语言的 char*,而是封装了 SDS:

  • O(1) 获取长度:len 字段已存,无需 strlen
  • 杜绝缓冲区溢出:自动检查并扩容
  • 减少内存重分配:预分配(len < 1MB 时翻倍;>=1MB 时每次加 1MB)+ 惰性释放
  • 二进制安全:不依赖 \0 结尾,可存任意二进制数据
  • 详细原理见 10-底层数据结构.md

1.3 基本读写命令

SET — 设置键值

语法:

SET key value [NX | XX] [GET] [EX seconds | PX milliseconds |
    EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL]

参数说明:

参数 含义
NX 仅当 key 不存在时设置(Not eXists)
XX 仅当 key 已存在时设置(eXists)
GET 返回旧值(6.2+),SET 仍会执行
EX seconds 过期时间(秒)
PX milliseconds 过期时间(毫秒)
EXAT timestamp Unix 时间戳过期(秒,6.2+)
PXAT timestamp Unix 时间戳过期(毫秒,6.2+)
KEEPTTL 保留原有 TTL(6.0+)

示例:

# 基本设置
SET username "alice"
# → OK

# 带过期时间(10秒后过期)
SET session:token "abc123" EX 10
# → OK

# 仅当不存在时设置(分布式锁场景)
SET lock:order:1001 "locked" NX EX 30
# → OK(成功获取锁)
SET lock:order:1001 "locked" NX EX 30
# → (nil)(锁已被持有)

# 获取旧值并设置新值(6.2+)
SET counter 100 GET
# → (nil)(旧值不存在)
SET counter 200 GET
# → "100"(返回旧值)

GET — 获取值

GET key
GET username
# → "alice"

GET nonexistent
# → (nil)

GETSET — 设置新值并返回旧值(已废弃,推荐 SET … GET)

GETSET key value
SET visits 10
GETSET visits 20
# → "10"(旧值)
GET visits
# → "20"

💡 最佳实践: Redis 6.2+ 推荐使用 SET key value GET 代替 GETSET。

GETDEL — 获取值并删除键(6.2+)

GETDEL key
SET temp "data"
GETDEL temp
# → "data"
GET temp
# → (nil)

GETEX — 获取值并设置过期时间(6.2+)

GETEX key [EX seconds | PX milliseconds |
    EXAT unix-time-seconds | PXAT unix-time-milliseconds | PERSIST]
GETEX session:token EX 3600
# → "abc123"(同时续期 1 小时)

GETRANGE — 获取子串

GETRANGE key start end
  • 索引从 0 开始,-1 表示最后一个字符
  • start/end 可越界,自动截断
SET message "Hello World"
GETRANGE message 0 4
# → "Hello"
GETRANGE message -5 -1
# → "World"

SETRANGE — 覆盖子串

SETRANGE key offset value
  • 如果 offset 超出原长度,中间用 \x00 填充
  • 返回修改后的字符串长度
SET message "Hello World"
SETRANGE message 6 "Redis"
# → (integer) 11
GET message
# → "Hello Redis"

STRLEN — 获取字符串长度

STRLEN key
SET name "Alice"
STRLEN name
# → (integer) 5

APPEND — 追加内容

APPEND key value
  • 返回追加后的总长度
  • 如果 key 不存在,等同于 SET
SET log "Error: "
APPEND log "connection timeout"
# → (integer) 25
GET log
# → "Error: connection timeout"

1.4 批量操作命令

MSET — 批量设置

MSET key1 value1 [key2 value2 ...]
  • 原子操作,全部成功或全部不做(但非事务性回滚)
  • 返回 OK
MSET name "Alice" age "30" city "Beijing"
# → OK

MGET — 批量获取

MGET key1 [key2 key3 ...]
  • 返回数组,不存在的 key 返回 nil
  • 比 N 次 GET 少 N-1 次网络往返
MGET name age city nonexistent
# → 1) "Alice"
# → 2) "30"
# → 3) "Beijing"
# → 4) (nil)

性能提示: 批量获取时永远优先使用 MGET 而不是多次 GET,大幅减少 RTT(往返时间)。

MSETNX — 批量原子设置

MSETNX key1 value1 [key2 value2 ...]
  • 原子性:仅当所有 key 都不存在时才设置
  • 返回 1(成功)或 0(失败,全部不设置)
MSETNX user:1:name "Alice" user:1:email "a@b.com"
# → (integer) 1  (全部创建成功)

MSETNX user:1:name "Bob" user:2:name "Charlie"
# → (integer) 0  (user:1:name 已存在,全部不设置)

1.5 计数器命令

INCR / DECR — 自增/自减 1

INCR key
DECR key
  • 如果 key 不存在,默认从 0 开始
  • 值必须是整数(或能解析为整数),否则报错
  • 返回操作后的值
SET page:views 100
INCR page:views
# → (integer) 101
DECR page:views
# → (integer) 100

# key 不存在
INCR new:counter
# → (integer) 1

INCRBY / DECRBY — 自增/自减指定值

INCRBY key increment
DECRBY key decrement
INCRBY score 10
# → (integer) 10
INCRBY score -5
# → (integer) 5
DECRBY score 3
# → (integer) 2

INCRBYFLOAT — 浮点数自增

INCRBYFLOAT key increment
  • 支持科学计数法
  • 不保证精确精度(IEEE 754 浮点数)
SET price "19.99"
INCRBYFLOAT price 0.01
# → "20.00"

SET scientific "1.0e3"
INCRBYFLOAT scientific 1.0
# → "1001"

🚨 陷阱: INCRBYFLOAT 返回的是字符串,不是整数。多次操作后有浮点精度累积误差,金融场景请用整数(分)代替小数。

1.6 SETNX 与 SETEX

SETNX key value          # 仅当不存在时设置(SET key value NX 等效)
SETEX key seconds value  # 设置值并指定过期秒数(SET key value EX seconds 等效)

💡 最佳实践: Redis 2.6.12+ 推荐直接用 SETNX/EX 选项组合,而非分开使用 SETNX + EXPIRE(两个命令非原子,可能因宕机导致死锁)。

1.7 LCS — 最长公共子序列(7.0+)

LCS key1 key2 [LEN] [IDX] [MINMATCHLEN minlen] [WITHMATCHLEN]

参数说明:

参数 含义
LEN 仅返回 LCS 长度
IDX 返回每个匹配在原始字符串中的位置(索引)
MINMATCHLEN minlen 仅报告长度 >= minlen 的匹配
WITHMATCHLEN 返回每个匹配的长度
SET str1 "ABCDEFG"
SET str2 "ACDEGH"
LCS str1 str2
# → "ACDEG"
LCS str1 str2 LEN
# → (integer) 5

1.8 实战场景

场景 命令组合 说明
缓存 SET key value EX ttl 最常用:缓存数据库查询结果、页面片段
计数器 INCR / INCRBY 文章阅读量、点赞数、API 调用次数
分布式锁 SET lock_key rand_val NX EX 30 原子获取锁+设置超时,释放时用 Lua 验证 value 防误删
Session 共享 SET session:id data EX 1800 多服务器共享用户会话
限流 INCR rate:user:minute + EXPIRE 固定窗口计数器,配合 TTL 自动重置
序列号生成 INCR order:id 分布式唯一 ID 生成
配置存储 SET config:xxx value 动态配置,无需重启
临时数据 SETEX key 300 value 验证码、临时 Token

1.9 常见陷阱

🚨 bigkey 问题: 单个 String 过大(>10KB)会导致网络传输慢、主从同步阻塞。单个 value 最大 512MB,但强烈建议控制在 10KB 以内。

🚨 SETNX 非原子问题: SETNX key value 然后 EXPIRE key seconds 是非原子的,宕机可能导致永不过期。请用 SET key value NX EX seconds

🚨 MSET 不是事务: MSET 全部命令打包发送,但中途如果 Redis 宕机,可能部分已写入。它不保证回滚。

🚨 INCRBYFLOAT 精度: 浮点数运算有精度损失。需要精确小数时,用整数表示最小单位(如金额用分)。

🚨 GETSET 已弃用: 新代码请使用 SET key value GET


二、Hash(哈希)

2.1 概述

Hash 是键值对的映射表(field-value map),适合存储对象(如用户信息、商品属性)。相比将对象序列化为 JSON String,Hash 可以按 field 单独读写,节省网络开销。

2.2 内部编码

编码 条件 结构 说明
listpack(7.0+) / ziplist(6.2 及以前) field-value 对数量 < hash-max-listpack-entries(默认 512),且每个 value 长度 < hash-max-listpack-value(默认 64 字节) 连续内存紧凑数组 k1,v1,k2,v2 交替存放,内存极省
hashtable 超过任一阈值 标准哈希表(dict) O(1) 查询,但内存开销大

配置阈值(redis.conf):

hash-max-listpack-entries 512
hash-max-listpack-value 64

🔬 深入原理:编码转换

当 listpack/ziplist 超阈值时,Redis 自动转为 hashtable。但反过来不会自动降级——即使删到只剩 1 个 field,仍是 hashtable。如需降级,须用 HSET 重新写入或重启加载 RDB。

2.3 基本命令

HSET — 设置 field 的值

HSET key field value [field value ...]
  • Redis 4.0+ 支持多 field-value 对,等同于废弃的 HMSET
HSET user:1001 name "Alice" age "30" email "alice@example.com"
# → (integer) 3

HGET — 获取单个 field

HGET key field
HGET user:1001 name
# → "Alice"
HGET user:1001 nonexistent
# → (nil)

HMGET — 获取多个 field

HMGET key field1 [field2 field3 ...]
HMGET user:1001 name age nonexistent
# → 1) "Alice"
# → 2) "30"
# → 3) (nil)

HGETALL — 获取所有 field-value

HGETALL key
  • 返回 field, value, field, value 交替的数组
  • O(N),N 为 field 数量
HGETALL user:1001
# → 1) "name"
# → 2) "Alice"
# → 3) "age"
# → 4) "30"
# → 5) "email"
# → 6) "alice@example.com"

🚨 陷阱: HGETALL 是 O(N) 操作,大 Hash(数万 field)会阻塞 Redis。大 Hash 请使用 HSCAN 分批迭代。

HKEYS / HVALS — 获取所有 field 名/所有 value

HKEYS key
HVALS key
HKEYS user:1001
# → 1) "name"
# → 2) "age"
# → 3) "email"

HVALS user:1001
# → 1) "Alice"
# → 2) "30"
# → 3) "alice@example.com"

HDEL — 删除 field

HDEL key field1 [field2 field3 ...]
  • 返回成功删除的 field 数量(不包括不存在的 field)
HDEL user:1001 email tmp_field
# → (integer) 1(仅 email 存在并被删除)

HEXISTS — 判断 field 是否存在

HEXISTS key field
HEXISTS user:1001 name
# → (integer) 1
HEXISTS user:1001 phone
# → (integer) 0

HLEN — 获取 field 数量

HLEN key
HLEN user:1001
# → (integer) 3

HSTRLEN — 获取 field 值长度

HSTRLEN key field
HSTRLEN user:1001 name
# → (integer) 5

HSETNX — 仅当 field 不存在时设置

HSETNX key field value
HSETNX user:1001 phone "123456"
# → (integer) 1(设置成功)
HSETNX user:1001 phone "654321"
# → (integer) 0(已存在,不设置)

HINCRBY / HINCRBYFLOAT — 数值自增

HINCRBY key field increment
HINCRBYFLOAT key field increment
HSET product:1001 stock 50
HINCRBY product:1001 stock -3
# → (integer) 47

HSET product:1001 price "19.99"
HINCRBYFLOAT product:1001 price 1.50
# → "21.49"

HRANDFIELD — 随机获取 field(6.2+)

HRANDFIELD key [count [WITHVALUES]]
  • count 为正:返回 count 个不重复的 field
  • count 为负:返回 |count| 个可能重复的 field
  • WITHVALUES:同时返回 field 和 value
HRANDFIELD user:1001 1
# → 1) "age"

HRANDFIELD user:1001 -3 WITHVALUES
# → 1) "name"
# → 2) "Alice"
# → 3) "email"
# → 4) "alice@example.com"
# → 5) "age"
# → 6) "30"

HSCAN — 增量迭代

HSCAN key cursor [MATCH pattern] [COUNT count]
HSCAN user:1001 0 MATCH "n*" COUNT 10
# → 1) "0"           (下一次迭代的游标,0 表示结束)
# → 2) 1) "name"
# →    2) "Alice"

💡 最佳实践: 生产环境遍历大 Hash 请务必使用 HSCAN,避免 HGETALL/HKEYS/HVALS 阻塞。

2.4 实战场景

场景 说明 示例
用户信息 一个用户一个 Hash,field 为属性名 HSET user:1001 name "Alice" age 30
购物车 用户 ID 为 key,商品 ID 为 field,数量为 value HSET cart:user1 sku:1001 2
商品属性 一个商品一个 Hash HSET item:5001 title "Book" price "29.99"
计数器组 多维度统计用 Hash 的 HINCRBY HINCRBY stats:daily views 1
配置项 一个模块一个 Hash HSET config:cache ttl 300 max_size 1000
去重判断 用 field 做快速存在性检查 HEXISTS blacklist:ip 10.0.0.1

2.5 常见陷阱

🚨 大 Hash 全量获取阻塞: HGETALL/HKEYS/HVALS 在大 Hash 上会阻塞主线程。使用 HSCAN 分批获取,或拆分为多个小 key。

🚨 编码不自动降级: listpack 转 hashtable 后,即使删减数据也不会回退,内存开销长期偏高。

🚨 field 同名问题: Hash 的 field 没有 schema 约束,注意同一 key 内 field 命名一致性,避免 nameName 并存。

🚨 过期只针对 key: EXPIRE user:1001 3600 会让整个 Hash 过期,不能给单个 field 设 TTL。如需字段级过期,用 String 独立存储。


三、List(列表)

3.1 概述

List 是有序的字符串链表,按插入顺序排序。可从头部(left)或尾部(right)操作。底层用 quicklist 实现,结合了链表和紧凑数组的优点。

3.2 内部编码

Redis 3.2+ 统一使用 quicklist

版本 编码 说明
< 3.2 linkedlist 双向链表,每个节点存一个元素,内存开销大
< 3.2 ziplist 小 List 用压缩列表存所有元素(阈值同 Hash)
>= 3.2 quicklist linkedlist + ziplist/listpack 混合:链表节点存一片连续数据

🔬 深入原理:quicklist

quicklist 是一个 linkedlist,每个节点是一个 ziplist/listpack(多个元素压缩在一起):

  • 减少指针开销:每个链表节点存多个元素,减少 next/prev 指针比例
  • 减少内存碎片:连续内存块
  • 可配置 list-max-listpack-size 控制每个 quicklist 节点的最大大小
  • list-compress-depth 控制中间节点是否压缩(LZF),省内存但多 CPU

3.3 基本命令

LPUSH / RPUSH — 从头部/尾部插入

LPUSH key element1 [element2 element3 ...]
RPUSH key element1 [element2 element3 ...]
  • 返回操作后 List 的长度
  • 支持一次插入多个元素(2.4+,按从左到右顺序插入头部/尾部)
LPUSH tasks "task1" "task2"
# → (integer) 2
LRANGE tasks 0 -1
# → 1) "task2"   (最后 push 的在最前面)
# → 2) "task1"

RPUSH tasks "task3" "task4"
# → (integer) 4
LRANGE tasks 0 -1
# → 1) "task2"
# → 2) "task1"
# → 3) "task3"
# → 4) "task4"

LPOP / RPOP — 从头部/尾部弹出

LPOP key [count]
RPOP key [count]
  • Redis 6.2+ 支持 count 参数,一次弹出多个元素
  • count 默认 1
  • List 为空时返回 (nil)
LPOP tasks
# → "task2"

LPOP tasks 3
# → 1) "task1"
# → 2) "task3"
# → 3) "task4"

LRANGE — 获取范围内的元素

LRANGE key start stop
  • 索引从 0 开始,-1 表示最后一个
  • O(S+N):S 为 start 到头部/尾部的距离(取小值),N 为返回元素数
RPUSH colors red green blue yellow
LRANGE colors 0 2
# → 1) "red"
# → 2) "green"
# → 3) "blue"

LRANGE colors -2 -1
# → 1) "blue"
# → 2) "yellow"

🚨 陷阱: LRANGE 在大 List 上性能较差,尤其是 LRANGE key 0 -1 获取全部元素。用 LTRIM 限制 List 长度,或用 Stream 替代大消息队列。

LTRIM — 修剪列表

LTRIM key start stop
  • 只保留 [start, stop] 范围内的元素,其余删除
RPUSH queue a b c d e
LTRIM queue 0 2
LRANGE queue 0 -1
# → 1) "a"
# → 2) "b"
# → 3) "c"

💡 最佳实践: LPUSH list item + LTRIM list 0 999 组合维持 List 最多 1000 个元素,实现固定大小的最新列表。

LLEN — 获取长度

LLEN key
LLEN queue
# → (integer) 3

LINDEX — 按索引获取元素

LINDEX key index
  • O(N),N 为到头部或尾部较近的距离
  • 负数索引从尾部算
LINDEX colors 1
# → "green"
LINDEX colors -1
# → "yellow"

LINSERT — 在指定元素前/后插入

LINSERT key BEFORE|AFTER pivot element
  • O(N),需要扫描找到 pivot
  • 如果 pivot 不存在,返回 -1
RPUSH letters a b d
LINSERT letters BEFORE d c
# → (integer) 4
LRANGE letters 0 -1
# → 1) "a"  2) "b"  3) "c"  4) "d"

LSET — 按索引替换元素

LSET key index element
  • 索引越界报错
LSET letters 2 "X"
# → OK

LREM — 删除指定元素

LREM key count element
  • count > 0:从左到右删除最多 count 个匹配元素
  • count < 0:从右到左删除最多 |count| 个匹配元素
  • count = 0:删除所有匹配元素
  • 返回实际删除的数量
RPUSH dup a b a a c a
LREM dup 2 a
# → (integer) 2  (从左删除前两个 a)
LREM dup -1 a
# → (integer) 1  (从右删除一个 a)

LPOS — 查找元素位置(6.0.6+)

LPOS key element [RANK rank] [COUNT num-matches] [MAXLEN len]
  • RANK:指定第 N 个匹配项(1 为第一个,-1 为最后一个)
  • COUNT:返回最多 num-matches 个位置
  • MAXLEN:最多扫描 len 个元素
RPUSH lst a b c d a e a
LPOS lst a
# → (integer) 0

LPOS lst a RANK 2
# → (integer) 4

LPOS lst a COUNT 3
# → 1) (integer) 0
# → 2) (integer) 4
# → 3) (integer) 6

3.4 阻塞命令

BLPOP / BRPOP — 阻塞式弹出

BLPOP key1 [key2 key3 ...] timeout
BRPOP key1 [key2 key3 ...] timeout
  • 如果所有 key 都为空,阻塞等待直到有元素可用或超时
  • timeout 为 0 表示永久阻塞
  • 返回 key + element 数组,超时返回 (nil)
  • 支持多个 key:按顺序检查,第一个有数据的 key 被弹出
# 终端 1:阻塞等待
BLPOP queue 0
# (阻塞中...)

# 终端 2:插入数据
LPUSH queue "new_task"

# 终端 1 返回:
# → 1) "queue"
# → 2) "new_task"
# → (27.5s)  (阻塞时长)

💡 最佳实践: BLPOP/BRPOP 是实现消息队列(消费者-生产者)的经典模式。多个消费者阻塞在同一个 key 上时,Redis 会依次分发,实现负载均衡。

BLPOP/BRPOP 多 Key 处理顺序

BLPOP key1 key2 0

先检查 key1,如果为空再检查 key2。只有 key1 为空时才弹出 key2。如果两个都有数据,只弹出 key1。

3.5 移动命令(6.2+)

LMOVE / BLMOVE — 原子移动元素

LMOVE source destination LEFT|RIGHT LEFT|RIGHT
BLMOVE source destination LEFT|RIGHT LEFT|RIGHT timeout
  • 从 source 弹出一个元素,push 到 destination
  • 6.2+ 引入,替代 RPOPLPUSH/BRPOPLPUSH(已弃用)
RPUSH source a b c
LMOVE source dest RIGHT LEFT
# → "c"(从 source 右弹出,push 到 dest 左)
LRANGE source 0 -1
# → 1) "a"  2) "b"
LRANGE dest 0 -1
# → 1) "c"

LMPOP — 批量弹出(7.0+)

LMPOP numkeys key1 [key2 ...] LEFT|RIGHT [COUNT count]
  • 从第一个非空 List 中弹出 count 个元素
  • 返回 key + elements[] 数组
RPUSH lst1 a b c
LMPOP 2 lst1 lst2 LEFT COUNT 2
# → 1) "lst1"
# → 2) 1) "a"
# →    2) "b"

3.6 常用组合模式

模式 命令组合 说明
栈(Stack) LPUSH + LPOP 后进先出(LIFO)
队列(Queue) LPUSH + RPOP(或 RPUSH + LPOP 先进先出(FIFO)
阻塞消息队列 LPUSH/RPUSH + BRPOP/BLPOP 消费者阻塞等待消息
固定大小最新列表 LPUSH + LTRIM 维持固定长度,淘汰旧数据
可靠队列 RPOPLPUSH / LMOVE 弹出后放入备份队列,防止处理失败丢失
# 栈:后进先出
LPUSH stack item1 item2
LPOP stack  # → item2

# 队列:先进先出
RPUSH queue item1 item2
LPOP queue  # → item1

# 固定大小最新 100 条
LPUSH news "article1"
LTRIM news 0 99

# 可靠队列模式(原子备份)
LMOVE queue processing RIGHT LEFT   # 从 queue 右侧弹出,放入 processing 左侧
# ... 处理成功后再从 processing 删除
LREM processing -1 "task_done"

3.7 实战场景

场景 模式 说明
消息队列 LPUSH + BRPOP 多个消费者阻塞等待,实现简单 MQ
最新动态 LPUSH + LTRIM 保留最近 N 条记录(朋友圈、最新评论)
时间线 LPUSH + LRANGE 按时间倒序排列
工作队列 RPOPLPUSH / LMOVE 弹出并备份,处理失败可恢复
分页列表 LRANGE key start end 简单的基于偏移量的分页

3.8 常见陷阱

🚨 大 List O(N) 操作: LINDEX、LINSERT、LSET、LREM 都是 O(N)。大 List 频繁使用这些命令会严重阻塞。

🚨 LRANGE 全量获取: LRANGE mylist 0 -1 在 10 万级别元素时可能造成明显延迟。如果需要遍历大 List,考虑 Stream 或分批 LRANGE。

🚨 List 没有去重: 与 Set 不同,List 允许重复元素。如果需要去重,请用 Set。

🚨 BLPOP 客户端异常断开: 客户端阻塞等待时如果断开,Redis 会自动释放该连接,不会造成永久阻塞。

🚨 RPOPLPUSH 已弃用: Redis 6.2+ 请使用 LMOVE 替代 RPOPLPUSH/BRPOPLPUSH。


四、Set(集合)

4.1 概述

Set 是无序的字符串集合,不允许重复元素。支持高效的添加、删除、存在性判断,以及交集、并集、差集运算。底层使用哈希表实现,O(1) 的增删查。

4.2 内部编码

编码 条件 结构 说明
intset 元素全为整数,且数量 < set-max-intset-entries(默认 512) 有序整数数组 内存连续紧凑,查询 O(log N)
hashtable 超过阈值或有非整数元素 哈希表(dict) O(1) 查询,value 全为 NULL

配置阈值(redis.conf):

set-max-intset-entries 512

🔬 深入原理:intset 编码转换

intset 是升序排列的整数数组,支持 int16int32int64 三种内部编码,自动升级但不会降级。插入非整数或超过 512 个元素时升级为 hashtable。

4.3 基本命令

SADD — 添加元素

SADD key member1 [member2 member3 ...]
  • 返回成功添加的元素个数(已存在的忽略不计)
SADD tags:article:1 "redis" "database" "nosql"
# → (integer) 3

SADD tags:article:1 "redis" "cache"
# → (integer) 1  ("redis" 已存在,只添加了 "cache")

SREM — 删除元素

SREM key member1 [member2 member3 ...]
  • 返回成功删除的个数
SREM tags:article:1 "nosql" "mysql"
# → (integer) 1  ("mysql" 本就不存在)

SCARD — 获取集合大小

SCARD key
SCARD tags:article:1
# → (integer) 3

SISMEMBER — 判断元素是否存在

SISMEMBER key member
SISMEMBER tags:article:1 "redis"
# → (integer) 1
SISMEMBER tags:article:1 "mongodb"
# → (integer) 0

SMEMBERS — 获取全部成员

SMEMBERS key
  • O(N),N 为集合大小
  • 返回无序排列
SMEMBERS tags:article:1
# → 1) "database"
# → 2) "redis"
# → 3) "cache"

🚨 陷阱: SMEMBERS 是 O(N) 操作,百万级 Set 会严重阻塞。大 Set 请用 SSCAN 分批迭代。

SRANDMEMBER — 随机获取元素

SRANDMEMBER key [count]
  • count 为正:返回 count 个不重复的随机元素
  • count 为负:返回 |count| 个可能重复的随机元素
  • 不删除元素
SRANDMEMBER tags:article:1 2
# → 1) "cache"
# → 2) "redis"

SPOP — 随机弹出元素

SPOP key [count]
  • 随机删除并返回 count 个元素
  • count 默认 1
SADD lottery user1 user2 user3
SPOP lottery
# → "user2"
SPOP lottery 2
# → 1) "user3"
# → 2) "user1"

SMOVE — 移动元素到另一个集合

SMOVE source destination member
  • 原子操作:从 source 删除,添加到 destination
  • 如果 member 不在 source 中,返回 0
  • 如果 member 已在 destination 中,仅从 source 删除
SADD set1 a b
SADD set2 c
SMOVE set1 set2 a
# → (integer) 1
SMEMBERS set1
# → 1) "b"
SMEMBERS set2
# → 1) "a"  2) "c"

SSCAN — 增量迭代

SSCAN key cursor [MATCH pattern] [COUNT count]
SSCAN tags:article:1 0 MATCH "r*"
# → 1) "0"
# → 2) 1) "redis"

4.4 集合运算

差集 SDIFF / SDIFFSTORE

SDIFF key1 [key2 key3 ...]            # 返回差集(key1 有,其他 key 没有的)
SDIFFSTORE destination key1 [key2 ...]  # 将差集存入 destination
  • O(N),N 为所有集合元素总数
SADD setA 1 2 3 4
SADD setB 3 4 5 6
SDIFF setA setB
# → 1) "1"
# → 2) "2"

SDIFF setB setA
# → 1) "5"
# → 2) "6"

交集 SINTER / SINTERSTORE / SINTERCARD

SINTER key1 [key2 key3 ...]                        # 返回交集
SINTERSTORE destination key1 [key2 key3 ...]         # 交集存入 destination
SINTERCARD numkeys key1 [key2 ...] [LIMIT limit]     # 返回交集元素数量(7.0+)
SINTER setA setB
# → 1) "3"
# → 2) "4"

# 7.0+:仅计数量(更快,不传输数据)
SINTERCARD 2 setA setB
# → (integer) 2

SINTERCARD 2 setA setB LIMIT 1
# → (integer) 1  (最多统计到 1 就停止)

性能提示: 多个 Set 做 SINTER 时,Redis 会按元素数量升序处理。将最小集合放在参数列表最前面可获得最佳性能。

并集 SUNION / SUNIONSTORE

SUNION key1 [key2 key3 ...]
SUNIONSTORE destination key1 [key2 ...]
SUNION setA setB
# → 1) "1"  2) "2"  3) "3"  4) "4"  5) "5"  6) "6"

4.5 集合运算复杂度速查

运算 复杂度 说明
SDIFF O(N) N 是所有集合元素总数
SINTER O(N*M) N 是最小集合大小,M 是集合数量
SUNION O(N) N 是所有集合元素总数
SINTERCARD O(N*M) 同 SINTER,但只返回计数,不含数据传输

4.6 实战场景

场景 命令 说明
标签系统 SADD/SREM 文章、商品、用户打标签
点赞/收藏去重 SADD + SISMEMBER 防止重复点赞,快速检查是否已赞
共同关注/好友 SINTER 取两个人的关注集合交集
可能认识的人 SDIFF A 的关注 - B 的关注 = B 认识但 A 不认识的
黑名单/白名单 SISMEMBER 快速判断 IP/用户是否在名单中
抽奖/随机 SRANDMEMBER / SPOP 随机抽取获奖用户
用户画像/标签集合 SUNION 合并多个标签维度的用户
独立访客统计 SADD + SCARD 每日 UV 去重(数据量大时用 HyperLogLog)

4.7 常见陷阱

🚨 SMEMBERS 阻塞: 大集合做 SMEMBERS 会把所有数据传输到客户端,阻塞 Redis。使用 SSCAN 分批遍历。

🚨 SINTER 内存开销: 对两个大集合做 SINTER,Redis 需要把最小集合的全部元素加载到内存中的哈希表。集合越大,内存峰值越高。

🚨 intset 编码不降级: 删除元素后 intset 不会自动降级为更小的整数类型(如 int64 不会降为 int32)。

🚨 集合运算复杂度: SDIFF/SINTER/SUNION 的复杂度是 O(N),大集合运算可能耗时很长(秒级)。对时间敏感的场景,考虑将计算结果用 SINTERSTORE/SDIFFSTORE 缓存。

🚨 Set 无序: 不能按索引获取元素,也不能保证迭代顺序一致。如需有序,用 ZSet 或 List。


五、Sorted Set(有序集合)

5.1 概述

Sorted Set(ZSet)是带分数的有序集合,每个 member 关联一个 score(double 类型的浮点数),按 score 从小到大排序。score 可以重复,member 必须唯一。底层使用 skiplist(跳表)+ dict(字典)实现,兼顾 O(log N) 的排序和 O(1) 的查找。

5.2 内部编码

编码 条件 结构 说明
listpack(7.0+)/ ziplist(6.2 及以前) 元素数量 < zset-max-listpack-entries(默认 128),且每个 member 长度 < zset-max-listpack-value(默认 64 字节) 连续内存紧凑数组 member1, score1, member2, score2…
skiplist + dict 超过阈值 跳表 + 哈希表 双数据结构,skiplist 负责排序,dict 负责 O(1) 查找 score

配置阈值(redis.conf):

zset-max-listpack-entries 128
zset-max-listpack-value 64

🔬 深入原理:skiplist + dict 双数据结构

ZSet 同时维护 skiplist 和 dict:

  • skiplist(跳表):按 score 排序,支持范围查询、排名计算,O(log N)
  • dict(字典):member → score 的映射,O(1) 查找某个 member 的 score
  • 两个结构共享 member 字符串(指针复用),不会双倍内存

5.3 基本命令

ZADD — 添加/更新元素

ZADD key [NX | XX] [GT | LT] [CH] [INCR] score member [score member ...]

参数说明:

参数 含义
NX 仅添加新元素,不更新已存在的
XX 仅更新已存在的元素,不添加新元素
GT 仅在新 score > 旧 score 时更新(Greater Than)
LT 仅在新 score < 旧 score 时更新(Less Than)
CH 返回变更数量(新增 + 更新),默认只返回新增数
INCR 对 score 做增量(相当于 ZINCRBY),此时只能操作一个 member
# 基本添加
ZADD leaderboard 100 "alice" 200 "bob" 150 "charlie"
# → (integer) 3

# 仅当不存在时添加
ZADD leaderboard NX 300 "bob"
# → (integer) 0  (bob 已存在)

# 仅当新分数更大时更新
ZADD leaderboard GT 250 "alice"
# → (integer) 0  (250 > 100,更新了,但默认不统计更新)
ZADD leaderboard GT CH 180 "charlie"
# → (integer) 1  (使用 CH 统计更新)
ZADD leaderboard GT CH 50 "alice"
# → (integer) 0  (50 < 250,GT 阻止了更新)

# INCR 模式
ZADD leaderboard INCR 10 "alice"
# → "260"(返回新 score)

ZREM — 删除元素

ZREM key member1 [member2 member3 ...]
  • 返回实际删除的数量(不存在的忽略)
ZREM leaderboard "charlie" "nonexistent"
# → (integer) 1

ZSCORE — 获取分数

ZSCORE key member
ZSCORE leaderboard "alice"
# → "260"

ZCARD — 获取元素数量

ZCARD key
ZCARD leaderboard
# → (integer) 2

ZRANK / ZREVRANK — 获取排名

ZRANK key member           # 升序排名(0 开始,score 最小的为 0)
ZREVRANK key member        # 降序排名(0 开始,score 最大的为 0)
ZADD scores 80 "A" 90 "B" 70 "C"
ZRANK scores "A"
# → (integer) 1   (70<80<90,A 排第二,索引 1)
ZREVRANK scores "A"
# → (integer) 1   (90>80>70,A 排倒数第二,索引 1)

ZCOUNT — 统计分数范围内元素数

ZCOUNT key min max
  • min/max 默认闭区间,( 前缀表示开区间
  • -inf+inf 表示无穷
ZADD temps 20 "Beijing" 30 "Shanghai" 15 "Harbin" 35 "Sanya"
ZCOUNT temps 20 30
# → (integer) 2   (Beijing 20, Shanghai 30)
ZCOUNT temps (20 35
# → (integer) 2   (Shanghai 30, Sanya 35,不含 20)
ZCOUNT temps -inf 25
# → (integer) 2   (Harbin 15, Beijing 20)

ZINCRBY — 增加分数

ZINCRBY key increment member
  • 返回新 score(字符串格式)
  • member 不存在时,等同于 ZADD key increment member
ZINCRBY leaderboard 50 "alice"
# → "310"

5.4 范围查询命令

ZRANGE — 按索引范围查询

ZRANGE key start stop [BYSCORE | BYLEX] [REV] [LIMIT offset count]
    [WITHSCORES]

参数(6.2+ 统一语法):

参数 含义
BYSCORE 按 score 范围而非索引(替代旧的 ZRANGEBYSCORE)
BYLEX 按字典序范围(替代旧的 ZRANGEBYLEX)
REV 逆序(替代 ZREVRANGE / ZREVRANGEBYSCORE)
LIMIT offset count 类似 SQL LIMIT
WITHSCORES 同时返回 score
ZADD rank 100 "A" 200 "B" 150 "C" 300 "D" 250 "E"

# 按索引(默认升序)
ZRANGE rank 0 2 WITHSCORES
# → 1) "A"  2) "100"
# → 3) "C"  4) "150"
# → 5) "B"  6) "200"

# 按索引逆序
ZRANGE rank 0 2 REV WITHSCORES
# → 1) "D"  2) "300"
# → 3) "E"  4) "250"
# → 5) "B"  6) "200"

# 按 score 范围
ZRANGE rank 150 250 BYSCORE WITHSCORES
# → 1) "C"  2) "150"
# → 3) "B"  4) "200"
# → 5) "E"  6) "250"

# 按 score 范围 + LIMIT
ZRANGE rank 100 300 BYSCORE LIMIT 1 2 WITHSCORES
# → 1) "C"  2) "150"
# → 3) "B"  4) "200"

# 按 score 范围 + 逆序
ZRANGE rank 300 100 BYSCORE REV WITHSCORES
# → 1) "D"  2) "300"
# → 3) "E"  4) "250"
# → 5) "B"  6) "200"
# → 7) "A"  8) "100"

💡 最佳实践: Redis 6.2+ 的统一 ZRANGE 替代了旧的 ZRANGEBYSCOREZREVRANGEZREVRANGEBYSCOREZRANGEBYLEXZREVRANGEBYLEX。新代码请使用统一语法。

旧版范围查询命令(6.2 以前)

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]   # 按分数范围
ZREVRANGE key start stop [WITHSCORES]                          # 逆序按索引
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count] # 逆序按分数范围

ZRANGESTORE — 存储范围查询结果(6.2+)

ZRANGESTORE dst src min max [BYSCORE | BYLEX] [REV] [LIMIT offset count]
ZRANGESTORE top3 rank 0 2 WITHSCORES
# 结果存入 top3,但 WITHSCORES 不作用于 STORE(返回分数会被忽略)
ZRANGESTORE top_by_score rank 150 250 BYSCORE

ZSCAN — 增量迭代

ZSCAN key cursor [MATCH pattern] [COUNT count]
ZSCAN rank 0
# → 1) "0"
# → 2) 1) "A"  2) "100"
# →    3) "C"  4) "150"
# →    ...

5.5 删除命令

ZREMRANGEBYRANK — 按排名删除

ZREMRANGEBYRANK key start stop
ZADD z 10 a 20 b 30 c 40 d 50 e
ZREMRANGEBYRANK z 0 2
# → (integer) 3  (删除 a,b,c)

ZREMRANGEBYSCORE — 按分数删除

ZREMRANGEBYSCORE key min max
ZREMRANGEBYSCORE z 20 40
# → (integer) 2  (删除 b 20, c 30 - 注意上面已经删了 a, b, c,这只是示意)

5.6 弹出命令(5.0+)

ZPOPMIN / ZPOPMAX — 弹出最小/最大 score 的元素

ZPOPMIN key [count]    # 弹出 score 最小的 count 个
ZPOPMAX key [count]    # 弹出 score 最大的 count 个
ZADD pq 1 "low" 5 "mid" 10 "high"
ZPOPMIN pq 2
# → 1) "low"
# → 2) "1"
# → 3) "mid"
# → 4) "5"

ZPOPMAX pq 1
# → 1) "high"
# → 2) "10"

BZPOPMIN / BZPOPMAX — 阻塞式弹出(5.0+)

BZPOPMIN key1 [key2 key3 ...] timeout
BZPOPMAX key1 [key2 key3 ...] timeout
  • 阻塞直到有元素可供弹出或超时
  • timeout 为 0 表示永久阻塞
  • 返回 key + member + score 三元素数组
BZPOPMIN delay_queue 0
# (阻塞中...)

# 另一个客户端:
ZADD delay_queue 1600000000 "task1"

# 返回:
# → 1) "delay_queue"
# → 2) "task1"
# → 3) "1600000000"

5.7 多集合运算(ZDIFF / ZINTER / ZUNION)

ZDIFF — 差集(6.2+)

ZDIFF numkeys key1 [key2 ...] [WITHSCORES]
ZDIFFSTORE destination numkeys key1 [key2 ...]
ZADD z1 10 a 20 b 30 c
ZADD z2 20 b 30 c
ZDIFF 2 z1 z2 WITHSCORES
# → 1) "a"
# → 2) "10"

ZINTER — 交集

ZINTER numkeys key1 [key2 ...] [WEIGHTS weight1 [weight2 ...]]
    [AGGREGATE SUM|MIN|MAX] [WITHSCORES]
ZINTERSTORE destination numkeys key1 [key2 ...]
    [WEIGHTS weight1 [weight2 ...]] [AGGREGATE SUM|MIN|MAX]

参数说明:

参数 含义
WEIGHTS 每个集合 score 的乘数,默认全为 1
AGGREGATE SUM score 求和(默认)
AGGREGATE MIN 取最小 score
AGGREGATE MAX 取最大 score
ZADD z1 10 a 20 b 30 c
ZADD z2 1 a 2 b 5 c

# 交集 score 求和(默认)
ZINTER 2 z1 z2 WITHSCORES
# → 1) "a"  2) "11"
# → 3) "b"  4) "22"
# → 5) "c"  6) "35"

# 带权重(z1 * 2 + z2 * 3)
ZINTER 2 z1 z2 WEIGHTS 2 3 WITHSCORES
# → 1) "a"  2) "23"   (10*2 + 1*3)
# → 3) "b"  4) "46"   (20*2 + 2*3)
# → 5) "c"  6) "75"   (30*2 + 5*3)

# 取最大值
ZINTER 2 z1 z2 AGGREGATE MAX WITHSCORES
# → 1) "a"  2) "10"
# → 3) "b"  4) "20"
# → 5) "c"  6) "30"

ZUNION — 并集

ZUNION numkeys key1 [key2 ...] [WEIGHTS weight1 [weight2 ...]]
    [AGGREGATE SUM|MIN|MAX] [WITHSCORES]
ZUNIONSTORE destination numkeys key1 [key2 ...]
    [WEIGHTS weight1 [weight2 ...]] [AGGREGATE SUM|MIN|MAX]
ZADD z1 10 a 20 b
ZADD z2 1 a 5 c

ZUNION 2 z1 z2 WITHSCORES
# → 1) "b"  2) "20"  (z2 中不存在,保持原 score)
# → 3) "c"  4) "5"   (z1 中不存在,保持原 score)
# → 5) "a"  6) "11"  (10 + 1 = 11)

ZUNION 2 z1 z2 AGGREGATE MAX WITHSCORES
# → 1) "a"  2) "10"
# → 3) "b"  4) "20"
# → 5) "c"  6) "5"

🔬 深入原理:AGGREGATE 的工作方式

  • SUM(默认)result_score = weight1 * score1 + weight2 * score2 + ...
  • MINresult_score = min(weight1*score1, weight2*score2, ...)
  • MAXresult_score = max(weight1*score1, weight2*score2, ...)

如果某个 member 只在部分集合中出现,非参与的集合 score 视为 0(对 MIN 则是 +inf)。

5.8 实战场景

场景 命令组合 说明
排行榜 ZADD + ZRANGE … REV WITHSCORES score 为分数/热力值,逆序取 TOP N
延迟队列 ZADD + BZPOPMIN(或定时 ZRANGE) score 为 Unix 时间戳,到期弹出处理
带权重的队列 ZADD + ZPOPMIN score 为优先级(越小越优先)
时间线排序 ZADD + ZRANGE BYSCORE score 为时间戳,按时间范围过滤
分页查询 ZRANGE … LIMIT offset count 类似 SQL 的分页排序
多维排序 ZINTER + WEIGHTS 不同维度加权求和后排序(如:热度 = 阅读1 + 点赞2 + 评论*3)
滑动窗口限流 ZADD + ZREMRANGEBYSCORE + ZCARD score 为时间戳,每次请求清理窗口外数据后计数

延迟队列示例

# score = Unix 时间戳(未来某个时间点执行)
ZADD delay_queue 1700000000 "send_email:1001"
ZADD delay_queue 1700000100 "send_sms:2001"

# 消费者:循环弹出到期的任务
# 方式 1:BZPOPMIN 取 score 最小的(最早到期的)
BZPOPMIN delay_queue 0

# 方式 2:定时扫描(可用 Lua 保证原子性)
ZRANGE delay_queue 0 0 BYSCORE -inf <当前时间戳> LIMIT 0 1

滑动窗口限流示例

# Lua 脚本原子操作:
# 1. 清理窗口外的旧记录
# 2. 统计当前窗口内的请求数
# 3. 判断是否超过阈值

-- eval 脚本示例
local key = KEYS[1]          -- rate:user:1001
local window = ARGV[1]       -- 60local limit = ARGV[2]        -- 100local now = redis.call('TIME')[1]  -- 秒级时间戳

redis.call('ZREMRANGEBYSCORE', key, 0, now - window)  -- 清理过期
local count = redis.call('ZCARD', key)
if count < tonumber(limit) then
    redis.call('ZADD', key, now, now .. ':' .. math.random())
    redis.call('EXPIRE', key, window)  -- 整个 key 在窗口结束后过期
    return 1  -- 放行
else
    return 0  -- 限流
end

5.9 常见陷阱

🚨 skiplist + dict 内存开销: ZSet 维护双数据结构,内存占用比 Set 高。少量元素用 listpack 编码可大幅节省。

🚨 ZRANGE 全量返回: 大 ZSet 上用 ZRANGE key 0 -1 会阻塞。使用 LIMIT 分页。

🚨 score 类型是 double: score 是 IEEE 754 双精度浮点,存在精度问题。精确比较时慎用相等(==),改用范围。

🚨 ZINTERSTORE 目标 key 已存在: 会覆盖目标 key 的全部数据。如果目标 key 存有重要数据,确认语法正确。

🚨 ZDIFF/ZINTER/ZUNION 性能: 这些运算可能非常耗时(O(N*K) + O(M*log M))。对于大数据集,优先使用 *STORE 版本缓存结果。

🚨 member 长度影响编码: 长 member(>64 字节)会使 ZSet 从 listpack/ziplist 升级为 skiplist+dict,内存大幅增加。


六、GEO(地理位置)

6.1 概述

GEO 是 Redis 3.2 引入的地理位置数据类型,用于存储经纬度坐标。底层基于 ZSet,将经纬度通过 GeoHash 算法编码为 52 位整数作为 ZSet 的 score,member 即为位置名称。这意味着 GEO 可以使用 ZSet 的全部命令。

6.2 内部编码与底层原理

组件 说明
底层数据结构 ZSet(skiplist + dict)
score GeoHash 编码:经纬度 -> 52 位整数
member 位置名称(如 “Beijing”、“Store:1001”)
编码方式 将经纬度交替编码到 52 位整数(26 位经度 + 26 位纬度),使用 Z-order 曲线

🔬 深入原理:GeoHash 精度与误差

GeoHash 将二维经纬度降为一维编码。52 位编码的精度:

GeoHash 位数 纬度位数 经度位数 纬度误差 经度误差 距离误差
52 bits 26 bits 26 bits ~0.6 米 ~0.6 米 ~0.8 米
  • 原理:二分空间,经度 [-180, 180] 和纬度 [-90, 90] 交替二分
  • 编码越短:范围越大,精度越低;编码越长:范围越小,精度越高
  • 相邻位置的 GeoHash 前缀可能不同(Z-order 曲线边界问题)

💡 为什么基于 ZSet?

  • 免费获得 ZSet 的分页(ZRANGE)、删除(ZREM)、计数(ZCARD)功能
  • GEO 元素可以混用 ZSet 命令:ZREM locations "Store:1" 可以删除 GEO 位置
  • 不要 用 ZADD/ZSCORE 直接设置/读取 GEO score,必须用 GEO 命令

6.3 基本命令

GEOADD — 添加地理位置

GEOADD key [NX | XX] [CH] longitude latitude member [longitude latitude member ...]

参数说明:

参数 含义
NX 仅添加新位置,不更新已存在的(6.2+)
XX 仅更新已存在的位置(6.2+)
CH 返回变更数量(包括更新)
longitude 经度,范围 [-180, 180]
latitude 纬度,范围 [-85.05112878, 85.05112878]
GEOADD cities 116.397 39.908 "Beijing"
# → (integer) 1

GEOADD cities 121.473 31.230 "Shanghai" 113.264 23.129 "Guangzhou"
# → (integer) 2

# NX 模式:仅添加不存在的
GEOADD cities NX 116.397 39.908 "Beijing"
# → (integer) 0   (Beijing 已存在,不更新)

🚨 陷阱: 纬度范围是 [-85.05112878, 85.05112878],接近两极的点无法添加(这是 Mercator 投影的限制)。

GEOPOS — 获取经纬度

GEOPOS key member1 [member2 member3 ...]
  • 返回 [longitude, latitude] 数组对
  • 不存在的 member 返回 (nil)
GEOPOS cities Beijing Shanghai nonexistent
# → 1) 1) "116.39700299501419"
# →    2) "39.90799964970965"
# → 2) 1) "121.47300010919571"
# →    2) "31.229999155818104"
# → 3) (nil)

🔬 浮点精度说明: 返回的经纬度与输入值略有差异,因为 GeoHash 编码带来的精度损失(约 0.6 米级别)。

GEODIST — 计算两点距离

GEODIST key member1 member2 [m | km | ft | mi]

单位选项:

单位 含义
m 米(默认)
km 千米
ft 英尺
mi 英里
GEODIST cities Beijing Shanghai km
# → "1067.6087"
GEODIST cities Beijing Shanghai m
# → "1067608.6843"

GEOHASH — 获取 GeoHash 字符串

GEOHASH key member1 [member2 member3 ...]
  • 返回 Base32 编码的 GeoHash 字符串(11 个字符)
  • GeoHash 越长精度越高,可用于客户端自行计算
GEOHASH cities Beijing Shanghai
# → 1) "wx4g0bdrfz0"
# → 2) "wtw3sj5zbj0"

💡 最佳实践: 使用 GEOHASH 可以在应用层快速估算距离。两个 GeoHash 字符串前缀匹配长度越长,两点越近(但有边界效应,不是绝对的)。

6.4 范围查询命令

GEOSEARCH — 通用范围查询(6.2+ 统一命令)

GEOSEARCH key <FROMMEMBER member | FROMLONLAT longitude latitude>
    <BYRADIUS radius m|km|ft|mi | BYBOX width height m|km|ft|mi>
    [ASC | DESC] [COUNT count [ANY]] [WITHCOORD] [WITHDIST] [WITHHASH]

参数说明:

参数 含义
FROMMEMBER member 以某个已存在的位置为圆心
FROMLONLAT lon lat 以指定经纬度为圆心
BYRADIUS radius unit 按半径搜索
BYBOX width height unit 按矩形搜索(宽、高)
ASC / DESC 按距离升序/降序
COUNT count 限制返回数量
ANY 只要找到 count 个就返回(不保证最近的 count 个)
WITHCOORD 返回经纬度
WITHDIST 返回距离
WITHHASH 返回 GeoHash 编码的 52 位整数值
# 以 Beijing 为圆心,半径 500 km
GEOSEARCH cities FROMMEMBER Beijing BYRADIUS 500 km ASC WITHCOORD WITHDIST
# → 1) 1) "Beijing"
# →    2) "0.0000"
# →    3) 1) "116.39700299501419"
# →       2) "39.90799964970965"

# 以给定经纬度为圆心,半径 1000 km
GEOSEARCH cities FROMLONLAT 116.397 39.908 BYRADIUS 1000 km ASC COUNT 2 WITHDIST
# → 1) 1) "Beijing"
# →    2) "0.0001"
# → 2) 1) "Shanghai"
# →    2) "1067.6087"

# 矩形搜索
GEOSEARCH cities FROMMEMBER Beijing BYBOX 2000 2000 km ASC WITHDIST

GEOSEARCHSTORE — 存储搜索结����(6.2+)

GEOSEARCHSTORE destination source <FROMMEMBER member | FROMLONLAT lon lat>
    <BYRADIUS radius unit | BYBOX width height unit>
    [ASC | DESC] [COUNT count [ANY]] [STOREDIST]
  • STOREDIST:将距离作为 score 存入 destination(默认存 GeoHash)
GEOSEARCHSTORE nearby_cities cities FROMMEMBER Beijing BYRADIUS 500 km
# → (integer) 1
ZRANGE nearby_cities 0 -1 WITHSCORES
# → 1) "Beijing"  2) "4069885361241787"  (GeoHash 52 位整数)

# STOREDIST 将距离存为 score
GEOSEARCHSTORE nearby_dist cities FROMMEMBER Beijing BYRADIUS 500 km STOREDIST
ZRANGE nearby_dist 0 -1 WITHSCORES
# → 1) "Beijing"  2) "0"

6.5 旧版命令(6.2 以前)

GEORADIUS — 按经纬度半径搜索

GEORADIUS key longitude latitude radius m|km|ft|mi
    [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC]
    [STORE key] [STOREDIST key]

GEORADIUSBYMEMBER — 按成员半径搜索

GEORADIUSBYMEMBER key member radius m|km|ft|mi
    [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC]
    [STORE key] [STOREDIST key]

GEORADIUS_RO / GEORADIUSBYMEMBER_RO — 只读版本(6.2+)

GEORADIUS_RO key longitude latitude radius m|km|ft|mi
    [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC]
GEORADIUSBYMEMBER_RO key member radius m|km|ft|mi
    [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC]
  • _RO = Read-Only,不支持 STORE/STOREDIST,可在只读副本上执行

💡 最佳实践: Redis 6.2+ 推荐使用统一的 GEOSEARCH/GEOSEARCHSTORE 替代所有旧版 GEORADIUS 命令。

6.6 实战场景

场景 命令 说明
附近的人 GEOSEARCH ... FROMMEMBER user1 BYRADIUS 5 km 以用户位置为圆心,搜索 5km 内的其他用户
外卖配送 GEOSEARCH ... FROMLONLAT ... BYRADIUS 3 km 搜索附近 3km 内的骑手
门店搜索 GEOSEARCH ... FROMLONLAT ... BYRADIUS 10 km ASC COUNT 20 搜索最近 20 家门店
配送范围判断 GEOSEARCH ... FROMMEMBER store BYRADIUS 3 km 判断用户地址是否在配送范围内
距离排序 GEOSEARCH + ASC 按距离由近到远排序
LBS 应用 GEO 全套命令 打车、找房、社交等一切基于位置的场景

6.7 与其他类型互通

GEO 底层是 ZSet,可以混合使用 ZSet 命令:

# GEO 添加
GEOADD locations 116.397 39.908 "A"
GEOADD locations 121.473 31.230 "B"

# ZSet 命令查看
ZRANGE locations 0 -1 WITHSCORES
# → 1) "A"  2) "4069885361241787"
# → 3) "B"  4) "4052022549971571"

# ZSet 命令删除(合法)
ZREM locations "A"
# → (integer) 1

# ❌ 不要用 ZADD 直接写 GEO 数据(score 是 GeoHash 编码,手工计算不可行)

性能提示: 如果需要批量删除 GEO 数据,用 ZREM key member1 member2 ... 比多次 GEOADD ... 重新覆盖更高效(GEO 没有提供删除命令)。

6.8 常见陷阱

🚨 纬度限制: 纬度必须在 [-85.05112878, 85.05112878] 范围内,极地坐标无法存储。

🚨 GeoHash 精度限制: GEOPOS 返回的经纬度有约 0.6 米的误差。不是 GPS 精度的替代方案,只能做近似查询。

🚨 不要直接用 ZSet 命令写 GEO 数据: GEO 的 score 是 GeoHash 编码,直接用 ZADD 设置 score 会导致 GEO 命令结果错误。

🚨 大 GEO 集合查询: GEOSEARCH 底层是 ZRANGEBYSCORE + 平面距离计算(Haversine 公式)。在百万级数据中查询 500km 范围可能很慢(需扫描大量 GeoHash 区间)。

🚨 GeoHash 边界效应: 两个位置在 GeoHash 边界两侧时,前缀几乎完全不同,但实际距离很近。这就是 Z-order 曲线的局限性。


七、Bitmap(位图)

7.1 概述

Bitmap 不是独立的数据类型,而是 String 类型的位操作扩展。一个 String 可以看作一个位数组,支持对每一位进行 get/set。最大长度为 2^32 bits = 512 MB,可表示约 42.9 亿个位。

7.2 底层原理

  • Bitmap 底层是 String(SDS),只是把每个字节(8 bits)按位操作
  • offset 0 对应第一个字节的最高位(Redis 内部偏向左移)
  • 最大 offset 为 2^32 - 1

🔬 深入原理:Bitmap 内存效率

数据规模 传统方式 Bitmap 方式
1 亿用户签到标记 1 亿个 key(每个 1bit -> 56+ bytes overhead) 1 个 key ~12.5 MB
1 亿个布尔标记 ~5.6 GB(String overhead) ~12.5 MB
8 亿用户日活 ~44.8 GB ~100 MB
  • 每个位的存储成本是 1 bit,但 SDS 有结构和 padding 开销
  • SETBIT 可设置任意 offset,中间的位自动填充 0

7.3 基本命令

SETBIT — 设置某一位的值

SETBIT key offset value
  • offset 从 0 开始
  • value 为 0 或 1
  • 返回旧值(0 或 1)
  • offset 超出当前长度时,自动扩展并填充 0
SETBIT sign:user:1001 0 1
# → (integer) 0
SETBIT sign:user:1001 6 1
# → (integer) 0
SETBIT sign:user:1001 6 0
# → (integer) 1

GETBIT — 获取某一位的值

GETBIT key offset
GETBIT sign:user:1001 0
# → (integer) 1
GETBIT sign:user:1001 1
# → (integer) 0

BITCOUNT — 统计值为 1 的位数

BITCOUNT key [start end [BYTE | BIT]]
  • start/end:按字节索引(默认),支持负数
  • 7.0+ 支持 BIT 选项:按索引
  • O(N),N 为统计范围的字节数
SETBIT counter 0 1
SETBIT counter 10 1
SETBIT counter 100 1
BITCOUNT counter
# → (integer) 3

# 按字节范围统计
BITCOUNT counter 0 0
# → (integer) 1  (第 0 字节:偏移 0-7 中只有 offset 0 为 1)

# 按位范围统计(7.0+)
BITCOUNT counter 0 10 BIT
# → (integer) 2  (offset 0-10 中有 offset 0 和 10 为 1)

BITPOS — 查找第一个 0 或 1 的位置

BITPOS key bit [start [end [BYTE | BIT]]]
  • bit:0 或 1
  • 返回第一个匹配的 offset(从 0 开始)
  • 找不到返回 -1
  • start/end 默认按字节索引
SETBIT bitmap 10 1
SETBIT bitmap 20 1
BITPOS bitmap 1
# → (integer) 10   (第一个 1 在 offset 10)
BITPOS bitmap 0
# → (integer) 0    (第一个 0 在 offset 0)
BITPOS bitmap 1 2 2
# → (integer) -1   (第 2-2 字节范围内没有 1)

7.4 BITFIELD — 任意位宽整数读写(3.2+)

BITFIELD 将 Bitmap 看作任意位宽的整数数组,支持对指定偏移量的整数读写。

BITFIELD key [GET type offset] [SET type offset value]
    [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]

type 格式: [u|i]<bits>,如 u8(无符号 8 位)、i16(有符号 16 位)

类型 范围 说明
u1 ~ u63 0 ~ 2^n - 1 无符号整数
i1 ~ i64 -2^(n-1) ~ 2^(n-1) - 1 有符号整数

OVERFLOW 溢出策略:

策略 含义
WRAP 环绕(默认):u8 的 255 + 1 = 0, i8 的 127 + 1 = -128
SAT 截断饱和:超出最大值则停在最大值,超出最小值停在最小值
FAIL 溢出时不执行操作,返回 nil
# SET: 在 offset 0 处设置一个 8 位无符号整数
BITFIELD user:stats SET u8 0 100
# → 1) (integer) 0   (旧值)

# GET: 读取
BITFIELD user:stats GET u8 0
# → 1) (integer) 100

# INCRBY: 自增
BITFIELD user:stats INCRBY u8 0 1
# → 1) (integer) 101

# 多条操作组合(原子执行)
BITFIELD user:stats SET u8 #0 100 INCRBY u8 #1 50 GET u8 #1
# → 1) (integer) 0    (SET 旧值)
# → 2) (integer) 50   (INCRBY 结果,key 不存在认为 0)
# → 3) (integer) 50   (GET 结果)

# 溢出控制
BITFIELD key OVERFLOW SAT INCRBY u8 0 1   # 使用 SAT 策略
# → 如果当前是 255,则仍返回 255(不溢出)

💡 按位偏移 vs 整数字段偏移:

  • # 前缀:按整数字段索引,如 u8 #0 是第 0 个 u8(offset 0),u8 #1 是第 1 个 u8(offset 8)
  • 不带 #:直接指定位偏移量,如 u8 0(offset 0),u8 8(offset 8)

BITFIELD_RO — 只读版 BITFIELD(6.0+)

BITFIELD_RO key GET type offset [GET type offset ...]
  • 仅支持 GET,不能在只读副本上报错
  • 适合在 slave / read-only 副本上查询

7.5 BITOP — 位运算

BITOP operation destkey key1 [key2 key3 ...]
操作 含义
AND 按位与
OR 按位或
XOR 按位异或
NOT 按位取反(只能跟一个源 key)
  • O(N),N 为最长 key 的长度
  • 返回 destkey 的长度(字节数)
SETBIT a 0 1
SETBIT a 3 1
SETBIT b 0 1
SETBIT b 1 1

BITOP AND result a b
BITCOUNT result
# → (integer) 1   (只有 offset 0 同时为 1)

BITOP OR result a b
BITCOUNT result
# → (integer) 3   (offset 0, 1, 3 为 1)

7.6 实战场景

场景 命令 说明
用户签到 SETBIT + BITCOUNT 用户 ID 映射到 offset,day of year 做 key
活跃用户统计 BITOP AND/OR + BITCOUNT 多天签到做 AND 得到连续活跃用户
布隆过滤器(简化版) SETBIT + GETBIT 多个哈希函数映射到不同 offset
权限位 SETBIT + GETBIT 每位代表一个权限开关
在线状态 SETBIT + BITCOUNT 每位代表一个用户的在线状态
Bloom Filter(计数位图) BITFIELD INCRBY 多位计数器(如 u3 表示 0-7 次)
用户画像标签 SETBIT + BITOP AND/OR 多标签取交集/并集

签到统计连续天数示例

# 假设:今天是 day 200,用户 ID=1001
# 用户 ID 作为 offset,每天一个 Bitmap key

# 记录签到
SETBIT sign:200 1001 1   # 第 200 天签到
SETBIT sign:199 1001 1   # 第 199 天签到
SETBIT sign:198 1001 1   # 第 198 天签到

# 统计用户本月签到天数(day 170-199)
BITOP OR monthly sign:170 sign:171 ... sign:199
BITCOUNT monthly

# 统计连续 3 天都签到的用户(取交集)
BITOP AND consecutive:3 sign:198 sign:199 sign:200
BITCOUNT consecutive:3

# 统计某用户连续签到天数(应用层逻辑):
# 从今天往前逐天 GETBIT,遇到 0 停止

布隆过滤器简化实现

# 使用 3 个哈希函数映射到同一个 Bitmap
SETBIT bloom:users <hash1(uid)> 1
SETBIT bloom:users <hash2(uid)> 1
SETBIT bloom:users <hash3(uid)> 1

# 检查是否存在
GETBIT bloom:users <hash1(uid)>   # 都为 1 → 可能存在
GETBIT bloom:users <hash2(uid)>   # 任一为 0 → 一定不存在
GETBIT bloom:users <hash3(uid)>

7.7 常见陷阱

🚨 Bitmap 不是独立类型: Bitmap 底层是 String,可以用 GET/STRLEN 查看。SETBIT 设置高位 offset 时,整个 String 被扩展到相应长度,中间填充 \x00

🚨 BITCOUNT 的 start/end 是字节索引: 不是位索引。BITCOUNT key 0 0 统计的是第 1 个字节(offset 0-7),而非第 1 个位。7.0+ 可用 BIT 选项按位索引。

🚨 SETBIT offset 极大造成的扩容: SETBIT mykey 1000000000 1 会创建约 125 MB 的字符串(内存分配阻塞)。

🚨 BITFIELD 溢出默认 WRAP: u8 的 255 + 1 = 0。需要饱和溢出用 OVERFLOW SAT

🚨 BITOP 时间复杂度 O(N): 两个大 Bitmap 做 BITOP 需要遍历全部字节,耗时与最长 key 的长度成正比。


八、HyperLogLog(基数统计)

8.1 概述

HyperLogLog(HLL)是一种概率性数据结构,用于估算集合的基数(不重复元素的数量)。其核心优势是:无论存储多少元素,单个 key 只需约 12 KB 内存,标准误差仅 0.81%。

8.2 底层原理

特性
内存占用(单个 key) ~12 KB(固定)
标准误差 0.81%(即 99.19% 置信区间)
内部桶数 16384 个(2^14)
每个桶位数 6 bits
算法 HyperLogLog(基于 Philippe Flajolet 论文)

🔬 深入原理:HyperLogLog 工作流程

  1. 哈希:对每个元素计算 64 位哈希值
  2. 分桶:取哈希值的低 14 位,确定放入哪个桶(0~16383)
  3. 记录前导零:取哈希值的高 50 位,计算前导零长度 + 1,存入该桶(取最大值)
  4. 估算:对所有桶使用调和平均数公式计算基数近似值

为什么是 16384 个桶?

  • 桶越多,精度越高,但内存越多
  • 16384 桶 * 6 bits/桶 = 98304 bits = 12288 bytes ~ 12 KB
  • 这是精度(0.81%)和内存的最佳平衡点

稀疏 vs 稠密编码:

  • 稀疏编码:元素少时(HLL 内部计数器多为 0),用稀疏结构省内存
  • 稠密编码:元素多时,自动切换为标准 16384 桶的 6-bit 存储
  • 转换阈值由 hll-sparse-max-bytes 控制(默认 3000 字节)

8.3 命令

HLL 只有 3 个命令,API 极其简洁。

PFADD — 添加元素

PFADD key element1 [element2 element3 ...]
  • 返回 1 表示内部寄存器被修改(基数增加),返回 0 表示无变化
  • 注意:返回 0 不代表元素已存在,只是估算值未变(概率性)
PFADD visitors "user:1" "user:2" "user:3"
# → (integer) 1   (基数发生了变化)
PFADD visitors "user:1"
# → (integer) 0   (估算值未变)

PFCOUNT — 估算基数

PFCOUNT key1 [key2 key3 ...]
  • 单个 key:返回该 key 的基数估算值
  • 多个 key:返回所有 key 的并集基数估算值(等于临时 PFMERGE)
  • 基数较小时,使用 HLL 的稀疏编码,结果非常精确
PFCOUNT visitors
# → (integer) 3

PFADD visitors2 "user:3" "user:4" "user:5"
PFCOUNT visitors visitors2
# → (integer) 5   (并集:user:1,2,3,4,5 = 5 个)

PFMERGE — 合并多个 HLL

PFMERGE destkey sourcekey1 [sourcekey2 sourcekey3 ...]
  • 将多个 HLL 合并为一个,结果存入 destkey
  • 可用于计算多日 UV 的去重总和
PFADD day1 "user:1" "user:2" "user:3"
PFADD day2 "user:2" "user:3" "user:4"
PFMERGE week1 day1 day2
PFCOUNT week1
# → (integer) 4   (user:1,2,3,4 去重后 4 个)

8.4 实战场景

场景 命令 说明
UV 统计 PFADD + PFCOUNT 页面/PV 独立访客统计
每日/每周/每月 UV PFMERGE 每日 HLL 合并为周/月 HLL
独立 IP 统计 PFADD + PFCOUNT API 调用来源 IP 去重
大数据去重估算 PFADD + PFCOUNT 海量数据中估算不重复元素数
广告曝光去重 PFADD 广告独立观众数
搜索关键词 UV PFADD + PFCOUNT 每个关键词的独立搜索用户数

UV 统计完整示例

# 统计每个页面每天的独立访客
PFADD uv:page:home:20240101 "user:1" "user:2"
PFADD uv:page:home:20240102 "user:2" "user:3"

# 单日 UV
PFCOUNT uv:page:home:20240101
# → 2

# 合并为两天去重 UV
PFMERGE uv:page:home:2days uv:page:home:20240101 uv:page:home:20240102
PFCOUNT uv:page:home:2days
# → 3   (user:1, user:2, user:3)

# 多天可以直接传给 PFCOUNT(不持久化合并结果)
PFCOUNT uv:page:home:20240101 uv:page:home:20240102
# → 3

8.5 使用限制与精度

数据规模 实际误差表现
< 10000 个元素 几乎没有误差(稀疏编码)
10000 ~ 100 万 标准误差 0.81% 以内
> 1 亿 误差仍保持在 0.81% 左右
极小基数(< 100) 使用线性计数(Linear Counting)修正,精度较高

💡 最佳实践:

  • HLL 适合 百万级及以上 数据的去重统计
  • 小数据量(< 1000)用 Set(SCARD 精确)即可
  • HLL 无法获取成员、删除元素,只能添加和统计
  • 统计"最近 N 天累计 UV"用 PFMERGE 优于多次 PFCOUNT(不用重复计算)

8.6 HLL vs Set vs Bitmap 基数统计对比

维度 HyperLogLog Set Bitmap
内存(1 亿个不同元素) ~12 KB ~5.6 GB ~12.5 MB
精确度 估算,误差 0.81% 精确 精确
能否获取元素 不能 能(SMEMBERS) 不能
能否删除元素 不能 能(SETBIT offset 0)
添加速度 O(1) 极快 O(1) O(1)
统计速度 O(1) 极快 O(1) O(N) BITCOUNT
适合场景 大基数估算 小基数,需精确 用户 ID 用数字、偏移量已知

8.7 常见陷阱

🚨 不能获取元素: HLL 只存概率状态,不存储原始数据。无法列出有哪些元素,无法删除,无法判断某个元素是否已添加。

🚨 误差存在: 0.81% 的标准误差意味着 100 万的实际 UV 可能显示为 99.2 万到 100.8 万。需要精确统计时用 Set(但内存代价大)。

🚨 PFADD 返回 0 不等于元素重复: PFADD 返回 0 只说明基数估算值没变——即使是新元素,如果哈希分布碰巧不改变寄存器值,也会返回 0。

🚨 PFMERGE 会覆盖 destkey: 目标 key 原有数据会被覆盖,不是追加。

🚨 12KB 对小数据过重: 如果只有几百个元素,12KB 显得浪费(稀疏编码阶段会小很多)。元素极多时才体现 HLL 的内存优势。


附录:数据类型完整命令速查

String 命令总表

命令 复杂度 用途
SET O(1) 设置值(支持 NX/XX/GET/EX/PX 等)
GET O(1) 获取值
GETSET O(1) 设置并返回旧值(已弃用,用 SET…GET)
GETDEL O(1) 获取并删除(6.2+)
GETEX O(1) 获取并设置过期(6.2+)
GETRANGE O(N) 获取子串
SETRANGE O(N) 覆写指定位置子串
STRLEN O(1) 获取字符串长度
APPEND O(N) 追加内容
MGET O(N) 批量获取
MSET O(N) 批量设置
MSETNX O(N) 原子批量设置
INCR O(1) 自增 1
DECR O(1) 自减 1
INCRBY O(1) 自增指定值
DECRBY O(1) 自减指定值
INCRBYFLOAT O(1) 浮点自增
SETNX O(1) 不存在时设置
SETEX O(1) 设置并设置过期秒
LCS O(N*M) 最长公共子序列(7.0+)

Hash 命令总表

命令 复杂度 用途
HSET O(1)/O(N) 设置 field-value
HGET O(1) 获取单个 field
HMGET O(N) 获取多个 field
HGETALL O(N) 获取全部 field-value
HKEYS O(N) 获取全部 field
HVALS O(N) 获取全部 value
HDEL O(N) 删除 field
HEXISTS O(1) 判断 field 是否存在
HLEN O(1) 获取 field 数量
HSTRLEN O(1) 获取 field 值长度
HSETNX O(1) 不存在时设置
HINCRBY O(1) 整数自增
HINCRBYFLOAT O(1) 浮点自增
HRANDFIELD O(N) 随机获取 field(6.2+)
HSCAN O(1) 每步 增量迭代

List 命令总表

命令 复杂度 用途
LPUSH / RPUSH O(1)/O(N) 头部/尾部插入
LPOP / RPOP O(1)/O(N) 头部/尾部弹出
BLPOP / BRPOP O(1) 阻塞式弹出
LRANGE O(S+N) 范围查询
LTRIM O(N) 修剪列表
LLEN O(1) 获取长度
LINDEX O(N) 按索引获取
LINSERT O(N) 指定位置插入
LSET O(N) 按索引替换
LREM O(N+M) 删除匹配元素
LPOS O(N) 查找元素位置(6.0.6+)
LMOVE / BLMOVE O(1) 原子移动元素(6.2+)
LMPOP O(N) 批量弹出(7.0+)

Set 命令总表

命令 复杂度 用途
SADD O(1)/O(N) 添加元素
SREM O(1)/O(N) 删除元素
SCARD O(1) 获取集合大小
SISMEMBER O(1) 判断元素是否存在
SMEMBERS O(N) 获取全部元素
SRANDMEMBER O(1)/O(N) 随机获取元素
SPOP O(1)/O(N) 随机弹出元素
SMOVE O(1) 移动元素到另一个集合
SDIFF O(N) 差集
SINTER O(N*M) 交集
SUNION O(N) 并集
SDIFFSTORE O(N) 差集存入目标
SINTERSTORE O(N*M) 交集存入目标
SUNIONSTORE O(N) 并集存入目标
SINTERCARD O(N*M) 交集基数(7.0+)
SSCAN O(1) 每步 增量迭代

ZSet 命令总表

命令 复杂度 用途
ZADD O(log N) 添加/更新元素
ZREM O(M*log N) 删除元素
ZSCORE O(1) 获取分数
ZCARD O(1) 获取元素数量
ZRANK O(log N) 升序排名
ZREVRANK O(log N) 降序排名
ZCOUNT O(log N) 统计分数范围内元素数
ZINCRBY O(log N) 增加分数
ZRANGE O(log N + M) 范围查询(6.2+ 统一语法)
ZRANGESTORE O(log N + M) 存储范围查询结果(6.2+)
ZPOPMIN / ZPOPMAX O(log N*M) 弹出最小/最大(5.0+)
BZPOPMIN / BZPOPMAX O(log N) 阻塞式弹出(5.0+)
ZREMRANGEBYRANK O(log N+M) 按排名删除
ZREMRANGEBYSCORE O(log N+M) 按分数删除
ZDIFF O(N*K)+O(M*log M) 差集(6.2+)
ZINTER O(N*K)+O(M*log M) 交集
ZUNION O(N)+O(M*log M) 并集
ZDIFFSTORE O(N*K)+O(M*log M) 差集存储
ZINTERSTORE O(N*K)+O(M*log M) 交集存储
ZUNIONSTORE O(N)+O(M*log M) 并集存储
ZSCAN O(1) 每步 增量迭代

GEO 命令总表

命令 复杂度 用途
GEOADD O(log N) 添加位置
GEOPOS O(log N) 获取经纬度
GEODIST O(log N) 计算两点距离
GEOHASH O(log N) 获取 GeoHash 字符串
GEOSEARCH O(N+log(M)) 通用范围查询(6.2+)
GEOSEARCHSTORE O(N+log(M)) 存储查询结果(6.2+)
GEORADIUS_RO O(N+log(M)) 只读半径查询

Bitmap 命令总表

命令 复杂度 用途
SETBIT O(1) 设置指定位
GETBIT O(1) 获取指定位
BITCOUNT O(N) 统计 1 的位数
BITPOS O(N) 查找第一个 1 或 0
BITFIELD O(1) 单个子命令 任意位宽整数字段读写
BITFIELD_RO O(1) 单个子命令 只读 BITFIELD(6.0+)
BITOP O(N) 位运算(AND/OR/XOR/NOT)

HyperLogLog 命令总表

命令 复杂度 用途
PFADD O(1) 添加元素
PFCOUNT O(1) / O(N) 估算基数(多 key 时为 O(N))
PFMERGE O(N) 合并多个 HLL

关键命令版本演进一览

版本 新增/变更 类型
1.0 String, Hash, List, Set, ZSet 基本命令 全类型
2.0 SETBIT, GETBIT Bitmap
2.6 BITCOUNT, BITOP, INCRBYFLOAT, HINCRBYFLOAT Bitmap, String, Hash
2.8 BITPOS, SSCAN, HSCAN, ZSCAN, SRANDMEMBER count, ZRANGEBYLEX Bitmap, 迭代, Set, ZSet
3.2 GEO 类型, BITFIELD, quicklist GEO, Bitmap, List
4.0 统一 HSET 语法(废弃 HMSET) Hash
5.0 ZPOPMIN/ZPOPMAX, BZPOPMIN/BZPOPMAX ZSet
6.0 BITFIELD_RO Bitmap
6.0.6 LPOS List
6.2 GETDEL, GETEX, GETRANGE 负索引, HRANDFIELD, LMOVE/BLMOVE, ZRANGE 统一语法, ZDIFF/ZINTER/ZUNION, ZRANGESTORE, GEOSEARCH/GEOSEARCHSTORE, GEORADIUS_RO 全类型
7.0 LCS, LMPOP, SINTERCARD, BITCOUNT BIT String, List, Set, Bitmap