1 Redis中的数据类型
Redis中的数据类型可以分为两个大类:
- 核心数据类型:Redis社区版就支持的数据类型。
- 拓展数据类型:Redis Stack 和 Redis Enterprise 中包含的拓展类型。
1.1 核心数据类型
核心数据类型包含以下几种:
String
字符串:最基本的 Redis 数据类型,表示字节序列。Hash
哈希:键值对类型的集合。List
列表:按插入顺序排序的字符串列表。Set
集合:是唯一字符串的无序集合Sorted set
有序集合:唯一字符串的集合,它们通过每个字符串的关联分数来维护顺序。Stream
流:作用类似于仅附加日志,流有助于按事件发生的顺序记录事件,然后将它们联合起来进行处理。Bitmap
位图:方便对字符串执行按位运算Bitfield
位域:Redis 位域有效地对字符串值中的多个计数器进行编码。位域提供原子获取、设置和增量操作,并支持不同的溢出策略Geospatial
地理空间索引:对于查找给定地理半径或边界框内的位置很有用。
1.2 拓展数据类型
拓展数据类型包含以下几种:
JSON
:提供了一种与常见的 JSON 文本文件格式相匹配的数据结构,包括结构化的、分层的数组和键值对象。您可以将 JSON 文本导入 Redis 对象并访问、修改和查询各个数据元素。- Probabilistic data types概率数据类型:这些数据类型使您能够以近似但高效的方式收集和计算统计数据。有以下类型可供选择:
HyperLogLog
:提供大型集合基数(即元素数量)的概率估计。Bloom filter
布隆过滤器:检查集合中是否存在元素。Cuckoo filter
布谷鸟过滤器:检查集合中是否存在元素。它们与布隆过滤器类似,但在功能和性能之间的权衡略有不同。t-digest
:根据数据值流估计百分位数。Top-K
:估计数据点在值流中的排名。Count-min sketch
:估计值流中数据点的频率。
Time series
时间序列:存储和查询带时间戳的数据点。
1.3 通用特性
1.3.1 【聚合数据类型】自动创建与删除Keys
使用Redis List时,不需要手动创建空的列表再添加元素,而不需要在列表为空后手动删除列表。Redis会在我们往列表里添加元素时自动创建列表(如果列表不存在的的话),也会在列表为空时自动删除列表。
This is not specific to lists, it applies to all the Redis data types composed of multiple elements – Streams, Sets, Sorted Sets and Hashes.
这种特性并不只是争对Lists,也适用于所有由多个元素组成的Redis数据类型(如流、集合、有序集合、Hashes)。
基本上我们可以用三个规则来总结:
- 当我们向聚合数据类型添加元素时,如果目标键不存在,则会在添加元素之前创建一个空的聚合数据类型。
- 当我们从聚合数据类型中删除元素时,如果容器内变空,则键会自动销毁。 Stream 数据类型是此规则的唯一例外。
- 调用一个只读命令,比如LLEN(返回列表的长度),或者一个移除元素的写命令(对于一个空的键),总是会产生与键持有空的聚合类型相同的结果,命令期望找到该类型的空聚合。
2 常用数据类型详解
2.1 String
2.1.1 概述
字符串是最基本最简单的Redis数据类型,可以存储字节序列,包括文本、序列化的对象以及二进制数组。通常用于缓存,同时也支持一些附加功能,可以实现计数器和执行按位运算。
类似于go语言里的string。
2.1.2 命令
2.1.3 使用字符串实现计数器
字符串是 Redis 的基本值,使用它也可以执行一些有趣的操作。例如原子增量操作可以用于实现计数器。
相关命令:
- INCR:将字符串值解析为整数,然后将其加一,最后将获得的值设置为新值。
- DECR:将字符串值解析为整数,然后将其减一,最后将获得的值设置为新值。
- DECRBY:将字符串值解析为整数,然后将其减N,最后将获得的值设置为新值。
在内部,它始终是相同的命令,但作用方式略有不同。
这几个操作都是原子的!!!
2.1.4 使用字符串实现简单分布式锁
使用Redis实现分布式锁是很常见的场景。下面是一个简单的基于Redis的分布式锁的解决方案:
-
获取锁:
- 使用
SET key value NX PX milliseconds
命令去尝试设置一个键值对。其中,key
是用于表示锁的唯一标识,value
可以是一个唯一的标识符(如UUID),NX
选项表示仅在键不存在时才设置键值对,PX
选项表示设置键的过期时间(毫秒)。 - 如果该命令返回成功,表示获取到了锁,可以执行需要保护的代码。
- 如果该命令返回失败(表示锁已经被其他实例持有),可以选择重试或等待一段时间后再次尝试获取锁。
- 使用
-
释放锁:
- 使用DEL key命令删除锁的键。
- 释放锁时,确保只有持有锁的实例能够释放它,以避免误删其他实例的锁。
请注意,这只是一个简单的示例,实际上在实现分布式锁时还需要考虑更多的细节,如锁超时时间、宕机处理、锁重入等。更复杂的实现可以使用Redlock或基于Redis的分布式锁库,它们提供了更强大和可靠的分布式锁功能。
2.1.5 使用字符串实现简单时间序列
APPEND 命令可用于创建固定大小样本列表的非常紧凑的表示,通常称为时间序列。每次有新样本到达时,我们都可以使用命令存储它:
APPEND timeseries "fixed-size sample"
访问时间序列中的各个元素并不难:
- 可以使用
STRLEN
来获取样本数。 GETRANGE
允许随机访问元素。如果我们的时间序列具有关联的时间信息,我们可以轻松地实现二分搜索。SETRANGE
可用于覆盖现有时间序列。
这种模式的局限性是我们被迫进入仅追加操作模式,无法轻松地将时间序列切割到给定大小,因为Redis目前缺乏能够修剪字符串对象的命令。然而以这种方式存储的时间序列的空间效率是显著的。
可以根据当前 Unix 时间切换到不同的键,通过这种方式,每个键可能只有相对少量的样本,避免处理非常大的键,并使这种模式更适合分布在许多 Redis 实例上。
2.1.6 使用字符串实现简单的速率限制器
使用Redis的计数器实现固定窗口速率限制器:
-
创建一个Redis键来表示计数器,例如rate_limiter:api_endpoint.
-
在每个请求到达时,使用INCR命令递增计数器的值,并获取递增后的值。
-
检查递增后的值是否超过了限制阈值。如果超过了阈值,则拒绝这个请求。
-
使用TTL(生存时间)设置键的过期时间,使其在一个固定的时间窗口后自动过期。例如,如果时间窗口是1秒,则可以设置TTL为1秒。
示例代码(使用Python和Redis的redis-py库):
import redis
def rate_limiter(api_endpoint, limit, window):
redis_client = redis.Redis()
key = f"rate_limiter:{api_endpoint}"
count = redis_client.incr(key)
if count > limit:
return "Rate limit exceeded"
redis_client.expire(key, window)
return "Success"
2.1.7 提示及注意事项
-
按位运算操作
对字符串执行按位运算,请查看Bitmaps位图章节。
-
字符串限制
默认情况下,单个 Redis 字符串最大可为 512 MB。
-
性能
大多数字符串操作都是 O(1),这意味着它有很高的性能。然而也需要注意SUBSTR、GETRANGE 和 SETRANGE 命令,时间复杂度为 O(n),在处理大字符串时,这些随机访问字符串命令可能会导致性能问题。
-
序列化结构数据存储
如果您将结构化数据存储为序列化字符串,您还可以考虑使用 Redis 哈希或 JSON。
2.2 Hash
2.2.1 概述及使用场景
映射是键值对集合的数据类型。
类似于golang里的map[string]string
Redis 中的 Hash(哈希)数据结构是一个 key-value 的无序散列表。它适合存储和操作一些具有结构化数据的对象。以下是一些 Redis Hash 的常见应用场景:
- 对象存储
Redis Hash 可以用于存储和管理对象的属性。每个对象可以用一个 Hash 来表示,其中的字段可以表示对象的属性,字段的值则是属性的值。这样便于对对象进行读取、更新和查询。 - 缓存存储
Redis Hash 可以用于缓存数据。你可以将一些常用的数据结构化并存储为 Hash 对象,每个字段表示一个缓存项的键,对应的值则是缓存项的内容。这样可以提高读取和更新缓存的效率。 - 用户属性存储
对于需要存储用户属性的应用程序,可以使用 Redis Hash 来存储用户的属性信息。每个用户可以用一个 Hash 对象表示,字段表示属性名称,字段的值则是对应的属性值。这样可以方便地查询和更新用户属性。 - 计数器
Redis Hash 可以用于实现计数器功能。你可以使用 Hash 对象来存储某个实体的计数值,每个字段表示一个计数器的键,对应的值则是计数器的值。通过对计数器进行递增或递减操作,可以方便地实现计数功能。 - 地理位置信息存储
如果需要存储和查询地理位置信息,可以使用 Redis Hash。你可以将每个位置表示为一个 Hash 对象,其中的字段表示经度和纬度等属性,字段的值则是对应的数值。这样可以方便地存储和查询位置信息。
2.2.2 命令
2.2.3 提示和注意事项
-
性能
大多数 Redis 哈希命令的复杂度都是 O(1)。 一些命令(例如 HKEYS、HVALS 和 HGETALL)的复杂度为 O(n),其中 n 是字段值对的数量。 -
限制
每个哈希最多可以存储 4,294,967,295 (2^32 - 1) 个字段值对。实际上,您的哈希值仅受托管 Redis 部署的虚拟机上的总内存的限制。
2.3 List
2.3.1 概述
Redis Lists 是字符串值的链表结构,常用于:
- 实现堆栈和队列
- 为后台工作系统构建队列管理
Redis 列表采用链表实现,因为对于数据库系统来说,能够以非常快的方式将元素添加到非常长的列表中至关重要。另一个强大的优势是Redis 列表可以在恒定时间内以恒定长度获取。
当快速访问大量元素的中间部分很重要时,可以使用另一种数据结构——有序集。
2.3.2 命令
2.3.2 使用列表实现简单消息队列
List 可以用作简单的消息队列,其中生产者将消息推送到列表的一端,而消费者从另一端消费消息。这样可以实现基于发布/订阅模型的简单消息传递。
- 生产者使用
RPUSH
命令将消息推送到列表的末尾 - 消费者使用
LPOP
、BLPOP
命令从消息队列的头部获取消息
2.3.3 使用列表实现最新消息/动态
可以使用 List 存储最新的消息、动态或帖子。每当有新的消息到达时,可以将其插入到列表的开头,同时限制列表的长度,以保持只有最新的消息在其中。
- 发布消息时,将消息使用
LPUSH
命令推送到列表,同时使用LTRIM
命令控制列表的长度。 - 获取最新消息/动态时,使用
LRANGE
命令获取最新的消息/动态。
2.3.4 使用列表实现订阅者列表
在发布/订阅模型中,可以使用List
存储订阅者的信息。每当有新的订阅者加入或离开时,可以通过插入或删除操作来维护订阅者列表。
- 使用 Redis 列表来存储订阅者的信息,每个订阅者作为列表的一个元素。
- 当有新订阅者加入时,使用
RPUSH
命令将其添加到列表的末尾。 - 当订阅者取消订阅时,可以使用
LREM
命令将其从列表中移除。 - 使用
LRANG
E 命令可以获取整个列表或指定索引范围内的订阅者。
2.3.5 使用列表实现分页查询
List 支持从两端进行快速的插入和删除操作,这使得它非常适合用于实现分页查询功能。可以将查询的结果存储在一个 List 中,并根据需要从列表的两端获取结果。
- 使用 Redis 列表来存储要分页查询的数据,每个数据作为列表的一个元素。
- 使用
RPUSH
命令将新的数据添加到列表中。 - 使用
LRANGE
命令根据指定的索引范围获取指定页码的数据。(根据页码和每页数量,计算要获取数据的起始索引和结束索引。)
2.3.6 提示及注意事项
-
列表限制
Redis Lists最大元素个数为2^32 - 1 (4,294,967,295) 。
-
性能
访问其头部或尾部的列表操作的复杂度为 O(1),这意味着它们非常高效。然而,操作列表中元素的命令通常是 O(n)。这些示例包括 LINDEX、LINSERT 和 LSET。运行这些命令时请务必小心,尤其是在大型列表上操作时。
-
备选方案
当您需要存储和处理一系列不确定的事件时,请考虑将 Redis 流作为列表的替代方案。
2.4 Set
2.4.1 概述及使用场景
Redis Set集合是唯一字符串的无序集合。
类似于java里的HashSets
Redis 中的 Set(集合)数据结构是一个无序、唯一值的集合。它提供了一些操作来处理集合,包括添加元素、删除元素、检查成员存在性、集合间的交集、并集和差集等。以下是一些 Redis Set 的常见应用场景:
- 标签系统
可以使用 Set 存储和管理实体的标签。每个实体都有一个唯一的标识符,而这些标识符可以添加到相应实体的标签集合中。你可以使用 Set 提供的操作来查找共享标签、查找包含特定标签的实体等。 - 用户关注列表
对于社交媒体或关注系统,可以使用 Set 存储用户的关注列表。每个用户的关注列表是一个集合,其中包含他们所关注的其他用户的标识符。你可以使用 Set 提供的操作来查找共同的关注者、查找某个用户的关注者等。 - 唯一值的存储
如果你需要存储唯一的值,并且对于每个值的快速插入、查找和删除操作很重要,那么使用 Redis Set 是一个不错的选择。Set 保证值的唯一性,并提供了高效的集合操作。 - 集合运算
Redis Set 还提供了集合间的交集、并集和差集等操作。这些操作可以用于计算共同的元素、合并集合中的元素等。比如,在电子商务中,可以使用交集操作来查找同时满足多个筛选条件的商品。 - 标记已处理的数据
当需要对一些数据进行处理,并且需要跟踪已处理和未处理的数据时,可以使用 Set 来存储已处理的数据。每次处理完数据后,将其标识符添加到 Set 中,这样就可以轻松地检查数据是否已经处理过。
2.4.2 命令
2.4.3 提示和注意事项
-
限制:
Redis 集的最大大小为 2^32 - 1 (4,294,967,295) 个成员。
-
性能:
大多数集合操作(包括添加、删除以及检查某项是否是集合成员)的复杂度都是 O(1)。这意味着他们的效率很高。但是,对于具有数十万或更多成员的大型集,在运行 SMEMBERS 命令时应小心谨慎。该命令的复杂度为 O(n),并在单个响应中返回整个集合。作为替代方案,请考虑 SSCAN,它允许您迭代地检索集合的所有成员。
-
替代方案
对大型数据集(或流数据)设置成员资格检查可能会使用大量内存。如果您担心内存使用情况并且不需要完美的精度,请考虑使用 Bloom 过滤器或 Cuckoo 过滤器作为集合的替代方案。
Redis 集经常用作一种索引。如果您需要对数据进行索引和查询,请考虑 JSON 数据类型以及搜索和查询功能。
2.5 Sorted-set
2.5.1 概述及使用场景
有序集合是一个按照给定分数(score)排序的不重复字符串的集合。如果有多个字符串的分数相同,就按照字典序排序。
有序集合的典型应用场景:
- 排行榜
您可以使用排序集轻松维护大型在线游戏中最高分数的有序列表。 - 速率限制器
您可以使用排序集构建滑动窗口速率限制器,以防止过多的 API 请求。
2.5.2 命令
2.5.3 提示和注意事项
- 性能
大多数排序集操作的复杂度为 O(log(n)),其中 n 是成员数。
运行具有较大返回值(例如,数万或更多)的 ZRANGE 命令时请务必小心。该命令的时间复杂度为 O(log(n) + m),其中 m 是返回结果的数量。 - 备选方案
Redis 排序集有时用于索引其他 Redis 数据结构。如果您需要对数据进行索引和查询,请考虑 JSON 数据类型以及搜索和查询功能。