*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)是更好的选择。
- 避免过短的键:相比于
u1000flw,user: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)。读取-递增-写回操作在执行期间,其他客户端无法介入。最终值始终是准确的。
*修改与查询键空间
有些命令并不针对特定数据类型,而是用于管理整个键空间:
> 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)
BRPOP 和 BLPOP 是 RPOP/LPOP 的阻塞版本。如果列表为空,它们会保持连接直到有新元素加入或超时。这是实现生产者-消费者模式的理想构建块。
*Redis 哈希 (Hashes)
哈希是表示“对象”的绝佳选择,它由字段-值对组成:
> hmset user:1000 username antirez birthyear 1977 verified 1
OK
> hgetall user:1000
1) "username"
2) "antirez"
...
内存优化:包含少量字段且值较小的哈希在内存中会以特殊的编码方式存储,非常节省空间。
*Redis 集合 (Sets)
集合是字符串的无序集合。SADD 用于添加新元素。
*Redis 有序集合 (Sorted Sets)
有序集合是集合与哈希的结合。元素唯一,且每个元素都关联一个分数 (Score)。
- 排序逻辑:默认按分数升序排列;分数相同时按字典序排列。
- 场景:实时排行榜、带权重的任务队列。
*位图 (Bitmaps)
位图不是独立的数据类型,而是定义在字符串类型上的一组位操作。
*HyperLogLogs
这是一种估算集合基数(去重数量)的概率算法。
- 神奇之处:无论统计多少个元素,最坏情况下仅占用 12 KB 内存,标准误差小于 1%。
- 场景:统计每日搜索关键词的去重数量、统计网站的 UV(独立访客)。
> pfadd hll a b c d
(integer) 1
> pfcount hll
(integer) 4
*键的自动创建与销毁
对于容器类数据类型(Lists, Sets, Hashes, ZSets),Redis 遵循以下规则:
- 自动创建:向不存在的键添加元素时,会自动创建一个空的聚合类型容器。
- 自动删除:当容器变空时,键会被自动从内存中销毁(Streams 除外)。
- 兼容空键:对不存在的键调用只读命令(如 LLEN),其返回结果等同于操作一个空的容器。