目录
- 前言
- 一、数据类型
- 二、Redis单线程模型
- 三、String类型
- 四、什么是业务
- 五、Hash类型
- 六、List类型
- 七、SET类型
- 八、ZEST类型
前言
一、数据类型
Redis主要有Strings、Lists、Sets、Hashes、Sorted sets等数据类型,这些都是非常通用的,还有一些少见的可以去官网了解
Strings相当于C++中的字符串类型
Hashes相当于C++中的unordered_map
Lists相当于C++中的deque
Sets相当于C++中的Set
Sorted sets是有序集合,除了存储member之外,还需要存储一个score(权重)
Redis底层在实现上述数据结构的时候,会在源码层面,针对上述实现进行特定的优化,来达到节省时间/节省空间的效果。
特定的优化:内部的具体实现的数据结构,还会有变数
Redis承诺,假设现在有个hash表,你进行查询、插入、删除操作,都保证O(1)的时间复杂度,但这个背后的实现,不一定就是标准的哈希表,可能在特定场景下,使用别的数据结构实现,但仍然保证时间复杂度符合承诺
如下图,数据结构,表示是Redis承诺给你的,也可以理解为数据类型,内部编码则是Redis内部底层的实现,所以同一个数据类型,背后可能的编码实现方式是不同的—因为Redis会根据特定的场景优化!!!
raw:最基本的字符串,底层是持有一个如同C++的char数组
int:Redis通常也可以用来实现一些"计数"这样的功能,当value是一个整数的时候,此时Redis可能直接采用int来保存
embstr:针对短字符串进行特定的特殊优化
hashtable:最基本的哈希表
ziplist:当哈希表里面元素比较少的时候,可能就优化成ziplist了,因为压缩列表,能够节省空间
为什么要压缩?
redis上有很多的key,可能是某些key的value是hash,此时,如果key特别多,对应的hash也就特别多,但是每个hash又不大的情况下,就尽量去压缩,压缩之后就可以让整体占用的内存更小了
linkedlist和ziplist:从redis3.2开始,引入了新的实现方式,即quicklist,同时兼顾了linkedlist和ziplist的优点,quicklist就是一个链表,每个元素又是一个ziplist,把空间和效率都折衷的兼顾到,类似于C++中的deque
intset:集合中存的都是整数
skiplist:跳表,是一种链表,查询元素的时间复杂度为O(logN)
object encoding key
查看key对应的value的实际编码方式
Redis会自动根据当前的实际情况选择内部的编码方式,是自适应的!
二、Redis单线程模型
Redis只使用一个线程,处理所有的命令请求,并不是说Redis服务器内部真的就只有一个线程,其实有多个线程,多个线程是在处理网络IO
如下图,当两个客户端线程去对key的value进行+1操作时,redis-server并不会发生线程安全问题,因为它是单线程模型,保证了当前收到的这多个请求是串行执行的!
Redis能够使用单线程模型很好的工作,原因主要在于Redis的核心业务逻辑,都是短平快的,不太消耗CPU资源,也就不太吃多核了!!!
弊端:Redis必须要特别小心,某个操作占用时间长,就会阻塞其他命令的执行!
Redis虽然是单线程模型,为啥效率这么高呢?速度这么快呢?相对于MySQL等其他数据库而言
1、Redis访问内存,而数据库则是访问硬盘
2、Redis核心功能比数据库的核心功能更简单
数据库对于数据的插入删除查询都有更复杂的功能支持,这样的功能势必要有更多的开销,比如针对插入删除,数据库中的各种约束,都会使数据库做额外的工作
3、单线程模型,避免了一些不必要的线程竞争开销
Redis的每个基本操作,都是短平快的,就是一些简单操作一下内存数据,不是什么特别消耗cpu的操作,就算搞多个线程,也提升不大
4、处理网络IO的时候,使用了epoll这样的IO多路复用机制
三、String类型
Redis中的字符串,是直接按照二进制数据的方式存储的,即不会做任何的编码转换
SET命令
将string类型的value设置到key中。如果key之前存在,则覆盖,⽆论原来的数据类型是什么。之
前关于此key的TTL也全部失效。
[]相当于一个独立的单元,表示可选项,即可有可无
|表示或者的意思,多个只能出现一个
上述中的expiration表示设置过期时间,EX:单位是秒,PX:单位是毫秒,如下图
上述中的NX:如果key不存在,才设置,如果key存在,则不设置(返回nil)
上述中的XX:如果key存在,才设置(相当于更新value),如果key不存在,则不设置(返回nil)
GET命令
get只是支持字符串类型的value,如果value是其它类型,则会报错
MSET命令
一次设置多个key,时间复杂度O(N),N是当前设置的key的数量,返回值永远是OK
MGET命令
一次获取多个key对应的value,时间复杂度O(N),N是要获取的key的数量,如果key不存在或对应的value不是string,则返回nil
SETNX命令
不存在才能设置,存在则设置失败,1表示设置成功,0表示设置失败
SETEX和PSETEX
设置key的过期时间,单位是秒和毫秒
INCR命令
针对value,+1,时间复杂度O(1)
INCRBY命令
针对value,+n,时间复杂度O(1)
也可以加一个负数,即减去一个数
DECR命令
针对value,-1,时间复杂度O(1)
DECRBY命令
针对value,-n,时间复杂度O(1)
INCRBYFLOAT命令
把key对应的value进行±运算,运算的操作数可以是浮点数,只能通过加负数的方式来实现减法,时间复杂度O(1)
注意:由于redis处理命令的时候,是单线程模型,多个客户端同时针对同一个key进行incr操作,不会引起 “线程安全” 问题
APPEND命令
拼接字符串,返回值是长度的单位:字节
如下图,如果value是中文,不会直接显示,因为redis的字符串,不会对字符编码做任何处理,所以只会显示16进制,要想显示中文,需要在开启客户端时,带–raw选项
GETRANGE命令
获取子串,两端都是闭区间,时间复杂度O(N),N是子串的长度,其中区间两端可以使用负数,-1表示倒数第一个,-2倒数第二个…
SETRANGE命令
覆盖字符串的一部分,从指定的位置偏移,时间复杂度O(N),N是value的长度,返回值为替换后的string的长度
如下图,针对中文的替换,就可能会出现问题
如下图,针对不存在的key,setrange也会生成一个string
STRLEN命令
获取字符串的长度,单位是字节,时间复杂度O(1),如果key不存在,会返回0
string内部的三种编码方式
int:64位/8字节的整数
embstr:压缩字符串,适用于表示比较短的字符串
raw:普通字符串,适用于表示更长的字符串,只是单纯的持有字节数组
redis存储浮点数,本质上还是当字符串来存储,每次进行算术运算,都需要将字符串转化为小数,然后再转回字符串来存储
string类型的应用场景
缓存功能
redis这样的缓存,经常用来存储"热点"数据,即高频被使用的数据
redis作为缓存时,当应用服务器访问数据的时候,先查询redis,如果redis中有数据,就直接从redis取数据交给应用服务器,如果redis中数据不存在,则读取MySQL中的数据,返回给应用服务器,同时,把这个数据也写入到redis中
上述策略中,存在一个很明显的问题,随着时间的推移,会有越来越多的数据在redis中访问不到,就会从MySQL中获取数据,并写入redis,此时,redis中的数据就会越来越多!!!
解决方式
1、把数据写入到redis中时,给这个key设置一个过期时间
2、redis也在内存不足的时候,提供了淘汰策略
计数功能
如上图,redis也适用于计数,比如给视频网站中视频的播放次数计数,时间复杂度为O(1),速度很快,但redis并不擅长数据统计,比如统计播放量前100的视频有哪些,基于redis搞就很麻烦,而MySQL,只需要一个sql语句就行了
实际中要开发一个成熟、稳定的真实计数系统,要面临的挑战远不止如此简单,比如要实现防作弊、按照不同维度计数,避免单点问题,数据持久化到底层数据源等等
session会话功能
如上图,如果每个应用服务器,维护自己的会话数据,彼此之间不共享,用户请求访问到不同的服务器上,就可能会出现一些不能正确处理的情况
如上图,把session会话数据保存在redis中,这样所有的会话数据就都被各个服务器共享了
手机验证码功能
生成验证码
用户输入一下手机号,点击获取验证码,比如限制1分钟之内,最多获取5次验证码或者每次获取验证码时间间隔为30秒,主要还是为了防止用户频繁获取验证码,造成服务器压力过大
检查验证码
把短信收到的验证码这一串数,提交到系统中,系统判断验证码是否正确
四、什么是业务
业务就算公司/产品解决一个/一系列问题的过程,就可以称为"业务"
一个公司/产品要想生存,就得赚钱,而要想赚钱,就得能帮别人解决问题!!!
不同的公司,不同的产品,就有不同的业务,而不同的业务就需要不同的技术作为支撑。业务是非常重要的,很多时候,优化技术解决不了的问题,就可以通过优化业务来解决,比如早期的12306,假如要放出2月1日的全部票,会在之前的具体1月10日全部放出,而这样会造成很多人去抢票,从而很大可能造成服务器瘫痪,但后面引入了分时段放票,就可以减少服务器的负载
注意:实际开发过程中,必须要结合实际业务场景,来做一些技术上的调整!!!
五、Hash类型
redis本身就是键值对结构,而且是通过哈希的方式来组织的,为了区分value中的key和redis本身的key,就把value中的key用field来表示,而此时的value也变为了field对应的value
HSET命令
一次插入一组或多组键值对,时间复杂度为O(N),N为插入的键值对的个数,返回值为设置成功的键值对的个数
HGET命令
获取field对应的value,一次只能获取一个,时间复杂度O(1),如果不存在,返回空
HEXISTS命令
判断hash中是否存在指定的字段,一次只能判断一个,时间复杂度O(1),1表示存在,0表示不存在
HDEL命令
删除hash中指定的字段,即删除field,时间复杂度O(N),N为要删除的字段的个数,返回值为本次操作删除的字段的个数
HKEYS命令
获取hash中所有的field,时间复杂度O(N),N为hash中field的个数,返回值为field列表
HVALS命令
获取hash中所有的value,时间复杂度O(N),N为hash中value的个数,返回值为value列表
HGETALL命令
获取hash中所有的field和value,时间复杂度O(N),N为hash中字段的个数,返回值为hash中所有的field和value,且是一个field,一个value排列
注意:上述这三个命令存在一定的风险,要谨慎使用!!!
HMGET命令
类似于MGET命令,可以一次查询多个field,查询的field的顺序和显示相应的value的顺序是一致的,时间复杂度O(N),N为查询的field的个数
注意:也存在HMSET命令,只是HSET命令也能设置多个field,所以可以不用HMSET命令了
HLEN命令
获取hash中的元素个数,不需要遍历,时间复杂度O(1)
HSETNX命令
类似于SETNX命令,不存在的时候,才能设置成功,返回1,如果存在,则失败,返回0
注意:hash中的value,也能当作数字来处理!!!
HINCRBY命令(整数)
给hash中指定field对应的value加/减去一个值,时间复杂度O(1),返回值为value变化后的值
HINCRBYFLOAT命令(浮点数)
给hash中指定field对应的value加/减去一个值,时间复杂度O(1),返回值为value变化后的值
Hash类型的编码方式
压缩算法:rar、zip、gzip等等
压缩的本质:是对数据进行重新编码,而不同的数据,有不同的特点,结合这些特点,进行精妙的设计,重新编码之后,就能够缩小体积,比如aabbbbccccccdd这个字符串,就可以压缩成2a4b6c2d,不过这种压缩方式比较粗糙,而实际上常见的压缩算法都是精妙设计的
一个普通的hash表,可能会浪费一定的空间,因为hash表首先得是一个数组,就会出现的位置有元素,有的没有,所以在数据少的时候,就用ziplist来代替hash表,而ziplist也是同上,内部的数据结构也是精心设计的,目的是为了节省空间,但zip付出的代价,是进行读写元素,速度是比较慢的,元素个数少倒还好,元素个数多了,慢就会雪上加霜
只有当哈希中的元素个数比较少,且每个value的值长度都比较短时,才会用ziplist代替hash表
Hash类型的应用场景
缓存功能
string也能作为缓存使用,但存储结构化的数据时,hash类型更合适
如上图,虽然string也能做到,但需要使用到json这样的数据格式
如果使用string(json)的格式来表示UserInfo,万一只想要获取其中的某个属性,或者修改某个属性,那就需要把整个json都读出来,解析成对象,操作属性,再转回json字符串,写回去,就会很麻烦,而hash类型能很方便的处理这个问题
hash类型的缺点:付出的是空间的代价,需要控制hash在ziplist和hashtable两种内部编码的转换,可能会造成内存的较大消耗
采用原生字符串类型,即一个属性一个键的方式,如下图
上述这种方式虽然也可以,但是属于低内聚,不符合高内聚,低耦合的设计,代码处理起来就会很麻烦
高内聚:把有关联的东西放在一起,最好能放在指定的地方,便于查找,以衣服为例,如果你把你的衣服扔得到处都是,而你要出门,想穿某件衣服,就需要去找,而此时就需要遍历整个房子,非常的耗时间,即低内聚。而如果你把衣服都整理好,整整齐齐放在衣柜里,就能很快得找到你想穿的衣服,即高内聚
耦合:两个模块/代码之间的关联关系越大,越容易相互影响,认为是耦合越高,反之,越低,比如一个模块出现了bug,而另一个模块也随之不能正常工作,就说明两个模块之间的耦合度很高
如下图,右边的第一行,理论上是可以不存的,还能节省空间,但在工程实践中,一般都会把uid再存一份,后续写到相关的代码时,会更方便一些
六、List类型
列表(List)相当于数组或者顺序表,而底层结构则类似于C++中的deque,支持头插、头删、尾插、尾删,同时,也支持负数下标,最左侧元素的下标为0
列表的元素是"有序"的,而这里"有序"的含义,要根据上下文来进行区分,有的时候,是指升序或者降序,而有的时候是指顺序很关键,比如一模一样的列表,只是元素的顺序不同,这两个列表就算不一样的!!!
相对于hash类型中的field,列表的元素是允许重复的!!!
因为当前的List支持头插、头删、尾插、尾删,就可以把这个List当作一个栈/队列来使用
LPUSH命令
头插一个或多个元素,时间复杂度O(N),N为插入元素的个数,返回值为插入后list的长度
如果key已经存在,并且key对应的value类型,不是list,lpush命令就会报错,其它数据类型也类似
LRANGE命令
两端都为闭区间,下标支持负数,时间复杂度O(N)
针对下标越界的情况,redis是尽可能的获取到给定区间的元素,增强了程序的容错能力
注意:lrange中的l指的是list,不是left,所以不存在RRANGE命令
LPUSHX命令
在key存在时,将⼀个或者多个元素从头插到list中。不存在,直接返回0,时间复杂度O(N),N为插入元素的个数,返回值为插入后list的长度
RPUSH命令
尾插一个或多个元素,时间复杂度O(N),N为插入元素的个数,返回值为插入后list的长度
RPUSHX命令
在key存在时,将⼀个或者多个元素从头插到list中。不存在,直接返回0,时间复杂度O(N),N为插入元素的个数,返回值为插入后list的长度
LPOP命令
头删,时间复杂度O(1),返回值为删掉的元素或空
RPOP命令
尾删,时间复杂度O(1),返回值为删掉的元素或空
LINDEX命令
获取第index位置的元素,时间复杂度O(N),N为list中的元素个数,如果下标非法,则返回nil
LINSERT命令
在指定值的前面/后面插入元素,时间复杂度O(N),返回值是插入之后,得到的新的list的长度
基准值不存在时,会返回-1
存在多个基准值时,会从左往右找,在找到的第一个基准值前面/后面插入
LLEN命令
获取列表的长度,时间复杂度O(1)
LREM命令
lrem,是list remove的简写,count>0,表示从左往右删除count个指定的值;count<0,表示从右往左删除count的绝对值个指定的值;count=0,表示删除所有的指定的值,时间复杂度为O(N+M),N是列表的长度,M是要删除的元素的个数
LTRIM命令
除开指定区间内的元素,其它全部删除,时间复杂度O(N),N是被删除元素的个数
LSET命令
根据下标,修改元素,时间复杂度O(N),N是列表的长度,首尾元素为O(1),下标越界会直接报错
BLPOP命令和BRPOP命令
这里的B是阻塞block的缩写,阻塞:当前的线程不走了,代码不继续执行了,会在满足一定的条件后被唤醒,这里和生产者-消费者模型中的阻塞队列很类似,不同的是,由于list的长度没确定,所以这里只会阻塞队列为空的情况。所以当队列中有元素时,blpop、brpop和lpop、rpop作用完全相同,如果队列为空,blpop和brpop会产生阻塞,一直阻塞到队列不为空为止
阻塞版本会根据timeout,阻塞一段时间,单位是秒,期间redis可以执行其它命令,并不会对redis服务器产生负面影响!!!
如果多个客户端同时对一个键执行pop,则最先执行命令的客户端就会得到弹出的元素
如下图,这种返回结果,告诉我们数据是来自哪个key,数据是什么
如下图,当别的客户端往空列表中插入元素后,当前客户端就会立即返回
如下图,命令可以一次等待多个key,遍历键时,只要有一个键对应的列表可以弹出元素,命令立即返回
list的内部编码
list内部采用的是quicklist的编码方式,是ziplist和linkedlist的结合,整体是一个双向链表,链表的每个节点是一个压缩列表,而每个压缩列表,都不让它太大
List类型的应用场景
"数组"存储功能
list作为"数组",来存储多个元素,不过提供的查询功能,不如MySQL那么强大,具体redis中
的数据如何组织,都要根据实际的业务情况来决定
消息队列功能
基于生产者—消费者模型,当列表为空时,新生产一个元素时,谁先执行brpop命令,谁就能先拿到这个新来的元素,像这样的设定,就能构成一个"轮询"式的效果,比如消费者拿到一个元素后,还想继续,就得重新执行brpop命令,即重新排队
如下图,是一个分频道的消息队列,即有多个列表,例如抖音,有一个列表,用来传输短视频数据,另一个列表,用来传输弹幕,还有一个列表,来传输点赞,转发,收藏数据等等。而这样的好处在于:就可以在某种数据出现问题的时候,不会对其他数据造成影响(解耦合)
微博Timieline功能
每个用户都有属于自己的Timeline(微博列表),现需要分页展示⽂章列表。此时可以考虑使⽤列表,因为列表不但是有序的,同时⽀持按照索引范围获取元素。
每篇微博使⽤哈希结构存储,例如微博中3个属性:title、timestamp、content
向用户Timeline添加微博,user::mblogs作为微博的键:
分页获取用户的Timeline,例如获取用户1的前10篇微博:
如上图,当前一页有多少数据,是不确定的,可能会导致下面的循环次数比较多,从而会触发很多次的hgetall,也就是很多次的网络请求,为了解决这个问题,可以采用pipeline(流水线/管道),虽然咱们依旧是多个redis命令,但是把这些命令合并成一个网络请求进行通信,就能大大降低客户端和服务器之间的交互次数
当list长度很长时,获取列表中间的元素表现很差,这时可以将其分割,比如长度为1w,可以将其拆分为10份,即每个就是1k,这样获取元素容易多了!!!
七、SET类型
注意:这里的set是集合的意思,不是get对应的set(设置)!
集合就是把一些有关联的数据放在一起,redis中的集合有两个特点:
集合中的元素是无序的,和list中的是对应的
集合中的元素是不能重复的(唯一的)
set和list一样,集合中的每个元素,也都是string类型,也就可以使用json这样的格式让string也能存储结构化数据
SET命令
将⼀个或者多个元素添加到set中,重复的元素无法加入,时间复杂度O(1),返回值为添加成功的元素
SMEMBERS命令
获取一个集合的所有元素,时间复杂度O(N),N是集合的元素个数
SISMEMBER
判断一个元素是否在集合中,时间复杂度O(1),在就返回1,不在或key不存在就返回0
SCARD命令
获取集合的元素个数,时间复杂度O(1)
SPOP命令
随机删除一个或多个集合中的元素,count不写时就删除1个,写时,是几就删除几个,时间复杂度O(N),N是删除的元素个数,返回值为取出来的元素,没有元素了就返回nil
如下图,插入同样元素且插入顺序相同的集合,删除的元素顺序是不一致的,也证明了删除元素的随机性
官方文档给出的说明,也承诺了这点!
SMOVE命令
将一个元素从集合删除,移动到另一个集合中,时间复杂度O(1),移动成功返回1,失败返回0
如下图,当再给原来的集合插入相同的数据,然后移动到新的集合,redis并没有报错
SREM命令
将指定的一个或多个元素从集合中删除,时间复杂度O(N),N是要删除的元素个数,返回值为本次操作删除成功的元素个数
SINTER命令
获取两个或多个集合的交集,事件复杂度O(N*M),N是最小的集合元素个数,M是最大的集合元素个数
SINTERSTORE命令
将多个集合的交集保存在另外的集合中,时间复杂度同上,如果该集合存在,则会将之前的元素覆盖
SUNION命令
获取两个或多个集合的并集,时间复杂度为O(N),N是给定的所有集合的总的元素个数
SUNIONSTORE命令
将多个集合的并集保存在另外的集合中,时间复杂度同上,如果该集合存在,则会覆盖之前的元素
SDIFF命令
获取set的差集中的元素,时间复杂度为O(N),N是给定的所有集合的总的元素个数
SDIFFSTORE命令
获取给定set的差集中的元素并保存到另外的集合中,时间复杂度同上,如果集合已经存在,则会覆盖之前的元素
set的内部编码
分为intset(整数集合)和hashtable(哈希表)两种,intset是为了节省空间,做出的特定优化,用于元素均为整数,并且元素个数不是很多的场景
SET类型的应用场景
保存用户标签
用户画像,即产品的服务器后台会分析出你这个人的一些特征,比如性别、年龄、居住地、爱好等等,然后根据这些特征,就可以转换成"标签",也就是简短的字符串,保存到redis的set中,再投其所好,给你推送与之相关的内容
这个功能对用户有一个很大的弊端,你看到的东西,都是你愿意看到的,你不愿意看的,就很难被看到,也就是信息茧房,你看到的内容始终是一个小圈子,比如你认真看了一个焦虑的视频,后期服务器给你一直推送这类视频,就会搞乱人的心态!
计算用户之间的共同好友
因为set很容易求交集,所以在找两个用户的共同好友时,也就很容易了,比如QQ,有些时候就会告诉你与对方有哪些共同好友。同时,还可以做一下好友推荐,比如A和B是好友,A和C是好友,B和C和D都是好友,系统就可能会把D推荐给A
统计UV
一个互联网产品,衡量用户量,用户规模,主要的指标是两方面:
PV,即page view,用户每次访问该服务器,都会产生一个pv
UV,即user view,每个用户,访问该服务器,都会产生一个uv,但同一个用户多次访问,不会使uv增加,这就需要按照用户进行去重,就可以使用set来实现
八、ZEST类型
zset是有序集合,这里的有序指的是升序/降序中的有序,当然,redis内部实现是升序!排序的依据是分数,存储的一个pair,和C++中的类似,pair由member和score构成,score是浮点类型。不同于键值对,这里的pair可以用member查找score,也可以用score查找pair。同时,zset中的member是唯一的,但score可以重复
ZADD命令
往有序集合中添加pair,添加一个pair的时间复杂度为O(logN),N是有序集合中pair的个数,默认返回值为添加成功的pair个数
zrange命令查看添加的pair的member,带withscores可以查看score,因为有序集合的元素是有先后顺序的,所以就可以给这个有序集合赋予下标这样的概念!
不带NX或XX选项的时候,添加的member已经存在,就会更新分数
带有XX选项时,只会更新已经存在的元素,不会添加元素
带有NX选项时,只会添加新的元素,不会更新已经存在的元素
本来zadd返回的时新增的元素个数,带了CH选项后,zadd的返回值变为了新增和修改的元素个数
带有INCR选项时,就类似于zincrby命令,给元素的分数添加值
注意:上述的更新操作,可能会导致元素的位置发生变化,因为这是有序集合,所以要始终保持有序,同时,在两个元素分数相同的情况下,会根据member,按照字典序排序
ZCARD命令
获取有序集合中元素的个数,时间复杂度O(1)
ZCOUNT命令
获取分数在min和max之间的元素个数,默认为闭区间,如果想排除边界值,可以加 “(”,不过排除右边的边界值时,方式有点奇葩
时间复杂度O(logN),N是有序集合中元素的个数,这里是先找到左右端点元素的下标,然后根据下标减法得到元素的个数
在浮点数中,存在两个特殊的数值,inf:无穷大,-inf:负无穷大,zset中分数也是支持使用inf和-inf作为max和min的
ZRANGE命令
获取指定区间内的元素,时间复杂度O(log(N)+M),M是start - stop区间内元素的个数,N是有序集合中元素的个数
ZREVRANGE命令
按照分数降序进行遍历并打印,时间复杂度同上
ZRANGEBYSCORE命令
根据分数来找元素,和zcount命令类似,时间复杂度O(logN+M),M是区间内元素的个数,N是有序集合中元素的个数
ZPOPMAX命令
删除并返回count个分数最高的元素,时间复杂度O(M*logN),N是有序集合中元素的个数,M是要被删除的元素个数
这里是尾删,可以将最后一个元素的位置特殊记录下来,后续删除时,时间复杂度就为O(1)了,redis确实记录了这个位置,但在删除时,没有用这个特性,而是直接调用了一个"通用的删除函数",即给定一个member的值,进行查找,找到位置之后再删除
BZPOPMAX命令
这里的有序集合可以视为一个"优先级队列",有时,可以带有"阻塞功能",类似于list列表中的blpop和brpop命令,这里的timeout可以设置为浮点数,单位是秒,同时,也可以等待多个有序集合,很多的作用都和blpop类似
时间复杂度为O(logN),因为无论等待了多少个key,都只会返回一个元素,返回值:先返回是哪个key,然后是这个key的元素member,再就是分数
ZPOPMIN命令
删除有序集合中count个最小的元素,时间复杂度同zpopmax
BZPOPMIN命令
用法和bzpopmax命令一样
ZRANK命令
从左往右,获取元素的下标,0为最小下标,时间复杂度O(logN)
ZREVRANK命令
从右往左,获取元素的下标,时间复杂度同上
ZSCORE命令
获取指定元素的分数,时间复杂度O(1),此处redis对于这样的查询操作做了特殊的优化,付出了额外的空间代价
ZREM命令
删除指定的一个或多个元素,时间复杂度O(M*logN),M是要删除的元素个数,N是有序集合中元素的个数,返回值为删除成功的元素个数
ZREMRANGEBYRANK
删除以下标组成的区间内的元素,都是闭区间,时间复杂度O(logN + M),N是有序集合中元素的个数,M是区间内元素的个数,返回值为删除的元素个数
ZREMRANGEBYSCORE命令
删除分数区间内的元素,都是闭区间,时间复杂度为O(logN + M),N是有序集合中的元素个数,M是要删除的元素个数,返回值为被删除成功的元素个数,"("排除边界
ZINCRBY命令
给指定的元素分数增加一个数,支持加一个负数,时间复杂度(logN),N是元素的个数,同时元素的位置可能会发生变化,保持集合有序
ZINTERSTRORE命令
求两个或多个有序集合的交集,而这里会有numkeys,是因为后面还有选项,如果没有这个numkeys,就无法确定是后面的选项的内容,还是key,就如同HTTP协议中,有一个字段content-length,描述正文的长度,从而不会造成粘包问题
注意:有序集合的主体是member,不是score,所以求交集和并集,也只看member!!!
如下图,不带选项时,交集中的元素,分数为原来集合元素分数之和
带权重weight,是给每个集合中的元素分数乘以一个系数,如下图,给第一个集合权重0.8,第二个集合权重0.5,比如张三,150.8+270.5=25.5
带有AGGREGATE选项,指明交集元素的分数是求和,还是最小的,或是最大的,在没有这个选项时,默认是求和
时间复杂度为O(NK) + O(MlogM),其中,N是集合中元素最少的集合的元素个数,K是集合的个数,M是返回的最终结果的集合的元素个数
ZUNIONSTORE命令
获取两个或多个集合的并集,时间复杂度O(N) + O(M*logM),N是所有集合的元素的个数,M是最终结果的集合的元素个数
内部编码
如果有序集合中的元素个数较少,或者单个元素体积较小,就使用ziplist来存储
如果当前元素个数比较多,或者单个元素体积非常大,就使用skiplist来存储
ZSET的应用场景
排行榜系统
zset在微博热搜、游戏天梯排行、成绩排行等等中,都是非常适用的!
比如游戏天梯排行,只需要把玩家信息和对应的分数给放到有序集合中即可,自动就形成了一个排行榜,可以按照名次(下标)、分数,进行范围查询,当分数发生变化,排行顺序也会自动调整
比如微博热搜,但比游戏排行要复杂一下,因为有很多因素,比如访问量、点赞量、转发量、评论量等等,需要综合上述多种因素来获取一个较为合理的分数,也就会涉及权重,member则为微博的id,score就算各自维度的数值,通过zinterstore或zunionstore命令,把上述有序集合按照约定好的权重,进行集合间运算,得到的结果集合的分数就是热度