系列导航:

前两篇 overview 解决的是“为什么需要 Redis”以及“Redis 作为系统是什么”。到了第三篇,问题会落到真正影响建模的一步:同样是一个 Key,里面到底该放成什么结构。

很多 Redis 用法之所以越用越重,不是因为命令不会写,而是因为一开始结构就选错了。结构一旦选错,后面你看到的往往是这些现象:

  • 本来只想存一个对象,后来更新越来越别扭
  • 本来只是有序列表,后来又开始需要去重和范围读取
  • 本来只是简单消息,后来又想确认、回溯和重试
  • 本来只是成员集合,后来却要求按分数排序

这些都说明:问题不再是“能不能存进去”,而是“现在这份数据的语义,是否还和当前结构匹配”。

本文只做三件事:

  1. 给出一张 Redis 常见数据结构的认知地图。
  2. 说明每种结构最核心的判断维度。
  3. 说明什么时候应该从一种结构切换到另一种结构。

本文刻意不展开缓存模式、排行榜方案、消息系统设计等业务模式。这里只建立结构判断,不讨论完整业务落地。

先用四个问题判断结构

在选结构之前,可以先问四个最基础的问题:

  1. 这个值是一个整体,还是由多个字段组成?
  2. 我更关心顺序、唯一性,还是分数排序?
  3. 我需要按范围读取,还是按成员判断存在?
  4. 这份数据是静态值,还是会不断追加形成序列?

只要这四个问题答清楚,结构选择通常不会偏得太远。

一张够用的结构认知地图

先把最常用的几种结构放在一张表里:

结构先看什么更像什么什么时候要警惕切换
String值是否应整体读写一个完整值当你频繁只改局部字段
Hash值是否由字段组成一个字段对象当你多数时候还是整对象全取
List是否只关心顺序一个线性序列当你开始需要按成员检索或确认机制
Set是否只关心唯一成员一个无序集合当你开始需要顺序或分数
Sorted Set是否需要按 score 排序一个可排序集合当排序维度变复杂或成员无限增长
Stream是否需要不断追加的消息序列一条事件流当你其实只需要简单列表或广播

这张表的重点不是背结论,而是理解:结构的本质差异,在于它优先表达什么行为。

String 和 Hash:先分清“整体值”还是“字段对象”

这通常是第一组最容易混淆的选择。

什么时候优先 String

如果一份值更像一个整体,读的时候通常整块取出,写的时候也通常整块覆盖,那么 String 往往最自然。

它适合表达的是:

  • 一个完整文本
  • 一个序列化后的对象
  • 一个二进制值
  • 一个整体替换成本低的结果

你可以把 String 理解成“我更关心这个值整体是什么”,而不是“我想在 Redis 内部频繁改它的某个字段”。

什么时候优先 Hash

如果一份值天然由多个字段组成,而且你经常只改其中一部分字段,那么 Hash 更贴近这种语义。

它更像:

  • 一个由字段组成的对象
  • 一个可以按字段单独读取或单独更新的容器

什么时候该从 String 切到 Hash

出现下面这些信号时,往往说明 String 已经开始不顺手:

  • 你经常只想改一个字段,却总要整对象反序列化再写回
  • 对象越来越大,但修改总集中在少数字段
  • 业务经常按字段读取,而不是每次都拿整块值

什么时候别为了“更细粒度”盲目切到 Hash

如果你多数时候仍然是整对象读取,而且字段更新并不频繁,那么从 String 切到 Hash 不一定更好。结构切换不是为了显得更专业,而是为了让读写方式更匹配。

List、Set、Sorted Set:顺序、唯一性和排序不要混着看

这三种结构的区别,最适合从“你到底最在意什么关系”开始看。

List:只强调顺序

List 最核心的语义,是元素按先后进入一个线性序列。

如果你更关心的是:

  • 谁在前,谁在后
  • 从左边还是右边进出
  • 取一段连续区间

那么它的心智模型通常最接近 List

Set:只强调唯一成员

Set 不关心顺序,也不关心分数,它最在意的是“一个成员在不在集合里,而且不要重复”。

如果你的第一诉求是:

  • 某个元素是否存在
  • 两组成员有没有交集
  • 成员要天然去重

那它更像 Set 问题。

Sorted Set:既要唯一成员,又要有序排名

Sorted Set 可以理解成“带分数的 Set”。成员依旧唯一,但每个成员同时带一个 score,于是结构可以按 score 排序。

当你的问题变成下面这样时,通常是在向 Sorted Set 靠近:

  • 不只是要知道成员在不在,还要知道它排第几
  • 不只是保留顺序,还要按可计算分值做范围读取
  • 不只是线性先后,而是明确存在排序依据

List、Set、Sorted Set 之间什么时候切换

这一组切换最常见,因为很多需求会从“差不多能放”演变成“原结构已经不对味”。

从 List 切到 Set

当你原本只需要保留一串元素,后来开始更频繁地关心“某个成员是否已经存在”,并且不再真正依赖顺序时,说明问题已经更像集合而不是列表。

从 Set 切到 Sorted Set

当你一开始只要求唯一性,后来又开始要求:

  • 按某个权重排序
  • 取前 N 个成员
  • 按 score 做范围扫描

这说明仅靠 Set 已经不够,问题开始需要 Sorted Set 的 score 维度。

从 List 直接切到 Sorted Set

如果你最初只想保留先后关系,但后来发现真正需要的其实不是“插入顺序”,而是“按某个数值或时间戳排序”,那么更适合直接切到 Sorted Set

一个简单判断是:

  • 顺序来自进入先后,用 List
  • 顺序来自外部数值,用 Sorted Set

Stream 为什么不是“高级版 List”

很多人第一次看到 Stream,会自然把它理解成“更复杂一点的 List”。这会带来很多判断错误。

StreamList 的共同点,只是它们都能形成序列;真正的差异在于,Stream 表达的是不断追加、带消息身份、可被消费的记录流

所以,Stream 不是为了替代所有顺序结构,而是为了表达这类问题:

  • 数据会持续追加
  • 每条记录有自己的 ID
  • 读取不是单纯拿一段区间,而是围绕消费位置前进
  • 序列本身更像一条持续增长的事件流

什么时候从 List 切到 Stream

出现下面这些信号时,通常说明问题已经不再是简单顺序容器:

  • 你开始关心每条记录的独立身份
  • 你希望记录不是取完就算,而是能按消费位置前进
  • 你需要围绕“还没处理到哪里”来理解这份数据

什么时候别过早用 Stream

如果你只是需要一个简单的有序序列,或者只是在一端追加、另一端取出,List 往往更直接。Stream 的价值在于它表达的是流,而不是“更复杂所以更高级”。

Bitmap、HyperLogLog、Geo 和 JSON:什么时候再往外扩

当基础结构已经无法准确表达问题时,才值得考虑这些更专门的能力。判断方式仍然一样:先看语义是否匹配,而不是先看命令新不新。

Bitmap

当一批状态天然可以压成位,并且你更关心的是“某个位置是否为 1”,这是位图问题,而不是普通字符串问题。

HyperLogLog

当你需要的是近似去重计数,而不是精确成员集合,这已经不是 Set 问题,而是近似统计问题。

Geo

当排序或查询依赖的是地理位置,而不是普通数值分数,这就不再是普通 Sorted Set 心智模型能完整表达的范围。

JSON

当对象天然是嵌套文档,而且你真正需要的是路径级读写时,它才值得和 StringHash 分开看。

这些结构的共同点是:只有当问题本身已经进入对应语义,切换才有意义。

结构切换时,先看“主操作”有没有变

很多人判断是否要换结构,容易盯着“数据长什么样”;但更实用的标准其实是:你最常执行的主操作有没有变。

可以按这个顺序判断:

  1. 最常做的是整体覆盖,还是局部字段更新。
  2. 最常关心的是顺序、成员唯一,还是分数排序。
  3. 最常读取的是一段值,还是按成员判断、按范围检索。
  4. 数据更像静态容器,还是不断追加的流。

只要主操作变了,结构往往也该跟着变。

结构切换,不等于频繁迁移

需要强调一点:理解切换判断,不是鼓励你在同一条链路里反复迁移结构。真正更重要的是在建模初期就把主要语义想清楚。

切换判断的意义在于:

  • 帮你识别当前结构是不是已经开始别扭
  • 帮你知道问题为什么变别扭
  • 帮你在重构时有一条更清晰的方向

它不是让你每看到一个新命令就重写模型。

这一篇只留下一个结构判断框架

如果把今天的内容压缩成一个最小框架,可以记成:

  • 整体值还是字段对象:StringHash
  • 顺序、唯一性还是分数排序:ListSetSorted Set
  • 静态容器还是持续追加的流:普通结构或 Stream
  • 只有当问题进入更专门语义时,才考虑 Bitmap、HyperLogLog、Geo、JSON

理解 Redis 数据结构,关键不是记住每个命令,而是看见一份数据时,能先判断它究竟更像哪一种关系模型。

本篇解决了什么:

  • 给出了 Redis 常见结构的一张认知地图,区分整体值、字段对象、顺序、唯一性、排序和流
  • 说明了 StringHashListSetSorted SetStream 各自最核心的判断维度
  • 总结了几类常见切换信号,帮助判断什么时候当前结构已经不再匹配
  • 明确了本文只建立结构判断,不展开缓存、消息、排行榜等业务模式

下一篇:Redis 缓存设计:Cache Aside、穿透、击穿与雪崩(四)