文章目录
Redis 数据类型
Redis 数据类型
基础类型
字符串
哈希
列表
集合
有序集合
高级类型
向量集
流
JSON对象
特殊位操作
位图
位域
空间与统计
地理空间
概率统计
时间序列
Redis 提供了广泛的数据类型来存储你的数据。以下几种类型是为特定目的高度特化的:
- Geospatial(地理空间):存储带有相关坐标的字符串,用于地理空间查询。
- Vector sets(向量集):存储带有相关向量数据(以及可选元数据)的字符串,用于向量相似性查询。
- Probabilistic data types(概率数据类型):对大型数据集保持近似计数和其他统计数据。
- Time series(时间序列):存储实数值数据点及其收集时间。
其余的数据类型更为通用:
- Strings(字符串):存储文本或二进制数据。
- Hashes(哈希):在单个键内存储键值对。
- JSON:存储结构化的、分层级的数组和键值对象,匹配流行的 JSON 文本文件格式。
- Lists(列表):存储简单的字符串序列。
- Sets(集合):存储唯一的字符串集合。
- Sorted sets(有序集合):存储带有相关分数的唯一字符串集合。
- Streams(流):存储条目序列,每个条目包含一组字段值对。
这些通用数据类型在功能上存在一些重叠,实际上,你可以仅使用字符串加上一点点创造力来模拟其中的任何一种。然而,每种数据类型在性能、内存使用和功能性方面提供了不同的权衡。本指南将帮助你为你的任务选择最佳的数据类型。另外,本文不会涉及具体类型详细的指令格式,所有指令都可以从官方指令表查看。
Strings
- 定义 :Redis 字符串是二进制安全的字节序列。它不仅可以存储文本,还可以存储 JPEG 图片、序列化对象等二进制数据。
- 特点 :
- 二进制安全 :不依赖
\0作为结束符,而是通过长度来判断。 - 动态性 :支持修改、追加,类似于 C++ 的
string或 Java 的StringBuilder。 - 最大限制:单个 String 最大值为 512 MB。
- 二进制安全 :不依赖
- 基本操作 :
SET(存)、GET(取)、DEL(删)。
底层实现
SDS (Simple Dynamic String)
Redis 没有直接使用 C 语言的字符串(char*),而是封装了 SDS。SDS 的结构优势在于:
- O(1) 获取长度 :C 字符串遍历求长度是 O(N),SDS 直接记录
len属性。 - 杜绝缓冲区溢出:修改前会检查空间是否足够,不够会自动扩容。
- 二进制安全 :因为存了长度,中间可以包含
\0字符。
Redis SDS (高效)
C Language String (低效)
H
e
l
l
o
\0 (终结符)
求长度需遍历直到 \0
len: 5
alloc: 6
buf: Hello
\0 (兼容 C 函数)
O(1) 获取 len
空间预分配策略
编码方式
为了节省内存,Redis 会根据值的内容和长度选择不同的编码:
- int 编码 :
- 当存储的是纯整数 且值在
long long范围内时。 - Redis 直接将值存储在 RedisObject 的
ptr指针位置(即指针被强转成整数存储),不再额外分配 SDS 内存。
- 当存储的是纯整数 且值在
- embstr 编码 :
- 当存储的是短字符串(通常小于等于 39 或 44 字节,取决于版本)。
- RedisObject 结构体和 SDS 数据连续分配在一块内存中。
- 优势:只需要一次内存分配,释放也只需一次,对 CPU 缓存更友好。
- raw 编码 :
- 当字符串很长时(超过
embstr阈值)。 - RedisObject 和 SDS 数据分开分配两次内存。
- 原因:为了避免大对象导致无法扩容(embstr 是不可变的,修改时会转成 raw)。
- 当字符串很长时(超过
是
否
是
否
修改操作
存入 String
是否为整数?
int 编码
长度 < 44字节?
embstr 编码
raw 编码
转换为 raw
常用命令与高级场景
并发控制与锁 (SET ... NX/XX)
-
SETNX(Set if Not eXists):仅在键不存在时设置。
-
场景 :分布式锁 。
bash# 设置锁,过期时间 10 秒,key 不存在才设置成功 SET lock:resource_id "unique_value" NX PX 10000 -
面试点:为什么要加过期时间?(防止客户端崩溃导致死锁)。为什么要设置唯一值?(防止误删其他线程的锁)。
原子计数器 (INCR / DECR)
- 原理 :利用 Redis 单线程模型的特性,保证"读取-加1-回写"这三个操作是原子性的。
- 场景:文章浏览量、点赞数、库存扣减、分布式 ID 生成器。
- 注意:INCR 默认初始化为 0。如果值过大溢出,Redis 会当作字符串处理返回错误。
批量操作 (MGET / MSET)
- 原理:将多个命令打包发送,减少网络往返时间(RTT)。
- 场景 :加载用户的基本信息(如 ID、姓名、头像),避免多次循环调用
GET。 - 优化 :如果数据量大,建议使用
Pipeline或在代码中将 Key 分批 MGET,以免单次命令响应过大阻塞网络。
Lists
- 定义 :Redis 列表是一个双向链表,元素按插入顺序排列。
- 特性 :
- 有序性:保持插入时的先后顺序。
- 可重复:允许存储相同的字符串。
- 双向性:同时支持从头部和尾部进行高效的推入和弹出。
底层实现
逻辑上是链表,物理上经过大量内存优化:
- Redis 3.2 之前 :使用
LinkedList(双向链表)或ZipList(压缩列表)。链表节点开销大,压缩列表省内存但修改慢。 - Redis 3.2 之后 :引入了 QuickList (快速列表)。它是一个 LinkedList + ZipList 的混合体。它将多个 ZipList 用双向指针串联起来。这样既利用了 ZipList 的内存紧凑性,又利用了 LinkedList 的灵活性。
- Redis 7.0 之后 :底层 ZipList 被替换为 ListPack(紧凑列表),进一步消除了连锁更新的风险。
Redis List Structure
NodeDetail
Prev Pointer
Value: String
Next Pointer
Head Pointer
Node A
Node B
Node C
Tail Pointer
常用命令与高级场景
除了基础的增删改查,Redis 列表在处理并发和大规模数据时有特定的模式。
队列与栈
- 队列 :先进先出 (FIFO)。
- 实现:生产者
LPUSH(入队),消费者RPOP(出队)。
- 实现:生产者
- 栈 :后进先出 (LIFO)。
- 实现:生产者
LPUSH(入栈),消费者LPOP(出栈)。
- 实现:生产者
限流列表 / 最新新闻流 (Capped Lists)
利用 LPUSH 和 LTRIM 组合,可以低成本地实现"只保留最新 N 条记录"的功能,非常适合微博 Feed、日志记录等场景。
- 场景逻辑 :
LPUSH new_list item:将新内容推到头部。LTRIM new_list 0 999:截取前 1000 条,其余丢弃。
- 优势 :相比于手动删除旧数据,
LTRIM极其高效。
消息队列与阻塞操作
在"生产者-消费者"模型中,如果消费者使用 RPOP 发现列表为空,需要不断循环重试,这称为轮询。轮询会浪费 CPU 和网络带宽。
解决方案:使用阻塞命令。
BLPOP/BRPOP:- 当列表为空时,客户端阻塞(挂起),不占用 CPU,直到有数据写入或超时。
- 支持多个 Key:
BRPOP list1 list2 list3 timeout,优先返回第一个有数据的 Key。
- 面试考点 :
- 公平性:如果有多个客户端同时阻塞在同一个列表上,当数据进入时,先阻塞的客户端先拿到数据(FIFO 服务顺序)。
- 高可用/可靠性 :
BLPOP拿到数据后立即删除,如果客户端处理数据时崩溃,数据就丢了。解决方案 :使用RPOPLPUSH或LMOVE将读取到的数据暂存到一个"处理中"列表,处理完成后再删除或移动到"已完成"列表。
消费者 B (阻塞中) 消费者 A (阻塞中) Redis List 生产者 消费者 B (阻塞中) 消费者 A (阻塞中) Redis List 生产者 队列为空,客户端挂起等待... 数据到达,唤醒最早的阻塞者 开始处理订单... 继续阻塞... LPUSH task "Order 返回 "Order
Sets
- 定义 :集合用于存储唯一的字符串元素。它类似于 Java 中的
HashSet或 Python 中的set。 - 特性 :
- 唯一性:集合中不允许出现重复元素,自动去重。
- 无序性:集合不保证元素的存储顺序,每次返回的顺序可能不同。
- 高效性 :添加、删除、查找单个元素的时间复杂度均为 O(1)。
- 最大长度:2^32 - 1(约 42 亿个成员)。
- 适用场景:标签系统、共同好友、黑白名单、随机抽奖。
底层实现
Redis 集合根据元素数量和内容,会自动在两种编码之间切换:
- IntSet (整数集合) :
- 条件 :当集合中所有元素都是整数,且元素数量较少(默认少于 512 个)。
- 结构:本质是一个有序的、紧凑的整数数组。
- 优势:极其节省内存,且 CPU 缓存命中率高。
- HashTable (哈希表/字典) :
- 条件:集合元素超过 512 个,或者包含非整数字符串。
- 结构:标准的 Redis Dict,Key 为元素值,Value 为 NULL。
- 优势:即便有大量元素,增删查也能保持 O(1)。
否
是
否
是
添加非整数或数量超限
删除元素直到满足条件
插入元素
元素数量 < 512?
使用 HashTable
Dict 结构
元素全是整数?
使用 IntSet
有序整数数组
常用命令与高级场景
唯一性校验与去重
- 命令 :
SADD,SREM,SISMEMBER - 场景 :
- UV 统计:记录每日活跃用户 ID,利用唯一性自动去重。
- 标签去重 :给文章打标签时,使用
SADD,无需代码判断是否已存在。 - 黑名单 :使用
SISMEMBER检查用户 IP 是否在黑名单中,复杂度 O(1),远快于 List。
关系运算 (Set Algebra)
这是 Redis 集合最强大的功能,常用于社交网络分析。
- 交集 (
SINTER) :查找共同元素。- 场景 :共同好友 。
SINTER user:1:friends user:2:friends返回两人共同好友。
- 场景 :共同好友 。
- 并集 (
SUNION) :合并所有元素。- 场景 :推荐好友。把我好友的好友全都合并起来看一遍。
- 差集 (
SDIFF) :一方有另一方没有。- 场景 :差量数据同步 。计算
SDIFF local_data remote_data找出本地缺失的数据。
- 场景 :差量数据同步 。计算
用户 B 的好友
用户 A 的好友
Bob
Alice
David
Bob
Eve
David
交集: Bob, David
随机操作
SRANDMEMBER:随机返回一个元素,但不删除。适合推荐算法中的随机展示。SPOP:随机返回一个元素,并删除。适合抽奖系统(中奖后移除)或随机任务分配。
批量查询优化
SMISMEMBER:一次性检查多个元素是否在集合中。- 优势 :相比多次调用
SISMEMBER,这大大减少了网络 RTT 往返次数,适合批量权限检查。
- 优势 :相比多次调用
Sorted Sets
- 定义 :有序集合是唯一 字符串成员的集合,其中每个成员都与一个浮点数分数相关联。
- 排序规则 :
- 按分数排序:如果 A 和 B 分数不同,分数小的排在前面。
- 字典序排序:如果分数相同,Redis 会根据成员字符串的字典序进行排序(Binary-safe 比较规则)。
- 数据模型理解 :
- 它是 Set (元素唯一性)和 Hash(成员到分数的映射)的结合体。
- 但与 Set 不同,它是有序的;与 Hash 不同,它不仅映射,还维护全局顺序。
底层实现
- Hash Table (Dict) :映射
Member -> Score。- 作用 :提供 O(1) 复杂度的成员存在性检查和分数获取。如果不使用 Hash,想在 Skip List 中查某个 Member 的分数,需要 O(N) 或 O(log N) 的查找。
- Skip List (SkipList) :映射
Member <-> Score并维护有序性。- 作用 :支持高效的范围查询(ZRANGE)、排序(ZREVRANGE)和排名查找(ZRANK)。平衡树(红黑树)虽然也能做到,但在 Redis 这种单线程、修改频繁的场景下,跳跃表的实现更简单且并发性能更好(虽然 Redis 是单线程,但跳跃表有更好的 CPU 缓存局部性,且无需复杂的旋转操作)。
其中跳表是一种典型的"以空间换时间"的数据结构,它在单链表的基础上增加了多级索引,实现了类似二分查找的效果。
Redis Sorted Set Internal Structure
Skip List: O(log N) 范围查询
Level 3
Cherry: 30
Null
Level 2
Banana: 20
Level 1
Apple: 10
Level 0/Bottom
Orange: 25
Hash Table: O(1) 查找分数
Member: Apple
Score: 10
Member: Banana
Score: 20
Member: Cherry
Score: 30
常用命令与高级场景
基础操作
ZADD key score member:添加或更新成员。ZRANGE key start stop [WITHSCORES]:按分数从低到高返回列表。ZREVRANGE key start stop [WITHSCORES]:按分数从高到低返回列表。ZSCORE key member:获取指定成员的分数(通过 Hash Table 实现)。
排行榜系统
- 场景:实时显示游戏前 10 名,以及玩家的排名。
- 命令 :
- 玩家得分后:
ZINCRBY leaderboard 100 "PlayerA"(原子操作,避免先读后写)。 - 查看前10名:
ZREVRANGE leaderboard 0 9 WITHSCORES。 - 查看我的排名:
ZREVRANK leaderboard "PlayerA"。
- 玩家得分后:
- 注意:分数的更新是实时的,且不影响数据结构的整体稳定性。
滑动窗口限流
- 逻辑 :
- 记录请求 :
ZADD user:limit:123 <current_timestamp> <request_id>。 - 移除过期请求 :
ZREMRANGEBYSCORE user:limit:123 -inf <current_timestamp - window_size>。 - 统计请求数 :
ZCARD user:limit:123。 - 判断 :如果
ZCARD的结果大于阈值(例如 100),则拒绝本次请求。
- 记录请求 :
- 优势:利用 Redis 单线程特性,这整个逻辑是原子性的(配合 Lua 脚本),非常精准。
通用索引
- 场景:你需要查找以 "A" 开头的所有名字。
- 命令 :
ZRANGEBYLEX myindex [A [Z。 - 原理:插入时将所有元素的 score 设为 0,Redis 就会根据 Member 字符串本身进行排序。这比使用模糊匹配的 Key 扫描要高效得多。
Hashes
- 定义:Redis Hash 是一个键值对集合,其中 Key 映射到 Hash 对象,Hash 对象内部包含多个 Field(字段)和 Value(值)。
- 类比 :它相当于 Java 的
HashMap、Python 的dict或 Redis 中的 Map 结构。 - 特点 :
- 结构化存储 :适合存储一个对象的多个属性(如
user:1000存储 name, age, sex)。 - 灵活性:Field 数量无硬性限制(受限于内存),既可以只存 1 个字段,也可以存 4 亿个。
- 结构化存储 :适合存储一个对象的多个属性(如
- 最大限制:每个 Hash 最多存储 2^32 - 1(约 42 亿)个字段值对。
底层实现
- ZipList (压缩列表) :
- 条件 :当 Hash 中的字段数量较少(默认
< 512)且所有 Value 的长度较小(默认< 64字节)。 - 结构:一块连续的内存块,依次存储 Key-Value 数据。不存储指针。
- 优势:极度节省内存,利用 CPU 缓存。
- 条件 :当 Hash 中的字段数量较少(默认
- HashTable (哈希表/字典) :
- 条件:当 Hash 对象变得很大(字段多或 Value 大),达到阈值。
- 结构:标准的 Redis Dict 结构,采用链地址法解决冲突(DictEntry -> Next)。
- 优势:无论数据多大,读写速度都稳定在 O(1),但内存开销大(指针、内存碎片)。
Redis Hash
符合条件
超过阈值
内存占用低,修改成本高
内存占用高,读写快
Encoding_Decision 何时切换?
字段数 < 512?
Value < 64 Bytes?
Key: user:1000
Hash Object
ZipList:
紧凑连续内存
HashTable:
指针数组 + 链表节点
Zip
HT
配置参数 (可以在 redis.conf 中调整):
hash-max-ziplist-entrieshash-max-ziplist-value
常用命令与高级场景
基础与批量操作
HSET key field value:设置单个字段或多个字段(新版本支持HSET key f1 v1 f2 v2)。HMGET key field1 field2:一次性获取多个字段。- 优化 :相比多次
HGET,HMGET减少了网络 RTT(往返时间)。
- 优化 :相比多次
HGETALL key:获取所有字段和值。- 警告 :这是一个 O(N) 命令。如果 Hash 特别大(比如几百万个字段),会阻塞 Redis 主线程。
- 替代方案 :在生产环境中,建议使用
HSCAN进行增量遍历。
原子计数器
HINCRBY key field increment:对 Hash 中的某个字段进行原子递增。- 场景 :
- 文章阅读量 :
HINCRBY article:1000 views 1。 - 直播间互动:记录不同类型的互动数(点赞、分享、评论)。
- 优势 :比使用 String Key(如
article:1000:views)更节省 Key 数量,避免 Hash Collision 冲突,且所有相关数据集中在一个 Key 下,易于管理。
- 文章阅读量 :
字段级过期 (Field Expiration - Redis 7.4 新特性)
- 命令 :
HEXPIRE key seconds field...HSETEX key field seconds valueHTTL key field...
- 应用场景 :
- 事件追踪 :在
events:todayHash 中记录事件 ID,每个事件 Field 设置 1 小时 TTL。HLEN即可统计过去一小时内的事件数。 - 活跃 Session 管理 :在
user:sessionsHash 中存储用户的 Session ID,并设置自动过期。不需要为每个 Session 创建单独的 Redis Key。 - 滑动窗口计数 :在
ratelimit:user:1中设置多个时间窗口的计数器,每个计数器 Field 设置对应的 TTL。
- 事件追踪 :在