首页 > 数据库 >【Redis 理论与实践学习】 一、Redis的数据结构:4.Set类型

【Redis 理论与实践学习】 一、Redis的数据结构:4.Set类型

时间:2024-07-08 23:29:31浏览次数:16  
标签:Set 元素 Redis 整数 key 集合 数据结构

文章目录


简介

Redis 的 Set 是 String 类型的无序集合。集合中任意成员是唯一的,即集合中不能出现重复的数据。

  • 特点:

    • 无序性:集合中的元素没有特定的顺序。
    • 唯一性:集合中不允许重复的元素。
    • 元素数量限制:最多可以存储2^32-1个元素。
  • 基本操作:

    • 增加元素:向集合中添加一个元素,如果元素已存在则忽略。
    • 删除元素:从集合中移除指定元素。
    • 检查元素是否存在:判断集合中是否存在某个元素。
  • 集合运算:

    • 并集:返回多个集合的所有成员的并集。
    • 交集:返回多个集合的所有成员的交集。
    • 差集:返回指定集合和其他集合之间的差集。
  • 应用场景:

    • 去重:适用于需要存储唯一值的场景。
    • 标签管理:管理对象的标签集合。
    • 社交网络关系处理:例如找出共同好友等操作。

Set类型在标签管理中键值对关系。

Set 和 List的区别

在Redis中,Set(集合)和List(列表)是两种不同的数据结构,在数据结构和操作特性有以下区别:

  1. 数据结构特性:
    • List(列表): 是一个有序的字符串链表。列表中的每个元素都包含一个字符串值。
    • Set(集合): 是一个不重复且无序的字符串集合。集合中的每个元素都是唯一的,不存在重复的元素。
  2. 插入顺序:
    • List: 元素按照插入顺序排序,可以在列表的两端进行插入(左端和右端)。
    • Set: 元素没有特定的顺序,Redis会自动为集合中的元素建立索引以提高查找速度。
  3. 支持的操作:
    • List: 支持按索引获取元素,支持在列表两端的插入和删除操作,支持修剪(Trim)操作,可以用作栈(LIFO)或队列(FIFO)。
    • Set: 支持添加、移除和检查元素是否存在,支持集合间的交集、并集和差集等操作。

常用命令

增删改查类命令

添加元素

# 添加元素:向集合 key 中添加一个或多个成员元素。
SADD key member [member ...]

向集合 key 中添加一个或多个成员元素,下列例子中,构建一个user:ikun的用户关键字,把他的爱好放入到他的set集合中。

移除元素

# 从集合 key 中移除一个或多个成员元素。
SREM key member [member ...]

通过SREM指令移除(remove)一个或多个元素,比如移除user:ikun:hobbyBasketBall

这样集合中就不再拥有篮球这个爱好了,说明这个人就是假fans(doge),怎么检验是不是假fans呢?

判断元素是否存在

# 判断成员元素 member 是否存在于集合 key 中,返回布尔值。
SISMEMBER key member

通过SISMEMBER指令判断member元素是否是key集合的成员(is member),例如查询user:ikun:hobby喜不喜欢打篮球,可以用下面这个例子

最终返回一个0表示false表示不存在

获取集合大小

# 返回集合 key 的基数(集合中元素的数量)。
SCARD key

通过SCARD 获取集合的基数(Cardinality),即统计集合元素的数量

获取集合所有成员

# 获取集合所有成员
SMEMBERS key

通过SMEMBERS获取key中的所有成员(Members),如下图示例:

随机获取元素

# 返回集合 key 中一个或多个随机成员。如果指定了 count 参数且为正数,则返回多个随机成员;如果为负数,则会返回1个成员。
SRANDMEMBER key [count]

通过SRANDMEMBER返回集合key中一个或多个随机成员(rand member)。如果指定了 count 参数且为正数,则返回多个随机成员;如果为负数,则会返回1个成员。此方法比较适合抽奖操作。

随机移除并返回元素

# 移除并返回集合 key 中的一个或多个随机成员。与 SRANDMEMBER 不同的是,SPOP 会从集合中移除元素。
# 3.2 + 版本支持[count]命令
SPOP key [count]

通过SPOP弹出(Pop)并返回集合 key 中的一个或多个随机成员,与SRANDMEMBER 不同的是,该指令会彻底删除元素,适合不重复抽奖的场景。

运算操作命令

集合间操作

# 返回给定多个集合的并集
SUNION key [key ...]

# 返回给定多个集合的交集
SINTER key [key ...]

# 返回给定多个集合的差集
SDIFF key [key ...]

三者分别是数学中的并、交、差运算,其运算模式如下图:
首先构建两个集合

  • 并集
  • 交集
  • 差集

集合间操作并存储

# 计算给定多个集合的并集,并将结果存储在 destination 集合中。
SUNIONSTORE destination key [key ...]
# 计算给定多个集合的交集,并将结果存储在 destination 集合中。
SINTERSTORE destination key [key ...]
# 计算给定多个集合的差集,并将结果存储在 destination 集合中。
SDIFFSTORE destination key [key ...]

跟集合操作命令类似,唯一的区别就是就是会进行额外的存储记录操作。


上图就是一个使用并集并存储的例子,可以看到store也会作为一个集合存入到Redis中。


应用场景

集合(Set)具有无序、元素不可重复、支持并集、交集、差集等操作的特性。
因此,它非常适合用来存储需要保证元素唯一性且无序的数据。例如,在需要进行数据去重的场景下,集合类型能够很好地满足需求。此外,集合还能方便地统计多个集合之间的并集、交集和差集,这对于数据分析和处理非常有用。

然而,需要注意的是,集合类型在执行并集、交集、差集等操作时,计算复杂度较高。特别是在数据量较大的情况下,直接在Redis主库上执行这些操作可能会导致Redis实例阻塞,影响其他请求的处理效率。

为了避免主库因为集合类型的聚合计算而阻塞,可以考虑以下策略:

  1. 在主从集群中,选择一个从库来执行集合的聚合统计操作。
  2. 将集合数据返回给客户端,由客户端程序来完成聚合统计,减轻主库的压力。
    通过这些策略,可以有效地管理和利用Redis中集合类型的强大功能,并保持业务系统的稳定性和高效性。

博客点赞

用户点赞操作

article:1表示文章的标识,user:x表示任意用户,每个用户通过点击点赞按钮触发Redis调用SADD命令,从而使用户加入到文章的点赞集合中。大家可以试一试这个加入到点赞集合过程。

类似的,用户可以通过SREM命令实现点赞的取消,咳咳,这个就不用试了。


当然点赞的右边还有当前的点赞数量,这个可以使用SCAR命令返回对应的点赞数量。

公众号共同关注

在Redis中使用Set来实现公众号的共同关注场景是非常合适的。假设有多个用户,每个用户可以关注多个公众号,我们希望找出同时被多个用户关注的公众号,即求公众号的交集。

用户关注集合

使用Redis的Set来存储每个用户关注的公众号。

SADD user:1:subscriptions 1 3 4 5 8
SADD user:2:subscriptions 2 4 5 7 8

共同关注查询

通过set的集合运算功能实现关注查询

共同关注的公众号

SINTER user:1:subscriptions user:2:subscriptions

通过交集查询共同关注的公众号。

user:2推荐user:1的号

SDIFF user:1:subscriptions user:2:subscriptions

通过SDIFF命令查询差集给对方推荐公众号

抽奖活动

通过SRANDMEMBERSPOP命令实现随机选取一个或多个成员进行抽奖。

 SADD lottery:participants Alice James Emily William Sophia Benjamin John Sean Marry

将参与成员加入到奖池参与名单中,以备之后抽奖使用

# 随机抽1个人中 一等奖
SRANDMEMBER lottery:participants 1
1) "John"
# 随机抽2个人中 二等奖
SRANDMEMBER lottery:participants 2
1) "William"
2) "John"
# 随机抽3个人中 三等奖
SRANDMEMBER lottery:participants 3
1) "Alice"
2) "John"
3) "James"

使用SRANDMEMBER进行抽奖有一个坏处,就是会出现天选之子,比如上面的John,连续三次中奖,所以也可以使用另一个命令SPOP来进行去重抽奖,中奖后弹出集合不再进行抽奖。

# 随机抽1个人中 一等奖
SPOP lottery:participants 1
1) "William"
# 随机抽2个人中 二等奖
SPOP lottery:participants 2
1) "James"
2) "Benjamin"
# 随机抽3个人中 三等奖
SPOP lottery:participants 3
1) "Emily"
2) "John"
3) "Sean"

内部实现

Set类型有两种底层数据结构:整数集合和哈希表

  1. **整数集合(intset):**当集合中的所有元素都是整数,并且元素个数小于等于 512(默认值,可以通过配置 set-max-intset-entries 修改),Redis 会使用整数集合(intset)作为 Set 类型的底层数据结构。

  2. **哈希表(hashtable):**如果集合中的元素包含非整数类型的元素,或者整数元素个数超过了整数集合的限制,Redis 将使用哈希表作为 Set 类型的底层数据结构。

整数集合

整数集合(intset)是一种特定的数据结构,用于存储整数值集合,是集合键的底层实现之一。它的设计目标是在内存使用效率和执行效率之间取得良好的平衡。当集合只包含整数元素,且数量小于等于512时,Redis就会使用整数集合作为Set的底层实现。

intset即为整数集合

整数集合的结构设计

整数集合的整数类型可以int16_tint32_tint64_t三种,并且集合中不会出现相同元素,其代码结构设置在intset.h/intset结构体中,表示如下:

typedef struct intset {
    //编码方式
    uint32_t encoding;
    //集合包含的元素数量
    uint32_t length;
    //保存元素的数组
    int8_t contents[];
} intset;

contents即表示Set集合的内容,整数集合的每个元素都是contents数组的一个数组项,每个数组项在数组中按顺序从大到小排列,并且数组中不会包含重复元素。
length 表示数组contents的长度。
细心的同学会发现contents申请的整数类型为int8_t,但是前面确说数组只可以表示其他三种类型。这是整数集合的巧妙设计之一,int8_t只是一个基数,实际表示编码的是encoding属性。contents的真正类型取决于encoding的属性值。

encoding 值contents 实际类型contents 类型范围
INTSET_ENC_INT16int16_t-32768 ~ 32767
INTSET_ENC_INT32int32_t-232 ~ 232 - 1
INTSET_ENC_INT64int64_t-264 ~ 264 -1


上图表示两种类型的整数集合,此时两个集合的长度虽然都是4,但是每个contents的内存是天差地别。

  • int16_t的集合,其数组大小 = sizeof(int16_t) * 4 = 16 * 4 = 64 位
  • int64_t的集合,其数组大小 = sizeof(int64_t) * 4 = 64 * 4 = 256 位

由此可想到一个问题,当int16_t的整数集合加入一个int32_t 或 int64_t的整数,这个整数集合会怎么变化的呢?

整数集合的升级

当大整数加入到小整数的集合中,会先进行集合升级的操作,然后再将大整数加入到集合中。
升级的过程可以分为三个步骤

  1. 根据新元素的大小,扩充整数的contents空间,并为新元素分配空间
  2. 将旧元素按照新元素的位大小放入新的空间,并且再放置过程中,也要维持整数集合的有序性。
  3. 将新元素加入到数组里面。

如图中例子表示一个int16_t的整数集合,当加入新元素65535(int32_t)整数时,集合底层将会进行第一步扩充操作。

计算未来整数集合需要空间为32 * 5 = 160 ,所以扩充空间96位,扩充完数组后,将会进行第二部操作,扩充旧元素并保留顺序。

当扩充完原始数组后,就要执行最后一步即加入新元素。

最后,完成升级的三步操作就要更新集合的表信息。


更新encoding属性为新的存储类型,更新length为新的长度。至此就完成了更新操作,当然其他升级也与这个过程类似,这里就不过多演示了。

升级的好处

整数集合的底层实现确实可以根据需要动态升级存储元素的类型,这样可以有效地节省内存资源,避免不必要的内存浪费。具体来说,整数集合的优势包括:

  1. 提升灵活性: C语言通常是静态类型,使用int16_t数组就只能使用该数组,整数集合的设计使三种整数类型的在使用层面没有切换感知。
  2. **按需升级:**当需要添加更大范围的整数(如 int32_t 或 int64_t 类型)时,整数集合会动态地将内部数组升级为适合存储更大范围整数的类型,如 int32_t 或 int64_t。这样做可以在需要时扩展存储容量,而不是一开始就分配更大的空间,避免了一开始就浪费内存的情况。
  3. **节省内存:**这种设计使得整数集合在存储小范围整数时非常紧凑,节省了内存资源。只有在需要存储更大范围的整数时才会有较小的额外开销。

降级

整数集合不支持降级操作,一旦对数组进行了升级,就会一直保持升级后的状态。比如前面的升级操作的例子,如果删除了 65535 元素,整数集合的数组还是 int32_t 类型的,并不会因此降级为 int16_t 类型。

哈希表

哈希表提供了更灵活的存储方式,能够处理任意类型的元素,并且支持动态扩容。该内容在上一篇中Hash表内部实现已经提到了,这里就不再赘述了,如果大家感兴趣的话,请自行查看。

本文是经过个人查阅相关资料后理解的提炼,可能存在理论上理解偏差的问题,如果您在阅读过程中发现任何问题或有任何疑问,请不吝指出,我将非常感激并乐意与您讨论。谢谢您的阅读!

标签:Set,元素,Redis,整数,key,集合,数据结构
From: https://blog.csdn.net/qq_45171525/article/details/140188624

相关文章

  • redis学习笔记
    redis笔记1.Redis是什么?Redis(RemoteDictionaryServer)是一个使用C语言编写的,高性能非关系型的键值对数据库。与传统数据库不同的是,Redis的数据是存在内存中的,所以读写速度非常快,被广泛应用于缓存方向。Redis可以将数据写入磁盘中,保证了数据的安全不丢失,而且Redis的操作......
  • Redis事务
    001-redis事务 (1)Redis事务的概念: Redis事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。 总结说:redis事务就是......
  • Redis安全
    Redis安全一、账号密码端口安全1)账号密码安全。configgetrequiespass检查是否设置有密码,设置密码:CONFIGsetrequirepass"runoob"配置文件:#requirepassfoobared2)网络配置配置文件:bind192.168.1.100 #Redis服务器只监听本地网络接口,只有本机可以访问Redis服务器......
  • Redis核心问题总结(二)
    统计高并发网站每个网页每天的UV数据,结合Redis你会如何实现?选用方案:HyperLogLog如果统计PV那非常好办,给每个网页一个独立的Redis计数器就可以了,这个计数器的key后缀加上当天的日期。这样来一个请求,incrby一次,最终就可以统计出所有的PV数据。但是UV不一样,它要去......
  • redis如何与mysql数据保持一致?
    redis如何与mysql数据保持一致?同步双写:cacheasidepattern,读:先读缓存再读数据库,一个缓存的过期时间,实现起来简单好用极限情况还会有数据不一致的风险。CAP定理:c一致性a可用性p分区容错性,cp或者是ap异步双写:基于消息队列实现,写:生产者:先更新数据库,向队列发消息,消费者:监听消......
  • 在CentOS 7虚拟机上正确安装Redis
    在CentOS7虚拟机上正确安装Redis,可以按照以下步骤进行操作:更新系统软件包:sudoyumupdate安装Redis依赖库:sudoyuminstallepel-releaseyum-utilssudoyuminstallhttp://rpms.remirepo.net/enterprise/remi-release-7.rpmsudoyum-config-manager--enableremisudoyu......
  • redis缓存的穿透及解决的方案
    概念缓存穿透是指查询一个一定不存在的数据,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到DB去查询,可能导致DB挂掉。这种情况大概率是遭到了攻击。解决方案方案一:将每次查询到为null的值存入redis优点:简单缺点:消耗内存,可能会出现数据不一......
  • Python数据结构详解:列表、字典、集合与元组的使用技巧
    前言哈喽,大家好!今天我要和大家分享的是关于Python中最常用的数据结构:列表、字典、集合和元组的使用技巧。你有没有遇到过在处理数据时,不知道该用哪种数据结构来存储和操作数据的情况呢?别担心,今天这篇文章就来帮你搞定这些问题,让你在数据处理上更加得心应手。最后,别忘了关......
  • 【数据结构】—— 单链表(single linked list)
    文章目录1、单链表的定义优点和缺点单链表的构成2、单链表的创建初始化:3、单链表的接口实现打印尾插头插尾删头删查找在指定位置之前插入在指定位置之后插入删除指定位置的节点删除指定位置之后的节点销毁链表4、源代码1、单链表的定义单链表(SinglyLinkedList......
  • 高效维护区间之和/区间最值的数据结构(一)——树状数组
    高效维护区间之和/区间最值的数据结构(一)——树状数组树状数组的核心思想:分治。将数组以二叉树的形式进行维护区间之和。设aaa为原数组,......