set
- 一. set 类型介绍
- 二. set 命令
- sadd、smembers、sismember
- scard、spop、srandmember
- smove、srem
- 集合间操作
- 交集:sinter、sinterstore
- 并集:sunion、sunionstore
- 差集:sdiff、sdiffstore
- 三. set 命令小结
- 四. set 内部编码方式
- 五. set 使用场景
- 用户标签
- 共同好友
- 统计 UV
一. set 类型介绍
- 集合就是把一些有关联的数据放到一起,保存多个字符串类型的元素的 (可以使用 JSON 这样的格式,让 string 存储结构化数据),但和列表类型不同的是:
- 元素之间是无序的。
- 此处说的无序和之前 list 说的有序是对应的。
- 有序:顺序很重要,变换一下顺序,就是不同的列表。
- 无序:顺序不重要,变换一下顺序,还是那个集合。
- list:[1, 2, 3] 和 [2, 1, 3] 是不同的 list
- set:[1, 2, 3] 和 [2, 1, 3] 是相同的 set
- 元素不允许重复。
- 一个集合中最多可以存储 2^32 - 1 个元素。
- 元素之间是无序的。
- Redis 除了支持集合内的增删查改操作,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型,能在实际开发中解决很多问题。
集合类型:
二. set 命令
在 set 中的元素叫做 member,就像在 hash 中的元素叫做 field、value 类似。
sadd、smembers、sismember
- sadd:添加⼀/多个元素到 set 中。注意:重复的元素无法添加到集合中。
- 语法:
sadd key member [member ...]
- 时间复杂度:添加一个元素是 O(1),添加 N 个元素是 O(N)
- 返回值:本次添加成功的元素个数。
- smembers:获取一个 set 中的所有元素。注意:元素间的顺序是无序的。
- 语法:
smembers key
- 时间复杂度:O(N),N 是集合中元素的个数。
- 返回值:所有元素的列表。
- sismember:判断一个元素在不在 set 中。
- 语法:
sismember key member
- 时间复杂度:O(1)
- 返回值:元素在 set 中,返回 1;元素不在 set 中或者 key 不存在,返回 0
scard、spop、srandmember
- scard:获取一个 set 的基数 (cardinality),即 set 中的元素个数。
- 语法:
scard key
- 时间复杂度:O(1)
- 返回值:set 内的元素个数。
- spop:从 set 中删除并返回一个或者多个元素。注意:由于 set 内的元素是无序的,所以取出哪个元素实际是未定义行为,即可以看作随机的。
- 语法:
spop key [count]
- 时间复杂度:O(N),N 是 count 的个数。
- 返回值:取出的元素。
- srandmember:从 set 中返回一个或者多个随机的元素。
- 语法:
srandmember key [count]
- 时间复杂度:O(N),N 是 count 的个数。
- 返回值:取出的元素。
在 Redis 源码中,针对 spop 实现的时候,就采取了 “生成随机数” srandmember 的方式。
smove、srem
- smove:将一个元素从源 set 取出并放入目标 set 中。
- 语法:
smove source destination member
- 时间复杂度:O(1)
- 返回值:移动成功时,返回 1;移动失败时,返回 0
如果我给 key1 里再添加一个 1,再次把这个 1 移动给 key2,此时 smove 不会视为出错,也会按照 删除-插入 进行执行。
- srem:将指定的元素从 set 中删除
- 语法:
srem key member [member ...]
- 时间复杂度:O(N),N 是要删除的元素个数。
- 返回值:删除成功的元素个数。
不同操作的返回值,含义差别还是挺大的,需要用的时候,多翻文档即可。
集合间操作
- 交集 (inter):最终结果同时出现在两个集合中。
- 并集 (union):把多个集合中的数据都集中放在一起,如果元素有重复,也最终只保留一份。
- 差集 (diff):A 和 B 做差集,就是找出 A 中存在,但是 B 中不存在的元素。
交集:sinter、sinterstore
- sinter:获取给定 set 的交集中的元素。
- 语法:
sinter key [key ...]
- 时间复杂度:O(N * M),N 是最小的集合元素个数,M 是最大的集合元素个数。
- 返回值:交集的元素。
- sinterstore:获取给定 set 的交集中的元素并保存到目标 set 中。
- 语法:
sinterstore destination key [key ...]
- 时间复杂度:O(N * M),N 是最小的集合元素个数,M 是最大的集合元素个数。
- 返回值:交集的元素个数。
并集:sunion、sunionstore
- sunion:获取给定 set 的并集中的元素。
- 语法:
sunion key [key ...]
- 时间复杂度:O(N),N 是给定的所有集合的总的元素个数。
- 返回值:并集的元素。
- sunionstore:获取给定 set 的并集中的元素并保存到目标 set 中。
- 语法:
sunionstore destination key [key ...]
- 时间复杂度:O(N),N 是给定的所有集合的总的元素个数。
- 返回值:并集的元素个数。
差集:sdiff、sdiffstore
- sdiff:获取给定 set 的差集中的元素。
- 语法:
sdiff key [key ...]
- 时间复杂度:O(N),N 是给定的所有集合的总的元素个数。
- 返回值:差集的元素。
- sdiffstore:获取给定 set 的差集中的元素并保存到目标 set 中。
- 语法:
sdiffstore destination key [key ...]
- 时间复杂度:O(N),N 是给定的所有集合的总的元素个数。
- 返回值:差集的元素个数。
三. set 命令小结
命令 | 执行效果 | 时间复杂度 |
---|---|---|
sadd key member [member …] | 向集合中添加元素 | O(K),K 是元素的个数 |
smembers key | 求集合中的元素 | O(K),K 是元素的个数 |
sismember key member | 判断元素是否在集合中 | O(1) |
scard key | 获取集合中元素的个数 | O(1) |
spop key [count] | 随机删除 count 个元素 | O(N),N 是 count |
srandmember key [count] | 随机删除 count 个元素 | O(N),N 是 count |
smove source destination member | 移动源集合中的一个元素到目标集合 | O(1) |
srem key member [member …] | 删除集合中的元素 | O(K),K 是元素的个数 |
sinter key [key …] | 获取集合的交集 | O(N * M),N 是最小的集合元素个数,M 是最大的集合元素个数 |
sinterstore destination key [key …] | 存储集合的交集到目标集合中 | O(N * M),N 是最小的集合元素个数,M 是最大的集合元素个数 |
sunion key [key …] | 获取集合的并集 | O(N),N 是所有集合的元素个数 |
sunionstore destination key [key …] | 存储集合的并集到目标集合中 | O(N),N 是所有集合的元素个数 |
sdiff key [key …] | 获取集合的差集 | O(N),N 是所有集合的元素个数 |
sdiffstore destination key [key …] | 存储集合的差集到目标集合中 | O(N),N 是所有集合的元素个数 |
四. set 内部编码方式
集合类型的内部编码有两种:
- intset (整数集合):当集合中的元素都是整数并且元素的个数小于 set-max-intset-entries 配置 (默认 512 个) 时,Redis 会选用 intset 来作为集合的内部实现,从而减少内存的使用。
- Redis 是内存数据库,当元素均为整数,并且元素的个数不是很多的时候,为了节省空间,做出的特点优化。
- hashtable (哈希表):当集合类型无法满足 intset 的条件时,Redis 会使用 hashtable 作为集合的内部实现。
使用 object encoding key
可以查看集合内部的编码方式,如下:
五. set 使用场景
用户标签
集合类型比较典型的使用场景是标签 (tag),使用 set 来保存用户的 “标签”
- 例如 A 用户对娱乐、体育板块比较感兴趣,B 用户对历史、新闻比较感兴趣,这些兴趣点可以被抽象为标签。例如一个电子商务网站会对不同标签的用户做不同的产品推荐。
- 有了这些数据就可以得到喜欢同一个标签的人,以及用户的共同喜好的标签,这些数据对于增强用户体验和用户黏度都非常有帮助。分析出你这个人的一些特征,分析清楚之后,再投其所好。
- 上述用户数据,很多公司都在共享。
- 两个程序,两个账号,如何知道这两个账号是一个人?
- 现在的程序登入,主要就是两个入口,手机号、微信。
- 通过上述过程,搜集到的用户特征,就会转换成 “标签” (简短的字符串),此时就可以把标签保存到 Redis 的 set 中。
- 用户画像,这种事情其实是挺复杂的事情,一般一个大厂都会有专门的团队做这样工作。
- 上述玩法,抖音玩的是最好的,其它互联网大厂一看这么搞真好,于是纷纷效仿。
- 但是存在 “信息茧房” 问题:你看到的内容始终就是一个小圈子,看不到其它圈子中的事物。
- 当你很认真的看了一个视频之后,人家的服务器就判定你,非常爱看这种类型的视频,接下来就给你疯狂推送。
下面的演示通过集合类型来实现标签的若干功能。
- 给用户添加标签
sadd user:1:tags tag1 tag2 tag5
sadd user:2:tags tag2 tag3 tag5
...
sadd user:k:tags tag1 tag2 tag4
- 给标签添加用户
sadd tag1:users user:1 user:3
sadd tag2:users user:1 user:2 user:3
...
sadd tagk:users user:1 user:4 user:9 user:28
- 删除用户下的标签
srem user:1:tags tag1 tag5
...
- 删除标签下的用户
srem tag1:users user:1
srem tag5:users user:1
...
- 计算用户的共同兴趣标签
sinter user:1:tags user:2:tags
共同好友
使用 set 来计算用户之间的公共好友,基于 “集合求交集”
- 例如:QQ,我这边加了很多好友,你那边也加了很多好友。
- 基于上述还可以做一些好友推荐:A 和 B 是好友,A 和 C 是好友,B 和 C 和 D 都是好友,此时系统就会把 D 推荐给 A
统计 UV
一个互联网产品,如何衡量用户量,用户规模?主要的指标,是两方面:
- PV (page view):用户每次访问该服务器,都会产生一个 PV
- UV (user view):每个用户访问该服务器,都会产生一个 UV,但是同一个用户多次访问,不会增加 UV 的个数。
- UV 需要按照用户进行去重,上述的去重功能,就可以使用 set 来实现。