Redis:Redis 数据类型

文章目录

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 会根据值的内容和长度选择不同的编码:

  1. int 编码
    • 当存储的是纯整数 且值在 long long 范围内时。
    • Redis 直接将值存储在 RedisObject 的 ptr 指针位置(即指针被强转成整数存储),不再额外分配 SDS 内存。
  2. embstr 编码
    • 当存储的是短字符串(通常小于等于 39 或 44 字节,取决于版本)。
    • RedisObject 结构体和 SDS 数据连续分配在一块内存中。
    • 优势:只需要一次内存分配,释放也只需一次,对 CPU 缓存更友好。
  3. 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)

利用 LPUSHLTRIM 组合,可以低成本地实现"只保留最新 N 条记录"的功能,非常适合微博 Feed、日志记录等场景。

  • 场景逻辑
    1. LPUSH new_list item:将新内容推到头部。
    2. LTRIM new_list 0 999:截取前 1000 条,其余丢弃。
  • 优势 :相比于手动删除旧数据,LTRIM 极其高效。
消息队列与阻塞操作

在"生产者-消费者"模型中,如果消费者使用 RPOP 发现列表为空,需要不断循环重试,这称为轮询。轮询会浪费 CPU 和网络带宽。

解决方案:使用阻塞命令。

  • BLPOP / BRPOP
    • 当列表为空时,客户端阻塞(挂起),不占用 CPU,直到有数据写入或超时。
    • 支持多个 Key:BRPOP list1 list2 list3 timeout,优先返回第一个有数据的 Key。
  • 面试考点
    • 公平性:如果有多个客户端同时阻塞在同一个列表上,当数据进入时,先阻塞的客户端先拿到数据(FIFO 服务顺序)。
    • 高可用/可靠性BLPOP 拿到数据后立即删除,如果客户端处理数据时崩溃,数据就丢了。解决方案 :使用 RPOPLPUSHLMOVE 将读取到的数据暂存到一个"处理中"列表,处理完成后再删除或移动到"已完成"列表。

消费者 B (阻塞中) 消费者 A (阻塞中) Redis List 生产者 消费者 B (阻塞中) 消费者 A (阻塞中) Redis List 生产者 队列为空,客户端挂起等待... 数据到达,唤醒最早的阻塞者 开始处理订单... 继续阻塞... LPUSH task "Order 返回 "Order

Sets

  • 定义 :集合用于存储唯一的字符串元素。它类似于 Java 中的 HashSet 或 Python 中的 set
  • 特性
    • 唯一性:集合中不允许出现重复元素,自动去重。
    • 无序性:集合不保证元素的存储顺序,每次返回的顺序可能不同。
    • 高效性 :添加、删除、查找单个元素的时间复杂度均为 O(1)
  • 最大长度:2^32 - 1(约 42 亿个成员)。
  • 适用场景:标签系统、共同好友、黑白名单、随机抽奖。

底层实现

Redis 集合根据元素数量和内容,会自动在两种编码之间切换:

  1. IntSet (整数集合)
    • 条件 :当集合中所有元素都是整数,且元素数量较少(默认少于 512 个)。
    • 结构:本质是一个有序的、紧凑的整数数组。
    • 优势:极其节省内存,且 CPU 缓存命中率高。
  2. 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

  • 定义 :有序集合是唯一 字符串成员的集合,其中每个成员都与一个浮点数分数相关联。
  • 排序规则
    1. 按分数排序:如果 A 和 B 分数不同,分数小的排在前面。
    2. 字典序排序:如果分数相同,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"
  • 注意:分数的更新是实时的,且不影响数据结构的整体稳定性。
滑动窗口限流
  • 逻辑
    1. 记录请求ZADD user:limit:123 <current_timestamp> <request_id>
    2. 移除过期请求ZREMRANGEBYSCORE user:limit:123 -inf <current_timestamp - window_size>
    3. 统计请求数ZCARD user:limit:123
    4. 判断 :如果 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 亿)个字段值对。

底层实现

  1. ZipList (压缩列表)
    • 条件 :当 Hash 中的字段数量较少(默认 < 512)且所有 Value 的长度较小(默认 < 64 字节)。
    • 结构:一块连续的内存块,依次存储 Key-Value 数据。不存储指针。
    • 优势:极度节省内存,利用 CPU 缓存。
  2. 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-entries
  • hash-max-ziplist-value

常用命令与高级场景

基础与批量操作
  • HSET key field value:设置单个字段或多个字段(新版本支持 HSET key f1 v1 f2 v2)。
  • HMGET key field1 field2:一次性获取多个字段。
    • 优化 :相比多次 HGETHMGET 减少了网络 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 value
    • HTTL key field...
  • 应用场景
    1. 事件追踪 :在 events:today Hash 中记录事件 ID,每个事件 Field 设置 1 小时 TTL。HLEN 即可统计过去一小时内的事件数。
    2. 活跃 Session 管理 :在 user:sessions Hash 中存储用户的 Session ID,并设置自动过期。不需要为每个 Session 创建单独的 Redis Key。
    3. 滑动窗口计数 :在 ratelimit:user:1 中设置多个时间窗口的计数器,每个计数器 Field 设置对应的 TTL。
相关推荐
chirrupy_hamal2 小时前
PostgreSQL 中的“脏页(Dirty Pages)”是什么?
数据库·postgresql
陈天伟教授3 小时前
关系数据库-07. 关系操作
数据库·达梦数据库·国产数据库
zzhongcy3 小时前
复合索引 (item1, item2, item3 ) > (?, ?, ?) 不起作用,EXPLAIN 后type=ALL(全表扫描)
android·数据库
Elastic 中国社区官方博客3 小时前
Elastic:DevRel 通讯 — 2026 年 1 月
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
可观测性用观测云3 小时前
AWS RDS 可观测性最佳实践
数据库
程序员小白条3 小时前
面试 Java 基础八股文十问十答第八期
java·开发语言·数据库·spring·面试·职场和发展·毕设
汗流浃背了吧,老弟!4 小时前
向量数据库在RAG中的非必需场景及替代方案
数据库
brevity_souls4 小时前
SQL 中 BETWEEN 和 IN 的区别
数据库·sql
产幻少年4 小时前
redis位图
数据库·redis·缓存