*Redis 数据类型与抽象入门

Redis 不仅仅是一个简单的键值存储,它实际上是一个数据结构服务器,支持多种类型的值。这意味着,在传统的键值存储中,你只能将字符串键关联到字符串值,而在 Redis 中,值不局限于简单的字符串,还可以持有更复杂的数据结构。

以下是 Redis 支持的主要数据结构列表,本教程将逐一介绍:

  • 二进制安全字符串 (Strings)
  • 列表 (Lists):按插入顺序排序的字符串元素集合,其底层实现是链表
  • 集合 (Sets):唯一且无序的字符串元素集合。
  • 有序集合 (Sorted Sets):类似于集合,但每个字符串元素都关联一个称为分数 (Score) 的浮点数。元素始终按分数排序,因此可以获取指定范围内的元素(例如:获取前 10 名或后 10 名)。
  • 哈希 (Hashes):由字段 (Field) 与值 (Value) 组成的映射表,字段和值均为字符串。这与 Ruby 或 Python 的 Hash/Dict 非常相似。
  • 位图 (Bitmaps):利用特殊命令将字符串值当作位数组处理:你可以设置/清除单个比特位、统计设为 1 的位数、查找第一个设置或未设置的位等。
  • HyperLogLogs:一种概率数据结构,用于估算集合的基数(去重计数)。它比听起来简单得多,请参阅后文相关章节。
  • 流 (Streams):一种仅追加 (Append-only) 的类映射条目集合,提供抽象的日志数据类型。

仅通过命令参考来理解这些类型的工作原理及其适用场景并不直观,因此本文档旨在作为 Redis 数据类型及其常见模式的速成课程。

示例中我们将使用 redis-cli 工具,这是一个简单方便的命令行界面,用于向 Redis 服务器发出指令。


*Redis 键 (Keys)

Redis 键是二进制安全的,这意味着你可以使用任何二进制序列作为键,从简单的字符串 "foo" 到 JPEG 文件的二进制内容。空字符串也是有效的键。

关于键的设计准则:

  • 避免过长的键:例如,1024 字节的键不仅浪费内存,且在查找该键时会产生高昂的比较成本。如果需要匹配大数据,对其进行哈希处理(如 SHA1)是更好的选择。
  • 避免过短的键:相比于 u1000flwuser:1000:followers 的可读性明显更高,而增加的空间开销相对于值对象本身微乎其微。你需要在可读性与空间占用之间找到平衡点。
  • 遵循固定模式 (Schema):推荐使用冒号分隔,如 object-type:id(例如 user:1000)。多个单词可以使用点或破折号,如 comment:1234:reply-to
  • 最大限制:键的最大长度为 512 MB

*Redis 字符串 (Strings)

字符串是 Redis 最基础的值类型。由于 Redis 键本身也是字符串,所以当值也是字符串时,我们本质上是在执行字符串到字符串的映射。它非常适合缓存 HTML 片段或页面数据。

> set mykey somevalue
OK
> get mykey
"somevalue"
  • SET 会替换键中存储的任何现有值(即使类型不同)。
  • 值最大为 512 MB。

*原子操作

即使字符串是基础类型,你也可以对其执行原子操作,例如原子递增

> set counter 100
OK
> incr counter
(integer) 101
> incrby counter 50
(integer) 152

INCR 的原子性意味着什么? 这意味着即使多个客户端同时对同一个键执行 INCR,也永远不会产生竞态条件 (Race Condition)。读取-递增-写回操作在执行期间,其他客户端无法介入。最终值始终是准确的。


*修改与查询键空间

有些命令并不针对特定数据类型,而是用于管理整个键空间:

  • EXISTS:检查键是否存在。
  • DEL:删除键及其关联的值(无论类型)。
  • TYPE:返回存储在键中的值的类型。
> set mykey x
OK
> type mykey
string
> del mykey
(integer) 1
> type mykey
none

*Redis 过期时间 (Expires)

你可以为任何键设置生存时间 (TTL)。当时间耗尽时,键会被自动销毁。

  • 精度支持秒或毫秒。
  • 过期时间的分辨率始终为 1 毫秒。
  • 过期信息会持久化到磁盘;当服务器停止时,时间实际上仍在流逝(Redis 保存的是过期的绝对日期)。
> set key some-value
OK
> expire key 5
(integer) 1
# 5 秒后
> get key
(nil)

*Redis 列表 (Lists)

Redis 列表是通过链表 (Linked List) 实现的。这意味着在列表的头部或尾部添加元素的时间复杂度是常数级 $O(1)$,无论列表中有 10 个还是 1000 万个元素。

  • LPUSH/RPUSH:在左侧(头部)或右侧(尾部)添加元素。
  • LPOP/RPOP:弹出并删除元素。
  • LRANGE:获取指定范围内的元素(支持负索引,-1 表示最后一个元素)。

*阻塞操作 (Blocking)

BRPOPBLPOPRPOP/LPOP 的阻塞版本。如果列表为空,它们会保持连接直到有新元素加入或超时。这是实现生产者-消费者模式的理想构建块。


*Redis 哈希 (Hashes)

哈希是表示“对象”的绝佳选择,它由字段-值对组成:

> hmset user:1000 username antirez birthyear 1977 verified 1
OK
> hgetall user:1000
1) "username"
2) "antirez"
...

内存优化:包含少量字段且值较小的哈希在内存中会以特殊的编码方式存储,非常节省空间。


*Redis 集合 (Sets)

集合是字符串的无序集合。SADD 用于添加新元素。

  • 特性:元素具有唯一性。
  • 操作:支持交集 (SINTER)、并集 (SUNION) 和差集 (SDIFF)。
  • 场景:标签 (Tagging)、好友列表、随机抽取 (SPOP)。

*Redis 有序集合 (Sorted Sets)

有序集合是集合与哈希的结合。元素唯一,且每个元素都关联一个分数 (Score)。

  • 排序逻辑:默认按分数升序排列;分数相同时按字典序排列。
  • 场景:实时排行榜、带权重的任务队列。

*位图 (Bitmaps)

位图不是独立的数据类型,而是定义在字符串类型上的一组位操作。

  • 优势:极度节省空间。例如,记录 40 亿用户的单比特状态(如“是否签到”),仅需 512 MB 内存。
  • 命令SETBITGETBITBITCOUNTBITOP

*HyperLogLogs

这是一种估算集合基数(去重数量)的概率算法。

  • 神奇之处:无论统计多少个元素,最坏情况下仅占用 12 KB 内存,标准误差小于 1%。
  • 场景:统计每日搜索关键词的去重数量、统计网站的 UV(独立访客)。
> pfadd hll a b c d
(integer) 1
> pfcount hll
(integer) 4

*键的自动创建与销毁

对于容器类数据类型(Lists, Sets, Hashes, ZSets),Redis 遵循以下规则:

  1. 自动创建:向不存在的键添加元素时,会自动创建一个空的聚合类型容器。
  2. 自动删除:当容器变空时,键会被自动从内存中销毁(Streams 除外)。
  3. 兼容空键:对不存在的键调用只读命令(如 LLEN),其返回结果等同于操作一个空的容器。