redis
1 · redis#
Redis is a data structure server.
Help you solve problems from
cachingtoqueuingtoevent processing.
1.1 · 数据结构总览
Redis 常见数据结构主要有 9 类,其中前 5 个是最核心、最常用的基础结构,后 4 个更偏向解决特定问题的专项结构。
1. 常用核心数据结构
- String:最基础,字节序列。
- List:有序
String序列(按插入顺序),偏双端操作。 - Hash:键值对。
- Set:无序唯一
String集合。 - Sorted Set:
带分值的有序唯一String集合。
2. 专项能力型数据结构
- Stream:可持久化消息流(看作
仅追加日志)。 - Geo:地理坐标与附近检索。
- Bitmap:对字符串进行按位操作。
- Bitfields:在字符串值中编码多个计数器。
- HyperLogLog:近似去重计数。
- Bloom filter:检查集合中元素的存在与否。
- Cuckoo filter:同 Bloom,功能和性能之间的权衡略有不同。
- 其他
1.2 · 特性
Redis 组件能力
- 高性能内存访问:适合高频读写、低延迟场景。
- 单线程命令原子性:适合计数、限流、锁、自增 ID。
- 过期机制:适合缓存、会话、限时锁、临时数据自动清理。
- 丰富数据结构与原生命令支持:许多常见业务动作都可以直接映射到 Redis 的数据结构和命令。
Redis 数据结构特性
- 丰富的数据结构:可以直接表达对象、队列、集合关系、排行榜、消息流。
- String 值存储与原子操作能力:适合缓存、计数器、限流标记、分布式锁、会话等场景。
- Hash 字段组织能力:适合对象属性、用户信息、配置项、统计字段等结构化记录场景。
- Set 集合运算能力:适合集合去重、交集、并集、差集、推荐、标签筛选。
- ZSet 排序与范围查询能力:适合排行榜、时间线、延时任务、区间检索。
- List 顺序存储与阻塞消费能力:适合简单消息队列和任务分发。
- Stream 消费组与确认机制:适合更可靠的消息处理和事件流消费。
- 位级存储与概率统计能力:适合海量状态位、签到、布尔标记、UV 统计等节省内存场景。
数据选型
- 如果是
一个 key 对一个值,先想 String。 - 如果是
一个对象多个字段,先想 Hash。 - 如果是
按顺序进出,先想 List。 - 如果是
去重 + 关系运算,先想 Set。 - 如果是
排名 / 排序 / 分值区间,先想 Sorted Set。 - 如果是
可靠消息队列,先想 Stream。 - 如果是
海量布尔状态,先想 Bitmap。 - 如果是
海量元素的存在性判断或去重预过滤,先想 Bloom Filter。 - 如果是
附近的人/地点,先想 GEO。 - 如果是
只要近似去重计数,先想 HyperLogLog。
1.3 · 不同数据结构的使用场景
快速选型
- 缓存单值、计数、锁:String
- 存对象字段:Hash
- 简单双端队列、阻塞消费:List
- 去重、标签、关系运算:Set
- 排行榜、时间线、延迟任务:Sorted Set
- 海量 UV 统计:HyperLogLog
- 紧凑状态记录、签到:Bitmap
- 附近的人/门店:GEO
- 可确认的消息队列:Stream
1.3.1 · 1. String#
最通用的数据结构,本质上是一个二进制安全的值,既能存文本,也能存数字、序列化对象、位图片段。
- 适合场景:缓存对象、简单 KV、计数器、限流器、分布式锁、ID 生成器、日志追加、文章内容/预览存储。
- 常用能力:
SET/GET、INCR/DECR、APPEND、GETRANGE/SETRANGE。 - 典型判断:如果一个 key 只需要对应一个值,优先先想 String。
- 不足:字段级更新不如 Hash 自然;结构化数据全部覆盖写时不够经济。
1.3.2 · 2. Hash#
适合存一个对象的多个字段,本质上是 key -> field -> value。
- 适合场景:用户资料、文章元数据、商品信息、会话信息、配置项、图节点属性、短网址映射。
- 常用能力:
HSET/HGET/HMGET/HGETALL/HINCRBY。 - 典型判断:当数据天然是“一个对象 + 多个属性”时,用 Hash 比多个 String key 更清晰。
- 优势:字段可单独读写,便于局部更新;对象聚合更自然。
- 不足:不适合需要保持顺序、做范围查询或做复杂关系运算的场景。
1.3.3 · 3. List#
适合维护有序、可重复的数据序列,偏向两端插入弹出。
- 适合场景:消息队列、任务队列、操作日志、时间顺序评论流、待办事项、分页读取。
- 常用能力:
LPUSH/RPUSH/LPOP/RPOP/LRANGE/BLPOP/BRPOP。 - 典型判断:如果你只关心“先进先出 / 后进先出 / 按写入顺序消费”,List 很合适。
- 优势:天然保序,双端操作快,可配合阻塞命令做简单队列。
- 不足:按值查找、随机访问、大范围中间插入都不理想;需要多消费者确认时通常不如 Stream。
1.3.4 · 4. Set#
适合存无序且唯一的数据集合,重点在“去重”和“集合关系计算”。
- 适合场景:标签系统、点赞用户集合、投票去重、好友/关注关系、共同关注、抽奖池、商品筛选的倒排索引。
- 常用能力:
SADD/SREM/SISMEMBER/SINTER/SUNION/SDIFF。 - 典型判断:只要业务关键词里出现“去重”“是否存在”“共同/差异/并集”,优先考虑 Set。
- 优势:集合运算直接由 Redis 提供,写法简单,性能稳定。
- 不足:无序,不能按分数排序,也不适合范围分页。
1.3.5 · 5. Sorted Set#
在 Set 的唯一性基础上,为每个成员增加一个 score,适合“有序集合”问题。
- 适合场景:排行榜、热度榜、延迟队列、时间线、按分值范围检索、推荐结果排序、自动补全。
- 常用能力:
ZADD/ZINCRBY/ZRANGE/ZREVRANGE/ZRANGEBYSCORE。 - 典型判断:只要既要“成员唯一”,又要“可排序 / 可排名 / 可按区间取值”,就该想到 ZSet。
- 优势:同时支持排名、范围查询、按 score 排序,表达力很强。
- 不足:比普通 Set/String 更重;如果只是简单去重,不必上 ZSet。
1.3.6 · 6. HyperLogLog#
用于近似基数统计(Probabilistic Filtering),核心价值是极小内存下统计“有多少个不同元素”。
- 适合场景:UV、日活/月活、去重访客数、大规模唯一计数。
- 常用能力:
PFADD/PFCOUNT/PFMERGE。 - 典型判断:只关心“大概有多少不同用户”,不关心“这些用户是谁”,就适合 HyperLogLog。
- 优势:非常省内存,适合海量去重计数。
- 不足:结果是近似值,不能取回成员明细,也不能做精确集合运算。
1.3.7 · 7. Bitmap#
本质上是按位存储的紧凑二进制数组,适合状态位和稠密布尔记录。
- 适合场景:签到、在线状态、活跃记录、用户行为标记、布尔矩阵、紧凑计数器。
- 常用能力:
SETBIT/GETBIT/BITCOUNT/BITOP/BITFIELD。 - 典型判断:当对象可映射为连续编号,且每个对象只需 1 bit 或少量 bit 状态时,Bitmap 很划算。
- 优势:极致节省空间,按位统计和批量位运算很强。
- 不足:对业务建模要求高;编号稀疏时浪费空间;可读性较差。
1.3.8 · 8. GEO#
底层基于有序集合封装的地理位置能力,用于经纬度存储与附近检索。
- 适合场景:附近的人、门店搜索、配送范围、位置打卡、距离计算。
- 常用能力:
GEOADD/GEODIST/GEORADIUS/GEORADIUSBYMEMBER/GEOHASH。 - 典型判断:只要需求是“基于经纬度找附近”,直接用 GEO,而不是自己维护坐标计算。
- 优势:内建距离计算和范围查找,开发成本低。
- 不足:适合中轻量位置检索;复杂 GIS 分析仍应交给专业地理系统。
1.3.9 · 9. Stream#
适合可持久化的消息流,支持消费组、消息确认、待处理队列。
- 适合场景:消息队列、事件总线、异步任务分发、日志采集、订单事件流。
- 常用能力:
XADD/XREAD/XGROUP/XREADGROUP/XACK/XPENDING。 - 典型判断:当你需要“消息可回放、多消费者组、消费确认、未处理追踪”时,用 Stream。
- 优势:比 List/PubSub 更适合可靠消息处理。
- 不足:模型和运维复杂度更高;如果只是即时广播,Pub/Sub 更轻。
1.4 · 可编程性
Redis 可编程性(Programmability)= 在服务器端执行用户自定义逻辑的能力。
核心价值:
- 数据局部性:逻辑在数据所在处执行,减少网络往返和传输开销。
- 原子性:脚本执行期间阻塞所有其他客户端,效果要么全部生效、要么全部未发生,等同于事务语义。
- 组合能力:将多个命令 + 条件逻辑封装为一次调用,跨多个 key、多种数据结构原子操作。
Redis 提供两种可编程方式:Eval Scripts(Redis 2.6+)和 Redis Functions(Redis 7.0+,推荐)。
1.4.1 · Eval Scripts(Lua 脚本)#
脚本被视为客户端应用的一部分,服务端仅做缓存,不持久化。
基本用法:EVAL <script> <numkeys> [key ...] [arg ...]
> EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 foo bar
OK
KEYS表:所有 key 名参数(必须显式传入,Cluster 路由依赖于此)。ARGV表:所有非 key 的普通参数。redis.call():执行 Redis 命令,出错直接返回错误给客户端。redis.pcall():执行 Redis 命令,出错返回错误对象给脚本上下文,可由脚本自行处理。
脚本缓存与 EVALSHA:
- 每个
EVAL执行的脚本会按 SHA1 摘要缓存在服务端。 SCRIPT LOAD <script>→ 返回 SHA1 摘要,之后用EVALSHA <sha1> <numkeys> ...调用,节省网络带宽。- 缓存是易失的:服务器重启、故障转移、
SCRIPT FLUSH都会清空。应用需自行处理NOSCRIPT错误并重新加载。 - 反模式:动态拼接脚本内容 → 缓存膨胀;应始终参数化脚本,通过
KEYS/ARGV传参。
SCRIPT 命令族:SCRIPT LOAD / SCRIPT EXISTS / SCRIPT FLUSH / SCRIPT KILL。
1.4.2 · Redis Functions(推荐,7.0+)#
函数是数据库的一等公民,随数据一起持久化(AOF)和复制(主从),不需要应用在运行时加载。
Eval Scripts 的痛点(Functions 解决的问题):
- 所有客户端实例必须维护脚本副本,同步更新困难。
- 缓存随时可能丢失,事务中调用缓存脚本容易失败。
- SHA1 摘要无语义,调试困难(如
MONITOR中看不出含义)。 - 脚本之间不能互相调用,无法复用代码。
核心概念:
- Library(库):函数的容器,一个库包含一个或多个函数,整体加载/替换/删除。
- Function(函数):库中注册的命名入口点,通过
redis.register_function()注册。 - 库的 Shebang 格式:
#!lua name=<library_name>。
加载与调用:
-- 加载库(从文件)
$ cat mylib.lua | redis-cli -x FUNCTION LOAD REPLACE
-- 调用函数
> FCALL my_hset 1 myhash field1 "value1"
FUNCTION LOAD <code>:加载库,返回库名。加REPLACE可覆盖已有同名库。FCALL <function> <numkeys> [key ...] [arg ...]:调用函数。FCALL_RO:调用只读函数(可在只读副本上执行)。FUNCTION LIST / FUNCTION DELETE / FUNCTION DUMP / FUNCTION RESTORE:管理函数。
代码复用:同一 library 内的函数可以调用库内部的公共辅助函数(普通 Lua 函数,不需注册)。
Function Flags:注册时声明函数行为,告知 Redis 如何执行策略控制。
redis.register_function{
function_name = 'my_hgetall',
callback = my_hgetall,
flags = { 'no-writes' } -- 声明为只读
}
no-writes:只读函数,允许通过FCALL_RO在副本上执行。- 默认 Redis 假设函数可读可写,会拒绝在只读副本上运行。
集群部署:Redis 不会自动将 Functions 同步到所有集群节点,需管理员手动加载:
$ redis-cli --cluster-only-masters --cluster call host:port FUNCTION LOAD ...
1.4.3 · Eval Scripts vs Functions 选型#
| 维度 | Eval Scripts | Redis Functions |
|---|---|---|
| 版本 | 2.6+ | 7.0+ |
| 定位 | 应用的一部分 | 数据库的扩展 |
| 持久化 | 不持久化,缓存易失 | 随 AOF 持久化,主从复制 |
| 标识 | SHA1 摘要(无语义) | 用户定义的函数名 |
| 代码复用 | 脚本间不能互调 | 同库内函数可共享代码 |
| 部署 | 应用负责加载 | 管理员预加载,应用直接调用 |
| 适用场景 | 简单、临时、轻量脚本 | 正式的服务端逻辑封装 |
1.4.4 · 共性约束
- 原子执行:脚本/函数执行期间阻塞所有客户端,不能被其他命令穿插。
- 沙箱环境:不能访问文件系统、网络或其他系统调用,只能操作 Redis 数据。
- 最大执行时间:默认 5 秒(
busy-reply-threshold可配)。超时后不会自动终止(会破坏原子性),而是开始对其他客户端返回BUSY错误,可通过SCRIPT KILL(只读脚本)或SHUTDOWN NOSAVE(已写入数据的脚本)中断。 - 只读脚本:可通过
no-writesflag 或EVAL_RO/EVALSHA_RO/FCALL_RO执行,可在副本运行、可被SCRIPT KILL、不受 OOM 限制、不受写暂停阻塞。