先来复习一下之前学过的几个基本的全局命令:
- keys:用来查看匹配规则的key
- exists:用来判定执行key是否存在
- del:删除指定的key
- expire:给key设置过期时间
- ttl:查询key的过期时间
- type:查询key对应的value的类型
一、Redis的数据结构
type命令实际返回的就是当前键的数据结构类型,他们分别是:string(字符串),list(列表),hash(哈希),set(集合),zset(有序集合),但是这些只是Redis对外的数据结构。

实际上Redis针对每一个数据结构都有自己的底层内部编码实现,而且是多种实现,这样Redis会在合适的场景选择合适的内部编码。

string类型的raw是最基本的字符串(底层就是持有一个char数组(C++)或者byte数组(Java))【C++里的char是1字节,等价与Java中的byte,而Java中的char是两个字节的】;string类型的int是用来实现"计数"这样的功能,当value就是一个整数的时候,此时可能Redis会直接使用int来保存;embstr是针对短字符串进行的特殊优化。
hash类型的hashtable是最基本的哈希表;hash类型的ziplist是在哈希表里面的元素比较少的时候可能就优化成ziplist了,压缩列表能够节省空间。
list类型的linkedlist是链表;list类型的ziplist是压缩列表;从Redis3.2开始,引入了新的实现方式:quicklist,同时兼顾了linkedlist和ziplist的优点,quicklist就是一个链表,每一个元素又是一个ziplist把空间和效率都折中的兼顾到~~~(quicklist比较类似于C++中的std::deque)
set类型的intset集合中存的都是整数
zset类型的skiplist是跳表~~~
为什么要压缩??
Redis上有很多key,可能某些key的value是hash,此时,如果key特别多,对应的hash也特别多,但是每一个hash又不大的情况下,就尽量去压缩,压缩之后就可以让整体占用的内存更小了。
可以看到每一种数据结构都有至少两种以上的内部编码实现,我们可以通过 object encoding 命令查询内部编码:
cpp
object encoding key
Redis这样设计有两种好处:
- 可以改进内部编码,而对外的数据结构和命令没有任何影响,这样一旦开发出更优秀的内部编码,无需改动外部数据结构和命令,例如Redis3.2提供了quicklist,结合了ziplist和linkedlist两者的优势,为列表类型提供了一种更为优秀的内部编码实现,而对用户来说基本无感知。
- 多种内部编码实现可以在不同场景下发挥各自的优势,例如ziplist比较节省内存,但是在列表元素比较多的情况下,性能会下降,这时候Redis会根据配置选项将列表类型的内部实现转换为linkedlist,整个过程用户同样无感知。
Redis会自动根据当前的实际情况选择内部的编码方式,自动适应的,只记思想,不记数字!!类似与这样的"参数"非常常见,都是"可调的"。
二、Redis的单线程架构
Redis使用了单线程架构来实现高性能的内存数据库服务。Redis只使用一个线程处理所有的命令请求,不是说一个Redis服务器进程内部只有一个线程,其实也有多个线程,多个线程在处理网络IO。
2.1 引出单线程模型
现在开启了三个redis-cli客户端同时执行命令。
cpp
客户端1设置一个字符串键值对:
set hello world
客户端2对counter做自增操作:
incr counter
客户端3对counter做自增操作:
incr counter
我们已经知道了从客户端发送的命令经历:发送命令,执行命令,返回结构三个阶段,其中发送命令最重要。Redis是采用单线程模型执行命令的是指:虽然三个客户端看起来是同时要求Redis去执行命令的,但是微观角度上,这些命令还是采用线性方法去执行的,只是原则上命令的执行顺序是不确定的,但是一定不会有两条命令被同步执行,可以想象Redis内部只有一个服务窗口,多个客户端按照他们达到的先后顺序被排队在窗口前,依次接受Redis的服务,所以两条incr命令无论执行顺序,结果一定是2,不会发生并发问题。

Redis能够使用单线程模型很好的工作,原因主要在于Redis的核心业务逻辑,都是短平快的,不太消耗CPU资源,也就不太吃多核~
Redis必须要特别小心某一个操作占用时间长,就会阻塞其他命令的执行!!!
线程安全问题
在多线程中,针对类似这样的场景:两个线程尝试同时对一个变量进行自增,表面上看是自增两次,实际上可能只自增一次。
2.2 为什么单线程还能这么快
参照物是数据库(MySQL)
- 纯内存访问。Redis将所有数据放在内存中,内存的响应时长大约为100纳秒,这是Redis达到每秒万级别访问的重要基础。
- 非阻塞IO。Redis使用epoll作为I/O多路复用技术的实现,在加上Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络IO上浪费过多的时间。
- 单线程避免了线程切换和竞态产生的消耗。单线程可以简化数据结构和算法的实现,让程序模型更简单;其次多线程避免了在线程竞争同一份共享数据时带来的切换和等待消耗。
- Redis的核心功能比数据库的核心功能更简单。数据库对于数据的插入删除查询......都有更复杂的功能支持,这样的功能势必要花费更多的开销。
虽然单线程给Redis带来很多好处,但是还有一个致命的问题:对于单个命令的执行时间都是有要求的。如果某一个命令执行过长,会导致其他命令全部处于等待队列中,迟迟等不到响应,造成客户端的阻塞,对于Redis这种高性能的服务来说是非常重要的,所以Redis是面向快速执行场景的数据库。
三、String字符串
字符串类型是Redis最基本的数据类型,关于字符串需要特别注意:
- 首先Redis中所有的键的类型都是字符串类型,而且其他几种数据结构也都是在字符串类似基础上构建的,例如列表和集合的元素类型是字符串类型,所以字符串类型能为其他4中数据结构的学习奠定基础
- 字符串类型的值实际可以是字符串,包含一般格式的字符串或者类似于JSON、XML格式的字符串;数字可以是整形或者浮点型,甚至是二进制数据,例如图片、音频、视频等。不过一个字符串的最大值不能超过512MB。
由于Redis内部存储字符串完全是按照二进制流的形式保存的,所以Redis是不处理字符集编码问题的,客户端传入的命令中使用的是什么字符集编码,就存储什么字符集编码。 (MySQL的默认字符集是拉丁文,插入中文就会失败~)
3.1 常见命令
FLUSHALL可以把Redis上所有的键值对都带走,以后在公司,尤其是生产环境的数据库中,千万不敢敲。
3.1.1 SET
将string类型的value设置到key中。如果key之前存在,则覆盖;无论原来的数据类型是什么。之前关于此key的TTL也全部失效。语法如下:
cpp
SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
Redis文档给出的语法格式说明:
\] 相当于一个独立的单元,表示可选项(可有可无的),其中 \| 表示"或者"的意思,多个只能出现一个。\[ \] 和 \[ \] 之间是可以互相同时存在的。
时间复杂度为:O(1)
选项:
SET命令支持多种选项来影响他的行为:
- EX------使用秒作为单位设置key的过期时间
- PX------使用毫秒作为单位设置key的过期时间
- NX------只在key不存在时才进行设置,即如果key之前已经存在,设置不执行
- XX ------只在key存在时才进行设置,即如果key之前不存在,设置不执行
注意:由于带选项的SET命令可以被SETNX、SETEX、PSETEX等命令代替,所以之后的版本中,Redis可能进行合并。
返回值:
- 如果设置成功,返回OK
- 如果由于SET指定了NX或者XX,但是条件不满足,SET不会执行,并返回(nil)
3.1.2 GET
获取key对应的value。如果key不存在,返回nil。如果value的数据类型不是string,会报错。语法如下:
cpp
GET key
时间复杂度为:O(1)
返回值:key对应的value,或者nil当key不存在
3.1.3 MGET
一次性获取多个key的值。如果对应的key不存在或者对应的数据类型不是string,返回nil。语法如下:
cpp
MGET key [key ...]
时间复杂度为:O(N)N是key的数量
返回值:对应value的列表。
3.1.4 MSET
一次性设置多个key的值。语法如下:
cpp
MSET key value [key value ...]
时间复杂度为:O(N)N是key的数量
返回值:永远是OK

使用mget/mset由于可以有效地减少了网络时间,所以性能相较于更高。假设网络耗时1毫秒,命令执行时间耗时0.1毫秒。学会使用批量操作,可以有效提高业务处理效率,但是要注意,每次批量操作所发送的键的数量也不是无节制的,否则可能造成单一命令执行时间过长,导致Redis阻塞。
3.1.5 SETNX
设置key-value,但是只允许在key之前不存在的情况下。语法如下:
cpp
SETNX key value
时间复杂度为:O(1)
返回值:1表示设置成功。0表示没有设置。
3.1.6 SETEX和PSETEX
设置key的过期时间,单位是秒和毫秒。
3.2 计数命令
INCR
将key对应的string表示的数字加一。如果key不存在,则视为key对应的value是0。如果key对应的string不是一个整形或者范围超出了64位有符号整形,则报错。语法如下:
cpp
INCR key
时间复杂度为:O(1)
返回值:integer类型的加完后的数值。
INCRBY
将key对应的string表示的数字加上对应的值。如果key不存在,则视为key对应的value是0。如果key对应的string不是一个整形或者范围超过了64位有符号整形,则报错。语法如下:
cpp
INCRBY key decrement
时间复杂度为:O(1)
返回值:integer类型的加完后的数值。
DECR
DECRBY
INCRBYFLOAT
将key对应的string表示的浮点数加上对应的值。如果对应的值是负数,则视为减去对应的值。如果key不存在,则视为key对应的value是0,。如果key对应的不是string,或者不是一个浮点数,则报错。允许采用科学计数法表示浮点数。语法如下:
cpp
INCRBYFLOAT key increment
时间复杂度为:O(1)
返回值:加/减完后的数值。
很多存储系统和编程语言内部使用CAS机制实现计数功能,会有一定的CPU开销,但是在Redis中完全不存在这个问题,因为Redis是单线程架构,任何命令到了Redis服务端都要顺序执行。
3.3 其他命令
APPEND
如果key已经存在并且是一个string,命令会将value追加到原有string的后边。如果key不存在,则效果等同于SET命令。语法如下:
cpp
APPEND KEY VALUE
时间复杂度为:O(1),追加的字符串一般长度较短,可以视为O(1)
返回值:追加完成之后string的长度。
在启动Redis客户端的时候,加上一个 --raw这样的选项,就可以使Redis客户端能够自动把二进制数据尝试翻译。操作linux的时候,千万注意不要乱按 crtl + s
ctrl + s 在XShell中的作用是"冻结当前画面"
ctrl + q 解除冻结
GETRANGE
返回key对应的string子串,由start和end确定(左闭右闭)。可以使用负数表示倒数。-1表示倒数第一个字符,-2表示倒数第二个字符,其他的与此类似。超过范围的偏移量会根据string的长度调整成正确的值。语法如下:
cpp
GETRANGE key start end
时间复杂度为:O(N),N为[start,end]区间的长度,由于string通常比较短,可以视为是O(1)
返回值:string类型的子串。
SETRANGE
覆盖字符串的一部分,从指定的偏移开始。语法如下:
cpp
SETRANGE key offset value
时间复杂度为:O(N),N为value的长度,由于一般给的value比较短,通常视为O(1)
返回值:替换后的string的长度。
STRLEN
获取key对应的string的长度。当key存放的类似不是string时,报错。语法如下:
cpp
STRLEN key
时间复杂度为:O(1)
返回值:string的长度。或者当key不存在时,返回0
单位是字节。
C++中,字符串的长度本身就是用字节为单位;Java中,字符串的长度则是以字符为单位。MySQL的时候,varchar(N)此处的N的单位就是字符,MySQL中的字符也是完整的汉字,这样的一个字符,也可能是多个字节。
Java中的一个char == 2字节,Java中的char基于unicode这样的编码方式能够表示中文等符号~~Java中的char是用的unicode,一个汉字使用两个字节,Java中的String则是使用的utf8,一个汉字就是3个字节了,Java的标准库内部,在进行上述的操作过程中,程序员一般是感知不到编码方式的变换的。
3.4 内部编码
字符串类型的内部编码有3种:
- int:8个字节的长整型
- embstr:小于等于39个字节的字符串
- raw:大于39个字节的字符串
Redis会根据当前值的类型和长度动态决定使用哪种内部编码实现的。
Redis存储小数,本质上还是当做字符串来存储的,这就和整数相比差距很大了,整数直接使用int来存(准确的说是一个long long)比较方便进行算法运算,小数则是使用字符串来存储,意味着每次进行算术运算,都需要把字符串转成小数,进行运算,结果再转为字符串保存。
3.5 典型使用场景
缓存功能
下图是比较典型的缓存使用场景,其中Redis作为缓冲层,MySQL作为存储层,绝大多数请求的数据都是从Redis中获取的。由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。

计数功能
许多应用都会使用Redis作为计数的基础工具,他可以实现快速计数,查询缓存的功能,同时数据可以异步处理或者落地到其他数据源。如图所示:例如视频网站的视频播放次数可以使用Redis来完成,用户每播放一次视频,响应的视频播放数就会自增1。

实际上要开发一个成熟,稳定的真实计数系统,要面临的挑战远不止如此简单:防作弊、按照不同维度计数,避免单点问题,数据持久化到底层数据源等。
共享会话(Session)
一个分布式Web服务将用户的Session信息(例如用户登录信息)保存在各自的服务器中,但这样会造成一个问题:处于负载均衡的考虑,分布式服务会将用户的反问请求均衡到不同的服务器上,并且通常无法保证用户每次请求都会被均衡到同一台服务器上,这样当用户刷新一次访问是可能会发现需要重新登录,这个问题是用户无法容忍的。

为了解决这个问题,可以使用Redis将用户的Session信息进行集中管理,在这种模式下,只要保证Redis是高可用和可扩展性的,无论用户被均衡到哪一台Web服务器上,都集中从Redis中查询,更新Session信息。
手机验证码
很多应用处于安全考虑,会在每次进行登录时,让用户输入手机号并且配合给手机号发送验证码,然后让用户再次输入收到的验证码并进行验证,从而确定是否是用户本人。

四、Hash哈希
几乎所有的主流编程语言都提供了哈希类型,他们的叫法可能是哈希,字典,关联数组,映射。在Redis中,哈希类型是指值本身又是一个键值对结构,形如key = "key",value = { {} }。
哈希类型中的映射关系通常为field-value,用于区分Redis整体的键值对(key-value),注意这里的value是指field对应的值,不是键(key)对应的值,请注意value在不同上下文的作用。
4.1 基本命令
HSET
设置hash中指定的字段(field)的值(value).语法如下:
HGET
HEXISTS
HDEL
HKEYS
HVALS
HGETALL
HMSET
HSCAN
HLEN
HSETNX
HINCRBY
HINCRBYFLOAT
4.2 内部编码
哈希的内部编码有两种:
- ziplist(压缩列表):当哈希类型元素个数小于 hash-max-ziplist-entries 配置(默认512个),同时所有值都小于 hash-max-ziplist-value 配置(默认64字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更优秀
- hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)
压缩:
rar,zip,gzip,7z等一些具体的压缩算法。
压缩的本质是针对数据进行重新编码,不同的数据有不同的特点,结合这些特点,进行精妙的设计,重写编码之后,就能缩小体积~~
ziplist也是精心设计的,目的是节省内存空间,但是进行读写元素时,速度是比较慢的。如果元素个数少,慢的并不明显;如果元素个数太多了,慢就会雪上加霜。
4.3 使用场景
缓存功能
存储结构化的数据使用hash类型更合适一些~~~

4.4 缓存方式对比
截止目前为止,我们已经有三种方式缓存用户信息,下面给出三种方案的实现方法和优缺点分析:
原生字符串类型------使用字符串类型,每一个属性对应一个键
cpp
set user:1:name James
set user:1:age 23
set user:1:city Beijing
- 优点:实现简单,针对个别属性变更也很灵活
- 缺点:占用过多的键,内存占用量较大,同时用户信息在Redis中比较分散,缺少内聚性,所以这种方案没有实用性。
序列化字符串类型,例如JSON格式
cpp
set user:1 经过序列化后的⽤⼾对象字符串
- 优点:针对总是以整体作为操作的信息比较合适,编程也简单。同时,如果序列化方案选择合适,内存的使用效率很高。
- 缺点: 本身序列化和反序列需要一定开销,同时如果总是操作个别属性则非常不灵活。
哈希类型
cpp
hmset user:1 name James age 23 city Beijing
- 优点:简单,直观,灵活。尤其是针对信息的局部变更或者获取操作。
- 缺点:需要控制哈希在ziplist和hashtable两种内部编码的转换,可能会造成内存的较大消耗。

五、list列表
列表类型是用来存储多个有序的字符串,列表中的每一个字符串称为元素,一个列表最多可以存储 2^32 - 1 个元素。在Redis中,可以对列表两端插入和弹出,还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,他可以充当栈和队列的角色,在实际开发中有很多应用场景。

列表类型的特点:
- 列表中的元素是有序的,这个有序不是说这个列表中存储的元素是有序的,而是说,不同顺序的列表不同,即使他们的元素是相同的。
- 区分获取和删除的区别,删除元素的话,会将这个列表的长度减少;但是执行 lindex 4 只会获取元素,但是列表长度是不会变化的。
- 列表中的元素是允许重复的。
列表(List)相当于数组或者顺序表,注意,list内部的结构(编码方式)并非是一个简单的数组,而是更接近于"双端队列"(deque)。因为当前的List,头和为都能高效的插入和删除元素,就可以把这个List当做一个栈或者队列来使用。
Redis有一个典型的应用场景,就是作为消息队列,最早的时候,就是通过List类型~~,后来,Redis又提供了一个stream类型。
5.1 基本命令
LPUSH
将一个或者多个元素从左侧放入(头插)到list中,语法如下:
cpp
LPUSH key element [element ...]
时间复杂度:只插入一个元素为O(1),插入多个元素为O(1),N为插入元素的个数。
返回值:插入后list的长度。
LPUSHX
在key存在时,将一个或者多个元素从左侧放入(头插)到list中。不存在,直接返回。语法如下:
cpp
LPUSHX key element [element ...]
时间复杂度:只插入一个元素为O(1),插入多个元素为O(N),N为插入的元素个数。
返回值:插入后的list的长度。
RPUSH
RPUSHX
LRANGE
获取从start到stop区间的所有元素,左闭右闭。语法如下:
cpp
LRANGE key start stop
时间复杂度为:O(N)
返回值:指定区间的元素
LPOP
从list左侧取出元素(即头删)。语法如下:
cpp
LPOP key
时间复杂度为:O(1)
返回值:取出的元素或者nil
RPOP
LINDEX
获取从左边第index位置的元素。语法如下:
cpp
LINDEX key index
时间复杂度为:O(N)
返回值:取出的元素或者nil
LINSERT
在特定位置插入元素,语法如下:
cpp
LINSERT key <BEFORE | AFTER> pivot element
时间复杂度:O(N)
返回值:插入后的list长度
LLEN
获取list长度,语法如下:
cpp
LLEN key
时间复杂度:O(1)
返回值:list的长度
5.2 阻塞版本命令
blpop和brpop是lpop和rpop的阻塞版本,和对应非阻塞版本的作用基本一致,除了:
- 在列表中有元素的情况下,阻塞和非阻塞版本表现是一致的。但是如果列表中没有元素,非阻塞版本会立即返回nil,但是阻塞版本会根据timeout,阻塞一段时间,期间Redis可以执行其他命令,但是要求执行该命令的客户端会表现为阻塞状态。
- 命令中如果设置了多个键,那么会从左向右进行遍历键,一旦有一个键对应的列表中可以弹出元素,命令立即返回。
- 如多个客户端同时多个键执行pop,则最先执行命令的客户端会得到弹出的元素。

BLPOP
LPOP的阻塞版本,语法如下:
cpp
BLPOP key [key ...] timeout
返回值:取出的元素或者nil
BRPOP
RPOP的阻塞版本,语法如下:
cpp
BRPOP key [key ...] timeout
返回值:取出的元素或者nil
5.3 内部编码
对于现在的列表类型的内部编码已经变为了quicklist,quicklist箱单与链表和压缩列表的结合。整体还是一个链表,链表的每一个节点是一个压缩列表。每一个压缩列表都不让他太大,同时再把多个压缩列表通过链式结构连起来~~

下面介绍的编码已经不在使用了:
列表类型的内部编码有两种:
- ziplist(压缩列表):当列表的元素个数小于 list-max-ziplist-entries 配置(默认512个),同时列表中每一个元素的长度都小于 list-max-ziplist-value 配置(默认64字节)时,Redis会选用ziplist来作用列表的内部编码实现来减少内存消耗。
- linkedlist(链表):当列表类型无法满足 ziplist 的条件时,Redis会使用 linkedlist 作为列表的内部实现。
5.4 使用场景
存储多个元素,进行查询
可以使用列表作为"数组"来存储多个元素,Redis提供的查询功能不像MySQL那样强大。

消息列表
Redis可以使用 lpush + brpop 命令组合实现经典的阻塞式生产者-消费者模型队列,生产者客户端使用 lpush 从列表左侧插入元素,多个消费者客户端使用 brpop 命令阻塞式地从队列中"争抢"队首元素。通过多个客户端来保证消费的负载均衡和高可用性。

只有一个消费者能"抢到"元素,谁先执行的这个brpop命令,谁就能拿到这个新来的元素,像这样的设定,就能构成一个"轮询"。
分频道的消息列表
Redis同样使用 lpush + brpop 命令,但是通过不同的键模拟频道的概念,不同的消费者可以通过 brpop 不同的键值,实现订阅不同频道的理念。

多个列表/频道,这种场景非常常见,日常使用的一些程序中,抖音等都有这些。有一个通常来传输短视频数据,还可以有一个通道来传输数据,还可以有频道来传输点赞,转发等,还可以有频道来传输评论和数据。
微博Timeline
每一个用户都有属于自己的Timeline(微博列表),现需要分页展示文章列表。此时可以考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素。
选择列表类型时,请参考:
- 同侧存取(lpush + lpop 或者 rpush + rpop)为栈
- 异侧存取(lpush + rpop 或者 rpush + lpop)为队列
六、Set集合
集合类型也是保存多个字符串类型的元素,但是和列表类型不同的是,在集合中:
- 元素之间是无序的
- 元素不允许重复,一个集合中最多可以存储2^32 - 1个元素
Redis除了支持集合内的增删查改操作,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型,能在实际开发中解决很多问题。

6.1 普通命令
SADD
将一个或者多个元素添加到set中。注意,重复的元素无法添加到set中。语法如下:
cpp
SADD key member [member ...]
时间复杂度为:O(1)
返回值:本次添加成功的元素个数
SMEMBERS
获取一个set中的所有元素,注意,元素间的顺序是无序的。语法如下:
cpp
SMEMBERS key
时间复杂度:O(N)
返回值:所有元素的列表
SISMEMBER
判断一个元素在不在set中,语法如下:
cpp
SISMEMBER key member
时间复杂度:O(1)
返回值:1表示元素在set中,0表示元素不在set中或者key不存在
SCARD
获取一个set的基数,即set中的元素个数。语法如下:
cpp
SCARD key
时间复杂度为:O(1)
返回值:set内的元素个数
SPOP
从set中删除并返回一个或者多个元素。注意,由于set内的元素是无序的,所以取出哪一个元素是未定义行为,即可以看做随机的。语法如下:
cpp
SPOP key [count]
时间复杂度:O(N),N是count
返回值:取出的元素
SMOVE
将一个元素从源set取出并放入目标set中,语法如下:
cpp
SMOVE source destination member
时间复杂度:O(1)
返回值:1表示移动成功,0表示失败
SREM
将指定的元素从set中删除,语法如下:
cpp
SREM key member [member ...]
时间复杂度:O(N),N是要删除的元素个数
返回值:本次操作删除的元素个数
6.2 集合间的操作
SINTER
获取给定set的交集中的元素,语法如下:
cpp
SINTER key [key ...]
时间复杂度:O(N * M),N是最小的集合元素个数,M是最大的集合元素个数
返回值:交集的元素
SINTERSTORE
获取给定set的交集中的元素并保存到目标set中。语法如下:
cpp
SINTERSTORE destination key [key ...]
时间复杂度:O(N * M),N是最小的集合元素个数,M是最大的集合元素个数
返回值:交集的元素个数
SUNION
SUNIONSTORE
SDIFF
SDIFFSTORE
6.3 内部编码
集合类型的内部编码有两种:
intset(整数集合)
hashtable(哈希表)
6.4 使用场景
集合类型比较典型的使用场景是标签。例如A用户对娱乐、体育板块比较感兴趣,B用户对历史、新闻比较感兴趣,这些兴趣点可以被抽象为标签。有了这数据就可以得到喜欢同一个标签的人,以及用户的共同爱好的标签,这些数据对于增强用户体验和用户粘度都非常有帮助。例如,一个电子商务网站会对不同标签的用户做不同的产品推荐。
cpp
sinter user:1:tags user:2:tags
使用Set来计算用户之间的共同好友
基于"集合求交集"
使用Set统计UV
去重
一个互联网产品,如何衡量用户量,用户规模??
主要的指标是两个方面:
PV(page view):用户每次访问该服务器,每次访问都会产生一个pv
UV(user view) :每一个用户访问服务器,都会产生一个uv,但是同一个用户多次访问,不会使uv增加,uv需要按照用户进行去重,上述的去重过程,就可以使用set去实现。
七、Zset有序集合
有序集合相对于字符串、列表、哈希、集合来说会有一些陌生。他保留了集合不能有重复成员的特点,但是与集合不同的是,有序集合中的每一个元素都有一个唯一的浮点数类型的分数与之关联,使得有序集合中的元素是可以维护有序性的,但是这个有序不是用下标作为排序依据而是用这个分数。
有序集合提供了获取指定分数和元素范围查找,计算成员排名等功能,合理地利用有序集合,可以帮助我们在实际开发中解决很多问题。
有序集合中的元素是不能重复的,但是分数允许重复。类比于一次考试之后,每一个人一定有一个唯一的分数,但是分数允许相同。
7.1 普通命令
7.2 集合间的操作
7.3 内部编码
有序集合类型的内部编码有两种:
ziplist(压缩列表)
skiplist(跳表)
7.4 使用场景
有序即可比较典型的使用场景就是排行榜系统。