String 类型的底层的数据结构实现主要是 int 和 SDS(简单动态字符串)
- SDS 不仅可以保存文本数据,还可以保存二进制数据。因为
SDS
使用len
属性的值而不是空字符来判断字符串是否结束,并且 SDS 的所有 API 都会以处理二进制的方式来处理 SDS 存放在buf[]
数组里的数据。所以 SDS 不光能存放文本数据,而且能保存图片、音频、视频、压缩文件这样的二进制数据。 - SDS 获取字符串长度的时间复杂度是 O(1)。因为 C 语言的字符串并不记录自身长度,所以获取长度的复杂度为 O(n);而 SDS 结构里用
len
属性记录了字符串长度,所以复杂度为O(1)
。 - Redis 的 SDS API 是安全的,拼接字符串不会造成缓冲区溢出。因为 SDS 在拼接字符串之前会检查 SDS 空间是否满足要求,如果空间不够会自动扩容,所以不会导致缓冲区溢出的问题。
SDS底层实现
struct sdshdr {
// 字符串长度
int len;
// 未使用的空间
int free;
// 字符数组,存储实际的字符串数据
char buf[];
};
应用场景
- 缓存对象
字符串数据类型常用于缓存一些常用的、计算或查询代价较高的数据,例如数据库查询结果、网页内容、API 响应等
- 常规计数
利用 Redis 的原子自增(INCR)和自减(DECR)操作,可以实现计数器功能,例如网站访问量、点击量、点赞数等
- 分布式锁
通过 Redis 的 SETNX(当 key 不存在时设置值)操作,可以实现分布式锁的功能,保证在分布式环境下多个进程对共享资源的互斥访问
- 共享session信息
使用 Redis 存储用户会话信息,可以实现会话的跨服务器共享,适用于分布式应用和负载均衡场
List
底层实现
List 类型的底层数据结构是由双向链表或压缩列表实现的:
- 如果列表的元素个数小于
512
个(默认值,可由list-max-ziplist-entries
配置),列表每个元素的值都小于64
字节(默认值,可由list-max-ziplist-value
配置),Redis 会使用压缩列表作为 List 类型的底层数据结构; - 如果列表的元素不满足上面的条件,Redis 会使用双向链表作为 List 类型的底层数据结构;
但是在 Redis 3.2 版本之后,List 数据类型底层数据结构就只由 quicklist 实现了,替代了双向链表和压缩列表。
压缩列表底层实现
Redis 的压缩列表(Ziplist)是一种特殊的内存紧凑型链表实现,用于表示 Redis 中的列表(List)数据类型。当列表中的元素数量较少且元素长度较短时,Redis 会选择压缩列表作为底层实现,以节省内存空间和提高访问效率。
压缩列表是一个连续的内存块,其中包含了一个或多个列表项(entry),每个列表项存储了一个字符串值。列表项之间没有指针连接,而是通过特殊的编码方式在内存中连续存储。以下是压缩列表的主要特点和底层实现:
- 连续内存存储:压缩列表将所有列表项存储在一个连续的内存块中,而不是使用分散的内存空间。这样可以减少内存碎片,提高内存利用率。
- 变长编码:为了减小存储空间占用,压缩列表使用变长编码(Variable Length Encoding)表示列表项的长度和元素值。变长编码根据数值的大小选择不同长度的字节来表示,较小的数值占用的字节更少。这样,较短的字符串可以占用较少的内存空间。
- 双向遍历:压缩列表支持双向遍历,即可以从头到尾遍历,也可以从尾到头遍历。这使得 Redis 的列表操作,如 LPOP 和 RPOP,可以在常数时间内完成。
- 动态调整:当向压缩列表中添加或删除元素时,压缩列表会自动调整内存空间。如果空间不足,压缩列表会分配更多的内存;如果空间过多,压缩列表会释放多余的内存。这样可以保证压缩列表始终保持紧凑的内存布局。
压缩列表的底层实现包括以下几个部分:
-
zlbytes:表示整个压缩列表占用的字节数,用于快速计算内存大小和分配新的内存空间。
-
zltail:表示压缩列表中最后一个列表项的偏移量,用于快速定位最后一个元素。
-
zllen:表示压缩列表中的列表项数量,用于快速获取列表的长度。
-
entries:表示压缩列表中的实际列表项,每个列表项包括以下几个部分:
- prevlen:表示前一个列表项的长度,用于从尾到头遍历压缩列表。
- encoding:表示当前列表项的编码方式,包括字符串长度和整数类型。
- content:表示当前列表项的实际字符串值。
总之,Redis 的压缩列表是一种内存紧凑型的连续存储结构,通过变长编码、双向遍历和动态调整等技术实现了高效的列表操作。在列表元素较少且长度较短的场景下,压缩列表可以节省内存空间,提高访问效率。
双向链表底层实现
Redis 中的双向链表(doubly linked list)作为 List 数据类型的另一种底层实现,主要用于存储较大的列表元素。相比于压缩列表,双向链表使用额外的指针来连接各个元素,支持快速的插入和删除操作。以下是双向链表的主要特点和底层实现:
- 双向指针:每个链表节点包含两个指针,一个指向前一个节点(prev),一个指向后一个节点(next)。这样可以方便地进行双向遍历,从而实现双端操作,如 LPOP 和 RPOP。
- 动态内存分配:双向链表的每个节点都是动态分配的,当添加或删除元素时,链表会自动分配或释放相应的内存空间。这使得链表可以灵活地扩展和收缩,适应不同大小的数据需求。
- 支持较大元素:由于双向链表使用额外的指针来连接各个节点,因此它可以支持较大的元素。相比于压缩列表,双向链表在存储较大元素时具有更好的性能和可扩展性。
双向链表的底层实现主要包括以下几个部分:
-
listNode:链表节点结构,包含以下几个字段:
- prev:指向前一个节点的指针。
- next:指向后一个节点的指针。
- value:存储节点值的指针,通常是一个字符串或其他数据类型。
-
list:链表结构,包含以下几个字段:
- head:指向链表头部节点的指针。
- tail:指向链表尾部节点的指针。
- len:表示链表中节点的数量。
- dup、free、match:链表节点值的复制、释放和比较函数指针,用于实现自定义的数据处理逻辑。
双向链表提供了一系列操作函数,如链表创建、销毁、插入、删除、查找等。这些函数实现了 Redis 列表所需的基本操作,满足了各种场景下的使用需求。
总之,Redis 的双向链表是一种灵活的动态数据结构,通过双向指针和动态内存分配实现了高效的列表操作。在存储较大元素和较长列表时,双向链表可以提供较好的性能和可扩展性。
quicklist底层实现
Redis 的 Quicklist 是一个列表数据类型的底层实现,它综合了压缩列表(Ziplist)和双向链表的优点,提供了一种内存紧凑且访问高效的数据结构。Quicklist 在 Redis 3.2 及以后的版本中作为默认的列表实现。
Quicklist 本质上是一个双向链表,其中每个链表节点(QuicklistNode)存储一个压缩列表(Ziplist)。Quicklist 通过划分多个压缩列表来存储列表元素,既保留了压缩列表的内存紧凑性,又利用双向链表实现了快速的插入和删除操作。以下是 Quicklist 的主要特点和底层实现:
- 压缩列表划分:Quicklist 将列表元素分散到多个压缩列表中存储,每个压缩列表的大小受到配置参数
list-max-ziplist-size
的限制。当一个压缩列表达到最大大小时,Quicklist 会自动创建一个新的压缩列表来存储更多的元素。 - 双向链表结构:Quicklist 使用双向链表连接各个压缩列表节点,支持双向遍历和快速插入、删除操作。这使得 Quicklist 在处理较大元素和较长列表时具有较好的性能和可扩展性。
- 动态调整:Quicklist 会根据列表操作动态调整其内部结构。当向 Quicklist 中添加或删除元素时,它会自动分配、合并或释放压缩列表节点。这样可以确保 Quicklist 始终保持紧凑的内存布局,同时满足不同大小的数据需求。
- 惰性删除:当删除 Quicklist 中的元素时,它采用惰性删除策略。删除操作只会标记元素为已删除,而不会立即释放内存空间。只有当压缩列表节点的空间利用率过低时,Quicklist 才会触发内存回收操作,合并相邻的压缩列表节点以释放多余的内存空间。
Quicklist 的底层实现主要包括以下几个部分:
-
quicklistNode:Quicklist 节点结构,包含以下几个字段:
- prev:指向前一个节点的指针。
- next:指向后一个节点的指针。
- ziplist:指向压缩列表的指针,存储实际的列表元素。
- count:表示压缩列表中元素的数量。
-
quicklist:Quicklist 结构,包含以下几个字段:
- head:指向 Quicklist 头部节点的指针。
- tail:指向 Quicklist 尾部节点的指针。
- count:表示 Quicklist 中总元素的数量。
- len:表示 Quicklist 中节点的数量。
- fill:表示每个压缩列表节点的最大元素数量,由配置参数
list-max-ziplist-size
控制。
总之,Redis 的 Quicklist 是一种高效的列表实现,它综合了压缩列表和双向链表的优点,通过多个压缩列表节点和双向链表结构实现了内存紧凑且访问高效的列表操作。在处理各种规模的列表数据时,Quicklist 都能提供较好的性能和可扩展性。
应用场景
Redis List 是一种有序的集合数据类型,提供了多种元素插入、删除、获取和遍历的操作。由于其高效且灵活的特性,Redis List 可以应用于多种场景,以下是一些常见的应用场景:
- 消息队列:Redis List 可用作简单的消息队列,支持生产者-消费者模型。生产者可以使用 LPUSH 或 RPUSH 将消息添加到队列的头部或尾部,消费者可以使用 LPOP 或 RPOP 从队列的头部或尾部获取并删除消息。如果需要阻塞等待消息,可以使用 BLPOP 或 BRPOP 命令。Redis提供了 BRPOP 命令。BRPOP命令也称为阻塞式读取,客户端在没有读到队列数据时,自动阻塞,直到有新的数据写入队列,再开始读取新数据
- 任务堆栈:Redis List 可用作任务堆栈,实现后进先出(LIFO)的任务处理模式。可以使用 LPUSH 将任务压入堆栈,使用 LPOP 从堆栈顶部弹出任务进行处理。
- 任务队列:Redis List 可用作任务队列,实现先进先出(FIFO)的任务处理模式。可以使用 RPUSH 将任务添加到队列尾部,使用 LPOP 从队列头部获取任务进行处理。
- 时间线和动态消息:Redis List 可用于存储时间线和动态消息,如用户的微博、动态等。可以使用 LPUSH 将新消息添加到列表头部,保证列表中的消息按时间顺序排列。使用 LRANGE 命令可以快速获取最新的消息或指定时间段内的消息。
- 数据缓存:Redis List 可用作数据缓存,存储最近访问的数据。当需要缓存新数据时,可以使用 LPUSH 将数据添加到列表头部,如果列表长度超过缓存容量,可以使用 RPOP 删除最早访问的数据。这样可以实现简单的 LRU(Least Recently Used)缓存策略。
- 排行榜和计分板:Redis List 可用于实现排行榜和计分板,按照分数或其他指标对元素进行排序。可以使用 LPUSH 或 RPUSH 添加元素,使用 SORT 命令对列表元素进行排序,使用 LRANGE 获取指定范围内的排名。
这些只是 Redis List 的一部分应用场景,由于其高性能和灵活性,Redis List 可以应用于许多其他场景。实际使用中,可以根据需求选择合适的 List 命令来实现所需的功能。
Hash
Hash 是一个键值对(key - value)集合,其中 value 的形式如: value=[{field1,value1},...{fieldN,valueN}]
。Hash 特别适合用于存储对象。
底层实现
Hash 类型的底层数据结构是由压缩列表或哈希表实现的:
- 如果哈希类型元素个数小于
512
个(默认值,可由hash-max-ziplist-entries
配置),所有值小于64
字节(默认值,可由hash-max-ziplist-value
配置)的话,Redis 会使用压缩列表作为 Hash 类型的底层数据结构; - 如果哈希类型元素不满足上面条件,Redis 会使用哈希表作为 Hash 类型的 底层数据结构。
在 Redis 7.0 中,压缩列表数据结构已经废弃了,交由 listpack 数据结构来实现了。
压缩列表底层实现
哈希表底层实现
哈希表是一种高效的动态数组数据结构,它通过哈希函数将键值对映射到一个数组中的索引位置。哈希表的特点是插入、删除和查找操作的时间复杂度都为 O(1),具有良好的性能和可扩展性。
哈希表的底层实现采用开放寻址法(open addressing)和渐进式哈希(incremental hashing)策略。当哈希表的负载因子(即已存储的键值对数量与数组容量的比值)超过一定阈值时,Redis 会自动扩容并重新哈希所有的键值对。这样可以确保哈希表在处理不同大小的数据时都能保持高效的性能。
底层实现关键点:
- 哈希函数:Redis 使用 MurmurHash2 算法作为哈希函数,将键值对的 field 映射到哈希表中的索引位置。MurmurHash2 算法非常适合在哈希表中使用,因为它可以快速计算哈希值且具有较低的冲突率。
- 开放寻址法:Redis 哈希表采用开放寻址法(open addressing)进行冲突解决。当两个或多个键值对的哈希值发生冲突时,它们会被放置在哈希表中的其他位置。Redis 使用线性探测法(linear probing)进行开放寻址,即在发生冲突时,沿着数组顺序查找下一个空闲位置来存放新的键值对。
- 渐进式哈希:为了保持哈希表的高效性能,Redis 使用渐进式哈希(incremental hashing)策略进行扩容和收缩。当哈希表的负载因子(即已存储的键值对数量与数组容量的比值)超过一定阈值(通常为 1.0)时,Redis 会自动扩容并重新哈希所有的键值对。同样,当负载因子低于一定阈值时,Redis 会自动收缩哈希表。渐进式哈希意味着,在扩容或收缩过程中,Redis 会逐步将键值对重新哈希到新的哈希表中,而不是一次性完成。这样可以减少单次操作的计算量和延迟,保持 Redis 的高性能和低延迟特性。
- 扩容和收缩策略:Redis 哈希表采用 2 倍扩容策略。当哈希表的负载因子超过阈值时,Redis 会将哈希表的容量扩大为当前容量的 2 倍。当负载因子低于阈值时,Redis 会将哈希表的容量减小为当前容量的一半。这种扩容和收缩策略可以保证哈希表在不同大小的数据集下都能保持高效的性能。
listpack底层实现
在 Redis 6.2 版本以后,Hash 类型数据结构的底层实现从 Ziplist 被替换成了 Listpack。Listpack 是一种紧凑的连续内存数据结构,它将多个键值对存储在一个连续的内存区域中。这样可以减少内存碎片并降低内存占用。Listpack 适用于存储较小的哈希表,具有紧凑的内存占用和高效的访问性能。
以下是 Redis Hash 类型数据结构中 Listpack 底层实现的关键点:
- 变长编码:Listpack 使用变长编码(variable length encoding)来存储每个键值对的长度。这意味着每个键值对的长度信息占用的字节数量取决于其实际长度。较短的键值对占用较少的字节,从而节省内存空间。
- 紧凑内存分布:Listpack 将键值对紧凑地存储在连续的内存区域中,以减少内存碎片和降低内存占用。每个键值对在 Listpack 中以连续的字节序列形式存储,包括长度信息、实际数据以及一些特殊标记。
- 顺序访问:Listpack 的访问模式是顺序访问。在查找、插入和删除操作中,Listpack 会按照存储顺序遍历所有的键值对,直到找到目标键值对。对于较小的哈希表,这种顺序访问模式具有较高的性能。但是,在处理较大的哈希表时,顺序访问的效率会降低。
- 自动切换:Redis 通过配置参数
hash-max-listpack-entries
和hash-max-listpack-value
控制何时使用 Listpack 作为底层实现。当哈希表中的键值对数量超过hash-max-listpack-entries
或键值对的值长度超过hash-max-listpack-value
时,Redis 会自动将底层实现切换为哈希表。
总之,在 Redis 6.2 版本以后,Hash 类型数据结构中的 Listpack 底层实现是一种紧凑的连续内存数据结构,适用于存储较小的哈希表。Listpack 通过变长编码、紧凑内存分布和顺序访问等技术实现较低的内存占用和高效的访问性能。与之前的 Ziplist 实现相比,Listpack 提供了更好的性能和可维护性。
应用场景
- 缓存对象
Hash 类型的 (key,field, value) 的结构与对象的(对象id, 属性, 值)的结构相似,也可以用来存储对象。
- 购物车
用户 id 为 key,商品 id 为 field,商品数量为 value,恰好构成了购物车的3个要素
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q6X9kLDQ-1690854545413)(C:\Users\hp\AppData\Roaming\Typora\typora-user-images\image-20230801091309300.png)]
Set
Set 类型是一个无序并唯一的键值集合,它的存储顺序不会按照插入的先后顺序进行存储
底层实现
Set 类型的底层数据结构是由哈希表或整数集合实现的:
- 如果集合中的元素都是整数且元素个数小于
512
(默认值,set-maxintset-entries
配置)个,Redis 会使用整数集合作为 Set 类型的底层数据结构; - 如果集合中的元素不满足上面条件,则 Redis 使用哈希表作为 Set 类型的底层数据结构。
整数集合底层实现
哈希表底层实现
应用场景
- 点赞
Set 类型可以保证一个用户只能点一个赞,这里举例子一个场景,key 是文章id,value 是用户id。
- 共同关注
Set 类型支持交集运算,所以可以用来计算共同关注的好友、公众号等。
- 抽奖活动
存储某活动中中奖的用户名 ,Set 类型因为有去重功能,可以保证同一个用户不会中奖两次。
Zset
底层实现
Zset 类型(有序集合类型)相比于 Set 类型多了一个排序属性 score(分值),对于有序集合 ZSet 来说,每个存储元素相当于有两个值组成的,一个是有序集合的元素值,一个是排序值。
Zset 类型的底层数据结构是由压缩列表或跳表实现的:
- 如果有序集合的元素个数小于
128
个,并且每个元素的值小于64
字节时,Redis 会使用压缩列表作为 Zset 类型的底层数据结构; - 如果有序集合的元素不满足上面的条件,Redis 会使用跳表作为 Zset 类型的底层数据结构;
调表底层实现
Redis 的底层实现主要包括内存分配、数据结构、事件处理、持久化等多个部分。在这里,我们主要关注 Redis 的数据结构实现,特别是调表(跳跃表)的底层实现。
跳跃表(Skip List)是一种数据结构,它允许快速地搜索、插入和删除有序数据。跳跃表的主要思想是在有序链表的基础上增加多级索引,从而减少查找时需要遍历的节点数量。跳跃表的搜索、插入和删除操作的时间复杂度为 O(log N),这使得它成为一种高效的数据结构。
Redis 使用跳跃表实现有序集合(Sorted Set)的底层数据结构。有序集合是一种同时支持按分数排序和按元素查找的数据结构。为了满足这两种需求,Redis 的有序集合同时使用跳跃表和散列表(Hash Table)存储数据。在实际使用中,跳跃表主要用于排序和范围查询操作,而散列表用于快速查找元素的分数。
以下是 Redis 跳跃表的一些底层实现细节:
- 节点结构:Redis 跳跃表的节点包含以下几个主要成分:元素、分数、后退指针(backward pointer)和层级信息。其中,元素存储有序集合的成员,分数用于排序,后退指针指向前一个节点,层级信息包含多个前进指针(forward pointer)和跨度(span)。
// Redis 跳跃表节点结构定义(源码 src/server.h)
typedef struct zskiplistNode {
robj \*obj;
double score;
struct zskiplistNode \*backward;
struct zskiplistLevel {
struct zskiplistNode \*forward;
unsigned int span;
} level[];
} zskiplistNode;
- 跳跃表结构:Redis 跳跃表包含头节点、尾节点和最大层数。头节点用于存储多级索引,尾节点指向最后一个元素,最大层数表示当前跳跃表的最大索引层数。
// Redis 跳跃表结构定义(源码 src/server.h)
typedef struct zskiplist {
struct zskiplistNode \*header, \*tail;
unsigned long length;
int level;
} zskiplist;
- 随机层数:为了保持跳跃表的平衡性,Redis 在插入节点时使用随机函数生成节点的层数。这样可以确保每一层的节点数量大约为上一层的一半,从而实现良好的查找性能。
// Redis 随机层数生成函数(源码 src/t\_zset.c)
int zslRandomLevel(void) {
int level = 1;
while ((random() & 0xFFFF) < (ZSKIPLIST_P \* 0xFFFF)) {
level += 1;
}
return (level < ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}
- 插入、删除和查找操作:Redis 跳跃表的插入、删除和查找操作都遵循类似的查找路径。首先从头节点的最高层开始查找,然后逐层向下查找,直到找到目标节点或者确定目标节点的插入位置。在查找过程中,跳跃表使用分数和元素进行比较,以确保有序集合的正确排序。
总之,Redis 使用跳跃表作为有序集合的底层数据结构,实现了高效的搜索、插入和删除操作。跳跃表的底层实现包括节点结构、跳跃表结构、随机层数和各种操作函数。通过这些底层实现,Redis 能够支持有序集合的各种功能和需求。
listpack底层实现
Listpack 是 Redis 中的一种紧凑型列表数据结构,它是为了替代原有的 ziplist 实现而引入的。Listpack 是一种紧凑的、可变长度的、连续内存数据结构,用于存储有序的值列表。它设计用于存储少量的数据,如哈希表的小数目元素。Listpack 的主要目标是实现存储和访问的高效率,同时保持较低的内存消耗。
以下是 Redis Listpack 的一些底层实现细节:
- 内存布局:Listpack 采用连续内存存储数据,包含一个 header 和多个 entry。每个 entry 包含一个特定长度的值。header 部分包含两个字段:一个 16-bit 的总长度字段(包含 header 本身的长度)和一个 16-bit 的元素数量字段。header 的大小为 6 字节。
- 数据编码:Listpack 支持多种数据编码格式,包括整数和字节串。为了节省空间,Listpack 使用标志位来表示不同类型的数据编码。对于整数类型,Listpack 使用一种可变长度的编码格式,称为 IntEnc。对于字节串类型,Listpack 使用原始字节数组存储数据。
- 操作函数:Redis Listpack 提供了一系列操作函数,用于实现列表的基本操作,如插入、删除、查找和迭代等。这些函数根据 Listpack 的内存布局和数据编码进行高效的实现。
以下是 Redis Listpack 的一些核心操作函数(源码:src/listpack.c):
lpNew
:创建一个新的 Listpack。lpFree
:释放一个 Listpack。lpLength
:获取 Listpack 中元素的数量。lpSeek
:查找指定索引的元素。lpInsert
:在指定位置插入一个元素。lpDelete
:删除指定位置的元素。lpGet
:获取指定位置的元素值。lpNext
和lpPrev
:实现 Listpack 的迭代。
总之,Redis Listpack 是一种紧凑的列表数据结构,用于存储少量的有序数据。Listpack 的底层实现包括内存布局、数据编码和各种操作函数。通过这些底层实现,Redis Listpack 能够实现高效的存储和访问操作,同时保持较低的内存消耗。
应用场景
Redis 的有序集合(Sorted Set,简称 zset)是一种将成员与分数关联的数据结构,成员之间以分数进行排序,每个成员的分数都是唯一的。zset 支持多种操作,如插入、删除、按分数范围查询、按成员范围查询和按排名查询等。zset 的底层实现使用跳跃表和散列表,以实现高效的排序和查找操作。
有序集合在许多应用场景中都非常有用,以下是一些典型的应用场景:
- 排行榜:zset 可以用于实现各种排行榜,如游戏中的积分排行榜、网站的热门文章排行榜等。通过将用户或项目的得分作为分数,将用户或项目的 ID 作为成员,可以轻松地将数据添加到有序集合中。之后,可以使用 zset 的范围查询操作来获取排名前 N 的用户或项目,或者使用排名查询操作获取特定用户或项目的排名。
- 延时任务队列:zset 可以用于实现延时任务队列。将任务的执行时间作为分数,将任务的 ID 作为成员,可以将待执行的任务添加到有序集合中。之后,可以使用 zset 的范围查询操作来获取当前需要执行的任务,并执行相应的操作。当任务完成后,可以将其从有序集合中删除。
- 时间序列数据:zset 可以用于存储时间序列数据,如用户的登录记录、传感器采集的数据等。将时间戳作为分数,将事件或数据的 ID 作为成员,可以将数据添加到有序集合中。之后,可以使用 zset 的范围查询操作来获取特定时间范围内的事件或数据。
- 自动补全:zset 可以用于实现搜索框的自动补全功能。将关键词的权重作为分数,将关键词本身作为成员,可以将热门搜索词添加到有序集合中。之后,可以使用 zset 的成员范围查询操作来根据用户输入的前缀获取匹配的关键词,并按权重排序。
- 去重统计:zset 可以用于实现去重统计,如统计用户访问次数、统计网站的不同 IP 访问次数等。将用户或 IP 的访问次数作为分数,将用户或 IP 的 ID 作为成员,可以将数据添加到有序集合中。之后,可以使用 zset 的分数查询操作来获取特定用户或 IP 的访问次数。
上述应用场景仅为有序集合的一部分,实际上 zset 可以应用于许多其他场景。总之,通过 Redis 的有序集合,我们可以实现各种有序、去重和统计功能,以满足不同的业务需求。
Bitmap
Bitmap,即位图,是一串连续的二进制数组(0和1),可以通过偏移量(offset)定位元素。BitMap通过最小的单位bit来进行0|1
的设置,表示某个元素的值或者状态,时间复杂度为O(1)。
底层实现
Bitmap(位图)并非一种单独的数据结构,而是基于字符串(String)数据结构实现的一种高效的二进制位操作方法。Bitmap 通常用于存储大量的布尔值(真/假),每个布尔值只占用一个位。由于位图是基于字符串实现的,它的底层实现实际上就是使用连续的字节(byte)存储数据。
以下是 Redis Bitmap 的一些底层实现细节:
- 内存布局:Bitmap 是基于字符串实现的,因此它的内存布局与字符串相同。字符串是一个连续的字节数组,每个字节包含 8 个位。在位图中,每个位(bit)表示一个布尔值,0 表示 false,1 表示 true。
- 位操作:Redis 提供了一系列位操作命令(如
SETBIT
、GETBIT
和BITCOUNT
等),用于实现 Bitmap 的基本操作。这些命令可以读取或修改位图中的单个位,或者统计位图中特定值的数量。位操作命令的实现主要依赖于位操作和字节操作,如位掩码和位移等。
以下是 Redis Bitmap 的一些核心位操作命令及其功能:
SETBIT
:设置位图中指定位置的值。例如,SETBIT key 10 1
将位图中第 10 位的值设置为 1。GETBIT
:获取位图中指定位置的值。例如,GETBIT key 10
将返回位图中第 10 位的值。BITCOUNT
:统计位图中特定值的数量。例如,BITCOUNT key
将返回位图中值为 1 的位的数量。BITOP
:对多个位图执行逻辑位操作(如 AND、OR、XOR 和 NOT 等)。例如,BITOP AND destkey key1 key2
将对 key1 和 key2 对应的位图执行 AND 操作,并将结果保存到 destkey 对应的位图中。
总之,Redis Bitmap 是一种基于字符串实现的高效位操作方法,用于存储大量的布尔值。Bitmap 的底层实现包括内存布局和位操作命令。通过这些底层实现,Redis Bitmap 能够实现高效的布尔值存储和访问操作,同时保持较低的内存消耗。
基于字符串实现
应用场景
Redis Bitmap(位图)是一种高效的二进制位操作方法,适用于存储大量的布尔值。由于每个布尔值仅占用一个位,Bitmap 在大规模数据处理时具有较低的内存消耗。以下是一些典型的 Redis Bitmap 应用场景:
- 用户签到功能:Bitmap 可以用于实现用户的签到功能。将每个用户的 ID 作为键,将每天的日期作为位的索引,可以记录用户的签到信息。通过
SETBIT
和GETBIT
命令可以方便地设置和获取用户的签到状态。 - 用户行为追踪:Bitmap 可以用于追踪用户的行为,如用户是否浏览过某个文章、是否点击过某个广告等。将每个用户的 ID 作为键,将行为标识(如文章 ID 或广告 ID)作为位的索引,可以记录用户的行为信息。通过
SETBIT
和GETBIT
命令可以方便地设置和获取用户的行为状态。 - 统计分析:Bitmap 可以用于统计分析,如统计活跃用户数量、统计用户某个行为的次数等。通过
BITCOUNT
命令可以统计位图中值为 1 的位的数量,从而得到相应的统计结果。此外,通过BITOP
命令可以实现多个位图之间的逻辑操作,如求用户行为的交集、并集等。 - 布隆过滤器:虽然 Redis 中的布隆过滤器是基于特殊的数据结构实现的,但我们也可以使用 Bitmap 来实现简化版的布隆过滤器。布隆过滤器是一种概率型数据结构,用于判断一个元素是否在一个集合中。将哈希函数的输出结果作为位的索引,可以使用 Bitmap 记录集合中的元素。通过多次哈希和位操作,可以实现高效的集合查询操作。
- IP 黑名单/白名单:Bitmap 可以用于实现 IP 黑名单或白名单。将 IP 地址转换为整数,然后将整数作为位的索引,可以记录 IP 的状态。通过
SETBIT
和GETBIT
命令可以方便地设置和获取 IP 的状态。
上述应用场景仅为 Redis Bitmap 的一部分,实际上 Bitmap 可以应用于许多其他场景。总之,通过 Redis Bitmap,我们可以实现大量布尔值的高效存储和访问操作,满足不同的业务需求。
HyperLogLog(统计基数)
HyperLogLog 是 Redis 2.8.9 版本新增的数据类型,是一种用于「统计基数」的数据集合类型,基数统计就是指统计一个集合中不重复的元素个数。但要注意,HyperLogLog 是统计规则是基于概率完成的,不是非常准确,标准误算率是 0.81%。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64
个不同元素的基数,和元素越多就越耗费内存的 Set 和 Hash 类型相比,HyperLogLog 就非常节省空间。
底层实现
Redis HyperLogLog(HLL)是一种概率型数据结构,用于解决基数计数问题。基数计数是统计一个集合中不重复元素的数量。传统的基数计数方法需要存储所有元素,因此会消耗大量内存。然而,HyperLogLog 通过牺牲一定的精度来实现低内存消耗。Redis 中的 HyperLogLog 实现基于 HLL 算法,并进行了一些优化。
以下是 Redis HyperLogLog 的底层实现细节:
- 数据结构:Redis HyperLogLog 使用一个固定大小的数组来存储数据。数组的每个元素称为 Register(寄存器),用于记录集合中元素的某些信息。每个寄存器占用 5 位(可以表示 0-31 的整数),因此一个 HLL 结构可以存储 2^14 个寄存器,总共占用 12 KB 内存。
- 哈希函数:为了统计集合中不重复元素的数量,需要对每个元素应用哈希函数得到一个固定长度的二进制串。Redis HyperLogLog 使用 MurmurHash64A 哈希函数,可以得到一个 64 位的哈希值。
- 寄存器索引和最大连续零数:对于每个哈希值,将其分成两部分:一部分用于寄存器索引(register index),另一部分用于计算最大连续零数(maximum consecutive zeros)。在 Redis HyperLogLog 中,哈希值的前 14 位用于寄存器索引,后 50 位用于计算最大连续零数。将哈希值映射到寄存器后,更新寄存器的值为最大连续零数。
- Harmonic Mean:Redis HyperLogLog 使用调和平均数(Harmonic Mean)来估算基数。计算调和平均数时,需要使用寄存器的值和 HLL 算法中的一些常数。最终得到的调和平均数是基数的一个近似值。
- 稀疏和密集表示:为了减少内存消耗,Redis HyperLogLog 对稀疏表示进行了优化。在数据稀疏时,可以使用更紧凑的格式来存储寄存器。当数据变得密集时,会自动切换到标准的 HLL 表示。这种优化方法在内存消耗和性能之间取得了平衡。
以下是 Redis HyperLogLog 的一些核心命令及其功能:
PFADD
:向 HyperLogLog 数据结构中添加元素。例如,PFADD key element
将元素添加到 key 对应的 HyperLogLog 结构中。PFCOUNT
:统计 HyperLogLog 数据结构中的不重复元素数量。例如,PFCOUNT key
将返回 key 对应的 HyperLogLog 结构中的基数估计值。PFMERGE
:合并多个 HyperLogLog 数据结构。例如,PFMERGE destkey key1 key2
将 key1 和 key2 对应的 HyperLogLog 结构合并,并将结果保存到 destkey 对应的 HyperLogLog 结构中。
总之,Redis HyperLogLog 是一种基于 HLL 算法实现的概率型数据结构,用于解决基数计数问题。通过牺牲一定的精度,HyperLogLog 可以实现低内存消耗的基数计数。其底层实现包括数据结构、哈希函数、寄存器索引和最大连续零数计算、Harmonic Mean 估算和稀疏表示优化等。
基于数组实现
应用场景
Redis HyperLogLog(HLL)是一种概率型数据结构,用于解决基数计数问题。它可以用来估算一个集合中不重复元素的数量,牺牲一定的精度以实现低内存消耗。以下是一些典型的 Redis HyperLogLog 应用场景:
- 统计用户活跃度:HyperLogLog 可以用于统计网站或应用中的活跃用户数量。例如,在一段时间内,记录每个访问用户的 ID。使用 HLL 结构可以快速估算出访问用户的数量,从而衡量用户活跃度。
- 网站访问量统计:HyperLogLog 可以用于统计网站的访问量,例如统计每天或每小时访问网站的独立 IP 数量。将每个访问 IP 地址添加到 HLL 结构中,可以快速估算出访问量。
- 搜索引擎查询统计:搜索引擎可以使用 HyperLogLog 来估算每个关键词的搜索结果数量。将搜索结果的 URL 添加到 HLL 结构中,可以获得一个近似的搜索结果数量。
- 在线广告曝光统计:HyperLogLog 可以用于统计在线广告的曝光次数。例如,可以记录每个广告被展示给不同用户的次数,从而了解广告的曝光情况。
- 社交网络中的关注者统计:在社交网络中,可以使用 HyperLogLog 来估算每个用户的关注者数量。将关注者的用户 ID 添加到 HLL 结构中,可以快速获得关注者数量的近似值。
- 数据流分析:在大数据处理中,可以使用 HyperLogLog 来估算数据流中的不重复元素数量。这对于数据去重、异常检测等场景非常有用。
- 数据仓库中的基数计数问题:在数据仓库中,可以使用 HyperLogLog 来估算数据集中的不重复元素数量,从而对数据进行分析和处理。
这些应用场景仅为 Redis HyperLogLog 的一部分。实际上,HLL 可以应用于许多其他需要基数计数的场景。总之,通过 Redis HyperLogLog,我们可以实现大量不重复元素数量的高效估算,满足不同的业务需求。
GEO
Redis GEO 是 Redis 3.2 版本新增的数据类型,主要用于存储地理位置信息,并对存储的信息进行操作
底层实现
Redis GEO 是 Redis 提供的一组地理空间相关的功能,用于存储地理位置信息并执行地理空间查询。Redis GEO 使用经纬度坐标表示地理位置,并提供了一系列命令来添加、更新和查询这些地理位置。以下是 Redis GEO 的底层实现细节:
- 数据结构:Redis GEO 使用 Sorted Set(有序集合)数据结构来存储地理位置信息。Sorted Set 是一个以分数排序的不重复元素集合。在 Redis GEO 中,地理位置的经纬度坐标被转换成一个一维的 GeoHash 值,然后作为分数存储在 Sorted Set 中。这样,我们可以使用 Sorted Set 的功能来执行地理空间查询。
- GeoHash:GeoHash 是一种将二维地理空间坐标(经纬度)编码为字符串的方法。GeoHash 将地球表面划分为网格,并将每个网格分配一个唯一的字符串。地理位置的经纬度坐标被映射到这些网格上。在 Redis GEO 中,GeoHash 值被编码为 52 位整数(有时也会用 53 位表示更高的精度),并用作 Sorted Set 的分数。GeoHash 的一个重要特性是,相邻的网格具有相似的 GeoHash 值,这使得 Redis GEO 能够执行地理空间查询。
- 查询算法:Redis GEO 提供了一系列地理空间查询命令,例如
GEORADIUS
和GEORADIUSBYMEMBER
。这些命令使用 Sorted Set 数据结构的功能,结合 GeoHash 算法执行查询。在查询过程中,会计算目标位置的 GeoHash 值,并查询附近的网格以找到符合条件的地理位置。通过改变查询参数,可以调整查询的精度和范围。
以下是 Redis GEO 的一些核心命令及其功能:
GEOADD
:向 GEO 数据结构中添加地理位置。例如,GEOADD key longitude latitude member
将具有给定经纬度坐标的地理位置添加到 key 对应的 GEO 结构中。GEOPOS
:获取地理位置的经纬度坐标。例如,GEOPOS key member
将返回 key 对应的 GEO 结构中 member 的经纬度坐标。GEODIST
:计算两个地理位置之间的距离。例如,GEODIST key member1 member2 [unit]
将返回 key 对应的 GEO 结构中 member1 和 member2 之间的距离,可以选择距离单位(如米、千米等)。GEORADIUS
:查询给定半径内的地理位置。例如,GEORADIUS key longitude latitude radius unit [options]
将返回 key 对应的 GEO 结构中位于给定经纬度坐标和半径范围内的地理位置。GEORADIUSBYMEMBER
:查询给定半径内的地理位置,以某个成员为中心。例如,GEORADIUSBYMEMBER key member radius unit [options]
将返回 key 对应的 GEO 结构中位于给定成员为中心的半径范围内的地理位置。
总之,Redis GEO 是一种基于 Sorted Set 数据结构和 GeoHash 算法实现的地理空间功能。通过 Redis GEO,可以存储地理位置信息并执行地理空间查询,满足不同的业务需求。