跳到主要内容

数据类型

2025年02月25日
柏拉文
越努力,越幸运

一、String


Redis 中,string 类型是最基本且最常用的数据类型之一。它可以存储任意的数据,比如文本、数字、二进制数据等。每个键值对中的值都可以是字符串类型,并且 Redis 中的所有数据结构都是基于键值对的模型。

Redis String 类型是由一个键和一个值组成的。键是唯一的,值可以是字符串(字符、数字等)或二进制数据(如图片、音频等)。Redis 中的字符串最大可以存储 512 MB 的数据。Redis 提供了许多针对字符串的操作,可以对字符串进行设置、获取、修改、追加、截取、增减操作等。对字符串的操作(例如 SET, GET, INCR, DECR 等)都是原子操作,即操作是不可中断的,要么成功,要么失败。

Redis String 适用于存储简单的、独立的数据,当你需要快速存取整个值时,String 类型是更好的选择。比如说计数器; 页面缓存、Token 缓存; 存储 JSON 字符串; 分布式锁等; 如果是对象数据, 如果你的字段非常少,或者数据规模较小时,String 类型更简单且节省存储、开销较少; 需要频繁访问整个对象,而不是单个字段时,使用 String 可以减少 Redis 操作次数,提升性能。

Redis String 优化策略: Redis String 底层存储编码为 int 整型编码embstr 嵌入式字符串编码(<= 44字节)raw 常规字符串编码(>44 字节)Redis 会根据存储的数据内容和长度,自动在这些编码之间进行切换,以提高内存利用率和访问效率。使用 embstr 编码,可以将字符串数据存储在同一个连续的内存块中,减少内存碎片,提高访问速度,所以, embstr 性能更优,占用较少的 CPU 和内存。而 raw 需要分配更多内存,适合大数据存储。 查看 Redis String 编码类型:

set key:1 "hello"

object encoding key:1

set key:2 100

object encoding key:2

二、Hash


Redis 中,hash(哈希)是一种用于存储 键值对集合 的数据类型,类似于一个小型的 key-value 字典(对象)。它特别适合存储具有多个属性的对象,例如用户信息、产品详情等。在 Redis 中,哈希 是以字段(field)和值(value)的形式存储在一个主键(key)下的。例如,可以使用 hash 来存储用户的信息,如 user:1001,并包含多个字段:nameageemail 等。

Redis Hash 存储结构类似于 JSON 对象或字典。比如: user:1001 → { "name": "Alice", "age": "25", "email": "alice@example.com" }Redis Hash 高效存储, 如果哈希中的字段数量较少(小于 512 个字段,字段和值总大小不超过 64KB),Redis 会使用特殊的 ziplist(压缩列表) 实现,节省内存。Redis 适合存储结构化数据, 可用于存储对象数据,如用户信息、配置项等,避免使用多个字符串键。Redis Hash 支持部分字段操作,避免整体操作, 可以只修改、获取某个字段,而不影响整个哈希数据。Redis 字段增量操作支持, Redis 提供对数值字段的原子递增/递减操作,方便计数操作。

Redis Hash 适用于存储结构化数据,如果你的数据像对象那样包含多个属性,使用 Hash 更合适。比如说: 用户信息存储; 对象属性操作CRUD, 需要频繁更新或获取对象的部分字段,例如商品库存、价格信息等; 动态配置管理, 需要存储多种配置参数并且可能会经常调整,比如应用配置、用户偏好设置等; 排行榜存储, 存储用户游戏统计数据,如分数、等级、胜场等,方便查询和更新; 避免键的爆炸, 如果你的数据非常多且字段固定,使用 Hash 可以减少 Redis 的键数量,避免单独存储多个 String 类型带来的管理负担。在存储大量类似数据时,避免使用大量独立的 String 类型键,而是将数据组织成 Hash 类型,以减少 Redis 管理的键数量,提高查询效率, 降低键的数量,避免“键爆炸”。合理使用哈希字段,避免过大对象, 虽然 Hash 可以存储大量字段,但单个 Hash 过大可能导致:LRU 淘汰策略时,整个 Hash 被一次性移除,影响业务稳定性, 迁移、持久化时增加 Redis 负载, 所以, 设计合理的存储粒度,避免单个 Hash 超过 10 万个字段。

Redis Hash 优化策略: Redis 内部会对小型 Hash 采用特殊的优化机制,即压缩列表(ZipList), 它可以将数据存储在同一个连续的内存块中, 减少内存碎片以减少内存开销, 触发条件为: 字段数量(hash-max-ziplist-entries) ≤ 512(默认值), 每个字段和值的大小 (hash-max-ziplist-value) ≤ 64 字节(默认值)。但是, ZipList 读写有指针位移, 且新增删除有内存重分配。如果数据超出阈值,Redis 将自动切换为标准的 Hash Table 结构,占用更多的内存。

  • 调整参数以适应业务需求:

    CONFIG SET hash-max-ziplist-entries 1024
    CONFIG SET hash-max-ziplist-value 128
  • 检查是否启用 ZipList 优化: 如果返回 ziplist,说明正在使用压缩存储。

    OBJECT ENCODING user:1001

Redis Hash 分片策略: 将大数据拆分到多个 Hash, 每个小段尽可能符合 ZipList 的优化范围, 因为 ZipList 非常节省内存空间。但是 ZipList 读写有指针位移, 且新增删除有内存重分配, 会有性能问题。所以, 我们权衡之后, 选择第三种最好。

// picId => usesrId, 大约有 100 万 数据量, 有如下三种方案:

set pic:{picId} user:{userId} // 全部 string, 浪费内存

hset allPics pic:{pidId} user:{userId} // 一个 hash, hash allPics 元素个数已经有 100 万多个, 是一个超大的 Big Key, 浪费内存。

hset segment:{picId/100} pic:{picId%100} user:{userId} // hash 分片

// 三种方案的结果, hash 分片消耗的内存是最小的, 且比前两种少了好几倍。一个 hahs 消耗的内存是最多的, 是一个超大的 Big Key。

三、List


RedisList(列表) 允许你在列表的两端进行高效的插入和删除操作。具体来说,Redis 提供了 LPUSHRPUSH 命令用来在列表的头部或尾部插入元素。Redis List 可以实现队列(FIFO)和栈(LIFO)的功能。通过 LPUSHRPOP 可以构建一个栈,而通过 LPUSHLPOP 则可以构建一个队列。

Redis List 可以用于任务队列, 使用 LPUSHRPOP 实现 先进先出的队列(FIFO; 可以日志记录, 可以用 List 存储日志数据,利用 LTRIM 保持一定长度的日志记录; 可以用于缓存队列, 在需要缓存最新数据的场景下,可以利用 List 类型存储和移除数据; 可以用于消息队列, 使用 RPOPLPUSH 实现消息队列,确保消息处理的可靠性。

Redis 优化策略: Redis 会根据 List 的大小和元素的长度来自动选择编码方式。当列表包含的元素少于 512 个,每个元素的大小不超过 64 字节, 使用 ZipList 存储, ZipList 是压缩列表(连续内存区域), 更节省内存,适用于小数据量, 插入和删除效率较低,查询效率较高。当列表的元素个数或元素长度超出 ZipList 的限制时,Redis 会将其编码方式自动切换为 LinkedListLinkedList 为双向链表, 每个元素有一个指向前一个元素和后一个元素的指针,因此可以在 O(1) 时间复杂度内完成头部或尾部的插入和删除操作。 相比于 ZipList,占用更多内存,插入和删除效率较高,查询效率较低。注意, 在 Redis 早期版本中,List 数据结构是通过双向链表(LinkedList)实现的,但这种实现有一些潜在的问题和不足,尤其是在内存效率和性能上。具体来说,双向链表存储每个元素时需要额外的内存来存储指针(前后指针),而且链表在内存中的非紧凑存储方式会导致较高的内存开销。为了优化这些问题,Redis 在版本 3.2 引入了 QuickList,它通过将多种数据结构的优点结合起来,提供了更高效的内存利用和更好的性能。

四、Set


RedisSet 数据结构是一种无序集合,包含多个不重复的元素,支持高效的集合操作,如交集、并集、差集等。Set 具有非常高效的插入、删除和查找操作,通常用于需要去重或者进行集合运算的场景。

Redis 结构特点: 无序, Set 中的元素没有顺序。你不能按照插入的顺序访问元素,但可以通过操作获取 Set 中的所有元素,或者执行集合运算(如交集、并集、差集); 不重复, Set 中的元素是唯一的。插入相同的元素不会改变 Set 的内容。这是 Set 最重要的特性,通常用于去重。

Redis 优化策略: Redis 中的 Set 数据结构通常使用 哈希表(Hash Table整数集合(IntSet 来实现,具体实现依赖于 Set 的大小和元素类型。对于包含小整数(例如小于某个阈值的整数)的 SetRedis 会使用 IntSet 编码,这是一种更高效的内存表示方式。IntSet 是针对整数类型元素的优化。当 Set 中的元素全部为整数时,Redis 会自动使用 IntSet 编码来节省内存。IntSet 使用压缩方式存储整数集合,通常比哈希表更加节省内存。如果 Set 中只包含整数,IntSet 编码能够大大减少内存的占用。IntSet 编码只适用于整数类型的元素,当包含非整数类型时,Redis 会自动切换回哈希表实现。对于大多数的 SetRedis 使用哈希表来实现。哈希表是一种 key-value 存储结构,其中每个元素的哈希值会被用作存储位置的依据。哈希表提供了 O(1) 时间复杂度的插入、删除和查找操作,使得 Redis Set 在这些操作上表现非常高效。当 Set 中的元素数量较大时,Redis 会通过哈希表来存储元素,从而支持快速访问和操作。内存优化, Redis 通过智能选择哈希表和 IntSet 编码,在需要时自动切换,实现了内存的节省和高效的访问。例如,IntSet 编码显著减少了存储小整数集合时的内存占用。渐进式更新, Redis 使用渐进式的哈希表扩展策略,哈希表的大小会根据元素的插入自动调整,保证了在处理大量元素时的高效性。

五、ZSet


RedisZset(有序集合,Sorted Set)是一种类似于 Set 的数据结构,它和 Set 相比最大的区别在于 Zset 中的元素是有序的。每个元素在 Zset 中都会关联一个 分数(score,这个分数用于排序,因此可以在 Zset 中进行基于分数的排序操作。Zset 结合了 Set 和列表(List)的特性:去重和有序性。

Zset 结构特点: 有序性, Zset 中的每个元素都有一个与之关联的分数(score)。分数是一个 双精度浮点数,Redis 会根据分数来对 Zset 中的元素进行排序,默认是按从小到大的顺序, 如果两个元素具有相同的分数,它们在 Zset 中的顺序是根据元素的字典顺序来决定的; 唯一性, Zset 中的每个元素都是唯一的, 你不能在同一个 Zset 中插入重复的元素,但可以更新元素的分数。元素的名字(值)是唯一的,但分数可以有重复; 高效操作, Redis 使用跳表(skiplist)和哈希表(hash table)结合的方式来实现 Zset,支持高效的元素插入、删除、查找和范围查询; 范围查询, 由于 Zset 是有序的,Redis 提供了高效的命令来执行范围查询, 你可以按照分数范围或者按排名范围获取 Zset 中的元素, 支持按分数区间、排名区间等多种方式进行查询。

Zset 优化策略: Redis 使用 跳表(Skiplist哈希表(Hash Table 结合的方式来实现 Zset(有序集合),这是为了兼顾高效的元素插入、删除和范围查询操作。跳表和哈希表各自有不同的优点,Redis 将两者结合,以在性能和内存使用上达到最佳平衡。具体来说,Redis 中的每个 Zset 元素都有一个唯一的值(成员)和一个关联的分数(score)。Zset 的每个成员在跳表和哈希表中都有对应的存储。具体工作流为: 插入元素时, Redis 会将元素的值和分数同时存入跳表和哈希表中, 在跳表中,元素根据其分数被插入到合适的位置,保持跳表的有序性, 在哈希表中,元素的值作为 key,分数作为 value 存储,确保元素的快速查找; 删除元素时, Redis 会从跳表和哈希表中删除该元素, 在跳表中,删除元素的过程是通过查找其分数所在的位置,删除该节点, 在哈希表中,删除对应的 key-value 键值对; 查找元素时, Redis 会首先通过哈希表检查元素是否存在。这一步是 O(1) 时间复杂度的操作, 如果元素存在,Redis 会在跳表中找到对应的节点,从而获得该元素的分数,或者获取它在 Zset 中的排名; 更新元素分式时, 如果需要更新元素的分数,Redis 会在哈希表中更新该元素的分数, 在跳表中移除旧的分数节点,并将元素插入到新的位置,从而保持跳表的顺序; 范围查询, 在范围查询(例如,按分数范围获取元素)时,Redis 会在跳表中高效地查找并返回符合条件的元素, 由于跳表的排序结构,可以在 O(log N) 时间内完成分数范围的查找

  • 跳表Skiplist: 是一种有序的数据结构,用于快速查找、插入和删除元素,时间复杂度为 O(log N),因此适合用于实现有序集合。跳表由多个层级的链表组成。每个元素在不同的层级链表中都有索引,元素通过不同层级的链表向下进行连接。跳表的底层链表包含所有的元素,每上一层链表都只包含部分元素,通过逐层“跳跃”可以加速查询。在 Zset 中,跳表的每个节点存储着一个元素的分数(score)和一个指向该元素的指针(成员值)。通过分数排序,跳表能高效地获取排名、范围查询等操作。跳表, 用于实现 Zset 的有序性,按照分数(score)排序。

  • 哈希表Hash Table: 提供了 O(1) 时间复杂度的查找、插入和删除操作,但它并不提供排序功能。因此,如果我们仅使用哈希表,就无法满足 Zset 的排序要求。哈希表用于存储 Zset 元素的成员(即值)。哈希表的 key 是元素的值,value 是元素的分数。由于哈希表查找是 O(1) 时间复杂度,所以它可以高效地检查元素是否存在于 Zset 中,并避免重复插入。哈希表的作用主要是帮助 Redis 快速查找 Zset 中的成员,并且能高效地进行元素的插入、删除操作。每次插入或删除元素时,Redis 会同时更新哈希表和跳表。哈希表, 用于提供高效的成员查找,避免重复的成员插入。

六、BitMap


Redis BitmapRedis 中的一种数据结构。位图(Bitmap 其实是利用 Redis 的字符串(String)类型作为存储容器,每个字符的一个字节(8个二进制位)表示一个二进制值(01)。每个二进制位(bit)可以表示一个布尔值,因此它是一个非常高效的空间节省结构,适合存储大量的 01。因此, Redis 位图是一种非常高效的工具,尤其适用于需要快速操作大量布尔值数据的场景,它能够显著提高存储和计算的效率。

位图(Bitmap)使用场景: 非常适合存储布尔类型的数据,常见的使用场景包括

  1. 用户活跃状态记录:例如,记录某用户是否在某天活跃,10000 个用户可以用一个 10000 位的位图表示。

  2. 权限控制:位图可以有效地表示多个权限的开启或关闭状态。

  3. 日志分析:例如,记录哪些用户访问过特定的页面,哪些页面被浏览过。

  4. A/B 测试:用于跟踪每个用户的分组,存储用户是否属于某个测试组。

七、HyperLogLog


HyperLogLog 是一种高效的 基数估算 Cardinality 或不同元素数量的概率 数据结构,它通过牺牲一定的精度来节省内存,适用于对大规模数据集进行基数估算的场景。Redis 提供了对 HyperLogLog 的支持,允许用户在进行大数据分析时能够以低成本高效地进行基数统计。它的主要特点是能够以非常小的内存消耗估算集合中不重复元素的数量,适用于大数据分析、日志分析、流量统计等场景。

HyperLogLog 优势: 1. 节省内存, HyperLogLog 通过概率算法来估算基数,使用的内存空间是固定的,与集合中元素的数量无关。对于大规模的数据集,内存消耗非常低; 2. 近似准确, 它的误差通常较小,适合大规模的基数估算场景,比如统计大量访问 IP 地址、网站访问量、独立用户数等; 3. 处理大规模数据, HyperLogLog 在处理大规模数据时,能够有效提供准确的估算结果,而不需要处理所有数据,避免了存储所有数据所带来的性能瓶颈。

HyperLogLog 原理: HyperLogLog 采用了一个基于哈希的算法,使用多个 来记录数据。每个桶存储的是该桶对应哈希值的一个特定部分的最大值。通过对这些桶的统计,HyperLogLog 能够对基数进行高效的估算。

  1. 哈希化数据:对于每个输入的元素,HyperLogLog 使用哈希函数计算出一个哈希值。

  2. 分配到桶:将哈希值分配到若干个桶中。每个桶会存储该哈希值中的某一部分信息。

  3. 统计最大连续零的数量: HyperLogLog 根据哈希值中的位模式来计算连续的零的数量,并记录这个值。这个零的数量间接反映了输入数据的分布情况。

  4. 估算基数:通过聚合各个桶中的统计数据,HyperLogLog 能够估算出集合的基数。

HyperLogLog 使用场景:

  1. 统计独立用户数(UV:例如,你想估算网站在一天内的独立访客数,使用 HyperLogLog 可以在很小的内存消耗下快速得出结果。

  2. 估算网站或应用的独立 IP 数量:比如统计不重复的 IP 地址数量,而不需要存储每一个 IP

  3. A/B 测试:用于统计每个实验组的唯一用户数量。

  4. 数据流分析:比如计算实时数据流中的不同事件或事件发生次数。

八、Geo


Redis GEORedis 提供的一个用于处理 地理空间(Geo-spatial 数据的功能。它允许用户存储、查询、分析和操作地理位置相关的信息,如经纬度、地点之间的距离、周围的位置等。Redis GEO 数据类型使用了一种高效的编码方式,可以支持快速的地理查询,如计算两个位置之间的距离、查找周围的地理位置等。它通常用于实现基于位置的服务,如附近搜索、地图标注、地理位置追踪等。Redis GEO 基于 Geohash(一种将经纬度转换为字符串的编码方式)来存储地理位置信息。每个位置存储的是其经纬度,并能够对这些位置进行高效的计算和查询。

GEO 优势:

  1. 高效Redis GEO 使用 Geohash 编码和高效的数据结构进行存储和查询,性能非常高,可以支持高并发的地理空间查询。

  2. 节省内存Redis GEO 存储的地理位置信息与传统的存储方法相比,能够节省大量内存,适合大规模地理位置数据存储。

  3. 灵活性:支持多种查询方式,如按经纬度范围查询、按成员周围查询、按距离排序等,适应不同的应用需求。

GEO 场景:

  1. 附近搜索: 在电商、旅游、社交平台等应用中,用户可以搜索距离某个地点最近的商店、餐厅、景点等。Redis GEO 可以提供高效的地理查询。

  2. 基于位置的推送通知: 可以根据用户的实时位置推送附近的促销信息或事件通知。

  3. 地图服务和导航系统: 在地图导航应用中,Redis GEO 可以帮助快速计算当前位置与目的地之间的距离、查询附近的兴趣点等。

  4. 共享经济应用: 如打车、共享单车等应用,可以通过 Redis GEO 查询用户和车辆/单车之间的距离,实时更新位置。