引入:Redis中的核心的两个命令:
(1)set 把key和value存储进去
(2)get根据key来取value值(get命令直接输入key就可以得到value值,如果key不存在就会返回nil(和null是一个意思 为空))
注意:对于这里的key和value不需要加上引号,就是表示字符串的类型
如果要是给key和value加上引号,也是可以的(单引号或者双引号都行)
一.五个Redis全局命令
1.keys:用来查询当前服务器匹配的key,通过一些特殊符号来描述key的摸样,匹配key的摸样就能被查询出来
①?:匹配任意一个字符
②*:匹配0个或者多个任意字符
③[abcde]只能匹配到abcde 别的不行(相当于固定好了选项)
④[^e]排除e,只有e匹配不了,其他的都能匹配
⑤[a-b]匹配a-b这个范围内的字符,包含两侧边界
注意:keys命令的时间复杂度是O(N),所以一般在生产环境上都会用keys命令,尤其是大杀器keys* (查询Redis中所有的key)
生产环境上的key非常多,二Redis只是一个单现成的服务器,执行key*的时间非常长,就是Redis服务器被阻塞了,无法给其他服务器提供服务
Redis经常会做缓存,挡在MySQL的前面,替MySQL负重前行的人,万一Redis被一个keys*挡住了,此时其他的查询Redis操作超时了,此时这些请求就会直接查询MySQL,一大堆的数据突然来了,就会导致数据库挂
工作中的几个环境:
①办公环境:(入职之后公司发的电脑)
②开发环境:有时候办公环境和开发环境是一个,有时候开发环境是一个单独的服务器
(做前端/做客户端,一般来说开发环境就是办公环境,后端来说,很可能是单独的服务器
有的后端程序比较复杂:1.编译一次时间比较久 (要使用性能高的服务器)2.有的程序一次要消耗很多的CPU和内存资源 3.有的程序比较依赖Linux,在Windows环境搭建不起来)
③测试环境:测试工程师使用的
④线上环境/生产环境:
(办公环境,开发环境,测试环境,也统称为线下环境,外界用户无法访问到)
线上环境则是外界用户可以访问到的,一旦线上环境出现了问题,一定会对用户的使用产生影响)
2.exists:判断key是否存在 返回值:返回key值存在的个数 针对多个key来说是非常有用的
注意:这里的Redis有很多的数据结构,所说的是value有很多的数据结构,而key就只是String类型
(Redis自身的这些键值对是通过hash表的方式来进行组织的,Redis具体的某一个值,又是一个数据结构)
这里是单独的exists hello
这里是exists hello key1
分开写的话会产生更多轮次的网络通信(这里效率比较低,成本比较高 和直接操作的内存相比)
更何况还不一定是一个主机,可能是多个主机
封装和分用:
进行网络通信的时候,发送方发送一个数据,这个数据要从应用层,到物理层 层层封装(每一层协议都要加上报头和报尾)=》发一个快递哟啊包装一下,要包装好几层
接收方接收消息的时候,要从物理层到应用层,层层分用(每一层协议中的报头或尾给拆掉)=》收到一个快递,要拆快递,要拆很多层
(3)del(delete)删除指定的key(可以一次删除一个或者多个) 时间复杂度:O(1)
返回删除key的个数
(4)expire:给指定的key设置过期时间(key的存活时间超过这个指定的值,就会自动被删除) 设置时间的单位是秒(s) pexpire key 毫秒
(5)ttl:查看当前key的过期时间还剩多少
redis的过期策略是什么?(金典面试题)
1.定期删除:①每次抽取一部分,进行定期验证过期时间 ②保证这个抽取检查的过程足够快(因为redis是单线程的程序,主要任务是处理每个命令的任务,如果扫描key的时间足够长的话,就可能导致正常处理请求的命令被阻塞了)
2.惰性删除:假设这个key已经到了过期时间,但是还没有删除,key还没有删除,紧接着后面有一次访问到key,于是就是让redis触发删除key的操作,同时返回一个nil
redis为了对上述进行补充,还提供了一系列的内存淘汰策略
注意:1.redis没有采用定时器的方式来定时删除key
2.如果有多个key也可以通过定时器来高效节省CPU的情况下来删除多个key
为什么redis没有采用定时器的方法?
答:redis的初心是用单线程的方式,但是采用多线程务必需要多线程,就违背了Redis的初心
实现定时器(在某个时间到达之后,执行指定任务):
1.基于优先级队列/堆来实现定时器:
正常的队列是先进先出
优先级队列是优先级高的先出
比如:在Redis设置过期时间就是“过期时间越早,优先级越高”
假定有很多的key设置了过期时间,把这些key都放在优先级队列中,指定优先级队列则是:过期时间越早,就先出
队首元素就是要先过期的key
①此时定时器只需要分配一个扫描线程,让扫描线程检查这个队首元素即可,如果队首元素还没过期,则其他元素一定还没有过期
②此时扫描线程对于队首元素的扫描也不能太频繁,对于这个可以根据当前时刻和队首元素的过期时间设置一个等待,当时间差不多了,系统再唤起这个线程,此时扫描线程不需要高频的扫描队首元素,把CPU的开销也节省下来了
③万一线程休眠,来了一个新任务,是11:30要执行:可以再任务添加的时候唤醒一下线程,重新检查一下队首元素,根据当前时刻和队首元素的时间等待时间
2.基于实践轮实现的计时器
把时间划分成很多小段(划分的粒度,看实际的需求)
每一段上都挂着一个链表,每个链表代表要执行的任务(想当于一个函数指针,以及对应的参数啥的,对于java来说相当于一个对象)
假设需要添加一个key,这个key在300ms之后过期
此时这个指针,就会每隔固定时间(此处约定的是300ms,每次走到一个格子,就会把这个格子上链表的任务尝试执行一下)
在Redis源码中,有一个比较核心的机制是事件循环
(6)type:返回key对应的数据类型(此处Redis所有的key都是String类型,key的value可能存在多种数据类型(none,string,list,set,zset,hash and stream(redis作为消息队列的时候,使用这个类型的value))
上述类型操作的方式差别比较大,使用的命令,都是完全不同的
二.数据结构及编码方式
有序集合:想当于是存储了member之外还需要存储一个score(权重,分数)
redis底层实现上述数据结构的时候,会在源码层面,针对上述实现进行特定的优化,来达到节省时间/节省空间的的效果(内部具体的实现的数据结构(编码方式),还会有变数)
redis承诺,现在我有这个hash表,你进行查询的,插入,删除操作,都保证O(1) 但是这个背后不一定就是一个标准的hash表,可能在特定的场景下,使用别的数据结构实现,但是仍然保证时间复杂度符合承诺
数据结构:redis承诺给你的,也可以理解成数据类型
编码方式:redis内部底层的实现
raw:最基本的字符串(底层持有一个char数组(C++里的char是1个字节,等价于Java中的byte),或者byte数组(java中的char是两个字节))
int:redis通常也可以同来实现一些“计数”这样的功能,当value是整数的时候,此时可能redis会直接使用int来保存
embstr:针对短字符进行特殊优化
hashtable:最基本的hash表
ziplist:在hash表中元素比较少的时候可能优化成ziplist(压缩列表,能够节省空间(为什么要压缩列表?redis上有很多很多key,可能是某些key的value是hash,此时如果key特别多,对应的hash也特别多,但是每个hash又不大的情况下,就尽量去压缩,压缩之后就可以让整体占用内存更小了))
linkedlist:链表
ziplist:压缩列表
从redis3.2开始,引入了新的实现方式quicklist,同时兼顾了linkedlist和ziplist的优点,quicklist就是一个链表,每个元素又是一个ziplist 把空间和效率都兼顾到了
inset:集合中存的是整数
skiplist:跳表
redis会更具当前的实际情况选择内部的编码方式
object encoding key 查看对应的value的实际编码方式
redis单线程模型:redis值使用一个线程,处理所有的命令请求,不是说一个服务器内部只有一个线程,其实也有很多线程,多个线程是在处理网络IO
当这两个客户端,也相当于“并发”发起了上述的请求,此时服务器这边不会存在类似的线程安全问题。
redis服务器实际上是单线程模型,报成了当前收到的多个请求是串行执行的
当多个请求同时到达redis服务器,也是要先在队列中排队,在等待redis服务器一个一个的取出里面的命令再执行,微观上将redis服务器是创兴/顺序执行这多个命令的
redis能用单线程模型很好的工作 原因主要在于redis的核心业务逻辑,都是短平快的,不用消耗太多的CPU资源
redis是单线程模型为什么效率这么高,速度这么快?(参照物事数据库(MySQL,oracle,sql server))
1.redis访问内存,数据库则是访问硬盘
2.redis的核心功能,比数据库的核心功能更简单(数据库对于数据的插入,删除,查询都有更复杂 的功能支持,这样的功能势必要花费更多的开销 比如:针对插入,删除,数据库中各种约束,都会使数据库做额外的工作)
3.单线程模型,避免了一些不必要的线程竞争开销(redis每个基本操作,都是短平快的,就是简单操作一下内存数据,不是什么特别消耗cpu的操作,就算搞多个线程,也提升不大)
4.处理网络IO的时候,使用了epoll这个样的IO多路复用机制(一个线程,就可以管理多个Socket,针对TCP来说,服务器这边每次要服务一个客户端,都要给客户端安排一个Socket 一个服务器多个客户端,同时有多个Socket,很多情况下,每个客户端和服务器之间的也没那么频繁,此时这么多Socket大部分时间都是静默的上面没有要数据是需要传输的。所以这里用到的IO多路复用,一个线程处理多个Socket(操作系统给程序员提供了API,内部系统都是由操作系统内核实现的 Linux上提供的IO多路复用,主要是三套API(select,poll,epoll)))
详细的数据结构解释:
1.string:redis中的字符串,直接按照二进制数据的方式进行存储的(不会做任何的编码转换,存的是啥,取的就是啥(不仅可以存储文本数据(MySQL的miring字符集是拉丁文,插入中文就会失败),整数,普通的字符串,JSON,xml,二进制数据(图片,视频,音频)))
(1)设置string类型的key:set key value [expiration EX seconds | PX milliseconds(这里是设置过期时间)] [NX(如果key不存在才设置,如果key存在就返回nil)|XX(如果key存在就设置(相当于更新value的值,原来的key的ttl也会失效),不存在就返回nil)]
有SETNX,SETEX,SETPX
注意:redis文档给出的语法格式说明:[ ]相当于一个独立单元,表示可选项(可以可无)
其中[ 表示或者的意思,多个只能出现一个
[ ]和[ ]之间是可以同时存在的
(2)get key :对于get来说只支持字符串类型的value,如果value是其他类型,使用get获取就会出错
(3)mset和mget:一次操作多组键值对
时间复杂度是O(N),这里可以说是O(1)
此处的N不是整个redis服务器的key的数量,而是给出的key的个数
(4)①incr 针对value+1(①此时key对应的value必须是整数 ②此操作返回值,就是+1之后的值 ③incr的key如果不存在,就会把key的value值当做0来使用)
②incrby 针对value+n
③decr 针对value-1(①key对应的value必须是整数,在64为范围内 ②如果这个key对应的value不存在,则当作0来处理 ③decr的运算结果也是计算之后的结果)
④decrby 针对value-n
⑤incrbyfloat 针对value+/-小数(对浮点数进行操作)
注意:多个客户端针对同一个key操作,不会引起线程安全问题
(5)字符串支持一些常用的操作(拼接,获取,修改字符串部分内容,获取字符串的长度)
拼接字符串是append
append返回值,长度单位是字节 redis的字符串不会对字符编码做任何处理(redis不认识字符,只认识编码)(在启动redis客户端的时候,加上一个--raw这样的选项,就可以使redis客户端能够自动的把二进制数据尝试翻译)
注意:ctrl+s在xshell中的作用是“冻结当前画面”
ctrl+q解除冻结
(6)getrange:获取范围内的字符串(有start和end确定(左闭右闭))(正常下标都是从0开始的,redis的下标是可以支持负数的 比如:-1 倒数第一个元素,下标为len-1的元素)
注意:如果是切中文,则有可能是不完整的(因为这里是以字节为单位)
(7)setrange:setrange key offset value (offset是偏移量(从第几个字节开始))
如果key的值不存在,也是可以操作的,会把key之前的值看做0x00
(8)strlen:获取到字符串的长度(单位是字节)strlen key
(9)string类型有三种编码方式:int(64位/8字节的整数),embstr(压缩字符串,适用于比较短的字符串),raw(普通字符串,适用于表示更长的字符串,只是单纯的特有字节数组)
查看编码的方式object encoding key
redis存储小数本质上还是当字符串来存储(意味着每次进行运算就要把字符串当成小数来进行运算,再把小数化成字符串来存储)
注意:每个业务场景都是有很多的key,假设每个key的value值都是string类型,且长度是100左右,就可以把key当做embstr
这样效果的实现方式:①先看redis是否提供了对应的配置项,可以修改最小字节的这个数字
②如果没有提供配置型,就要对redsi源码进行魔改
(10)string类型的应用场景:
应用场景一:
整体的思路:应用服务器访问数据的时候,先查询redis。
如果redis上数据存在了,就直接从redis中取数据交给应用服务器,不继续访问数据库
如果redis上数据不存在,再读取MySQL,把读到的结果返回给应用服务器,同时把数据也写到redis中
redis这样的数据经常用来存储“热点”(高频被使用(暗含一层假设:某个数据一旦被用到,那么很有可能在最近这段时间反复用到))数据
上述策略,存在一个明显的问题:随着时间的推移,肯定会有越来越多的key在redis上访问不到,从而从MySQL读取并且写入的数据越来越多,此时redis的数据会越来越多
1) 在把数据写给redis的同时给这个key设置一个过期时间
2)redis也在内存不足的时候,提供了淘汰策略
应用场景二:
企业统计收集用户的数据=>进一步明确用户的需求=>根据需求改进和迭代产品
redis并不擅长数据统计
这里统计数据可能是MySQL ,也可能是hdfs
这个统计数据的步骤是异步的,不是说,来一个请求,这里就必须立即马上写一个数据
应用场景三:
session对话:Cookie(浏览器存储数据的机制)Session(服务器这边存储数据的机制)这是个键值对
分散存储:
如果每个应用服务器维护自己的会话数据,此时彼此之间不共享,用户请求访问不到服务器上,就可能会出现一些不正确处理的情况了
所有的会话数据都被各个服务器共享了
手机验证码:
1.生成验证码:用户输入一下手机号,点击获取验证码(限制在一分钟之内,最多获取5次验证码或者每次获取验证码必须间隔30s(主要是怕用户频繁获取验证码,对我们的服务器压力过大))
2.那短信收到的验证码的一串数据,提交到系统中,系统进行验证验证码是否正确
业务:业务就是一个公司/一个产品,是如何解决一个/一系列问题的,解决问题的过程,就可以称为业务
(2)hash表:
redis自身已经是键值对结构了,自身就是通过hash的方式来组织的
把key这一层组织完成之后,到value这一层,value的其中一种类型还可以再是hash
(1)hset:hset key field value[field value...] 这里的value是字符串(这里可以设置多个field的值)
返回值是设置成功的键值对(field-value)的个数
(2)hget:hget key field(这里只能获取一个field的值)
若获取的field在key中没有就返回空
(3)hexists key f1 若存在就返回1,不存在就返回0
(4)hdel:删除hash中指定的字段(del删除的key,hdel删除的field)
返回值:本次操作删除的字段个数 hdel key field[field...]
(5)hkeys key:查找key中所有的field(这个操作先根据key找到对应的hash O(1),然后在遍历hash O(N))
(6)hvals key:获取hash中所有的value
(7)hgetall key:
(8)hmget key:可以获取多个field对应的value值
注意:①这里也提供了hmset,但是一般不需要,因为hset就可以设置多个,所以一般不用
②上述hkeys,hvals,hgetall都是存在一定风险的,hash的元素个数太多,执行的耗时就比较长,从而阻塞redis
③hscan遍历redis的hash,它属于渐进式遍历(敲一次命令,遍历一小部分,再敲一次,再遍历一小部分....连续执行多次就可以完成整个的遍历过程了)
④再java中ConcurrentHashMap就是一个线程安全的hash表,这个hash表在扩容的时候也是按照化整为0的方式进行的
(9)hlen key:获取hash中元素的个数,不需要遍历
(10)hsetnx key field value:类似于setnx,不存在的时候,才能设置成功,如果存在,设置失败
(11)hash这里的value,也可以当做数字来处理
hincrby就可以加减整数,hincrbyfloat 就可以加减小数,redis没有提供类似的incr和decr
(12)压缩:raw,zip,gzip,7z...一些具体的压缩算法(压缩的本质是对数据进行重新编码,进行精妙的设计,重新编码后,就能够缩小体积(比如:abbcccdddd 重新编码后表示:1a2b3c4d,有一个文件内容abcd0000000000efg 重新编码后:abcd0[10]efg))
ziplist也是同理的内部的数据结构也是精心设计的(目的是节省内存空间)
表示一个普通的hash表,可能会浪费一定的空间(hash首先是一个数组,数组上有些位置有元素,有些位置没有元素)
ziplist付出的代价,进行读写元素,速度比较慢的(如果元素个数少,慢的不明显,如果元素个数比较多,慢就会雪上加霜)
如果:
1、哈希中元素个数比较少,使用ziplist表示,元素个数比较多,使用hashtable表示
2.每个value的值长度都比较短,使用ziplist表示,如果每个value的长度太长了,也会转换成hashtable
hash-max-ziplist-entries配置(默认512个)
hash-max-ziplist-value配置(默认64字节)
这个配置是可以写到redis-conf中的
(13)作为缓存:
string也是可以作为缓存使用的,存储结构化的数据(类似于数据库 表这样的结构)使用hash类型更合适一些
如果使用string(JSON)的格式来表示UserInfo,万一只获取其中的某个field,或者修改field,就需要把整个JSON都读出来,解析成对象,操作field,再重新转化成JSON字符串,再写回去
如果使用hash的方式,确实读写field更直观高效,但是付出的是空间的代价。需要控制hash在ziplist和hashtable两种内部编码的转换,可能会造成内存的较大消耗
原生字符串类型的 缓存
这里会造成低内聚,而且键过于多,会造成内存消耗过大
注意:耦合:两个模块/代码之间的关联关系,关联关系越大,越容易相互影响
内聚:一个模块内部的东西要紧密相连
3.列表(List):想当于数组或顺序表
约定最左端的下标是0,redis的下标支持负数
注意:List内部的结果(编码方式)并非是一个简单的数组,而是更接近“双端队列”
列表中的元素是有序的(有序的含义,要根据上下文区分(有的时候谈到有序,指的是“升序”,降序。有的时候谈到有序,指的是顺序)此时把元素位置颠倒,顺序调换,此时得到的新的list和之前的list不一样)
列表中的元素是可以重复的 hash这样的类型,field是不可以重复的
(1)lindex:获取到元素的值
(2)lrem:返回被删除元素的值
(3)lpush:lpush key element[elelment...](一次可以插入一个元素,也可以插入多个元素)
这里是头插(如果key已存在,并且key对应的value类型,不是list,此时就要报错,redis所有数据类型的操作,都类似这个效果)
(4)lrange:lrange key start stop(此处的区间是闭区间,下标支持负数)(lpush=>left push)
(5)rpush:rpush key element[element...]尾插(rpush=>right push)
(6)lpop key 和rpop key(在当前的redis 5版本中,都是没有count参数,从6.2版本,新增了一个count参数,count描述了这次要删几个元素)
redis中的list是一个双端队列,从两头插入/删除元素都是非常高效的O(1)
搭配使用rpush和lpop,就相当于队列了
搭配使用rpush和rpop,就相当于栈了
(7)lindex key index:给定下标获取到对应的元素O(N)此处的N是指元素个数,如果下标没发返回nil
(8)linsert key <before|after> pivot element(返回值插入后,得到新的list长度)
万一插入列表中,基准值,存在多个,linsert进行插入的时候,要根据基准值,找到对应的位置,从左往右找,找到第一个符合基准值的位置即可
O(N)N表示队列的长度
(9)llen key:列表的长度
(10)lrem(rem=>remove):
lrem key count(要删除的个数) element(要删除的值)
count>0:删除从左到右count个element
count=0:全部删除
count<0:删除从右到左count个element
(11)ltrim key start stop:保留start和stop之间区间内的元素(区间外面的元素就值姐被删除了)
(12)lset:根据下标,修改下标元素
注意:lindex可以很好的处理下标越界的情况,直接返回nil
Iset来说则会报错,不会想JS那样,直接在10这个下标里搞出个元素来
(13)blpop和brpop:
如果list中存在元素,那么blpop和brpop 于lpop和rpop的作用就是一样的
如果list中不存在元素,那么blpop和brpop就会阻塞到队列中不为空为止
b=>block(阻塞)
阻塞队列(blockingQueue):
使用阻塞队列来作为中间“交易场所”(Broker)
期望这个队列有两个特性:
1、线程安全
2、阻塞:1)如果队列为空,尝试出队列,会产生阻塞,知道队列不为空,阻塞解除
2)如果队列为满,尝试入队列,也会产生阻塞,直到队列不满,阻塞解除
注意:
①redis中的list也相当于阻塞队列一样,线程安全是通过单线程模型支持的,阻塞只支持“队列为空”的情况,不考虑队列满
②但是阻塞版本会考虑timeout,阻塞一段时间,期间redis可以执行其他命令(此处的blpop和brpop看起来耗时很久,但实际上并不会对redis服务器产生负面影响),使用brpop和blpop的时候,这里是可以显示阻塞时间的(不一定是无休止的时间)
③命令中如果设置了多个键,那么会从左想右进行遍历,一旦有一个键对应的表可以弹出元素,命令立即返回(blpop和brpop都是可以同时去尝试获取多个key的列表元素的(多个key对应多个list,这多个list那个有元素了,就会返回那个元素))
④如果多个客户端同时对一个键执行pop,则最先执行命令的客户端会得到弹出的元素
1)针对一个非空的列表进行操作:
返回结果相当于一个pair(二元组)
一方面告诉我们当前的数据来自于那个key 一方面告诉我们取到的数据是什么
2)针对一个空列表进行操作
3)针对多个可以进行操作
(14)编码类型:
ziplist(压缩列表):把数据按照更紧凑的压缩形式表示的(节省空间,当元素多了,操作起来效率会降低)
linkedlist:链表
quicklist:quicklist相当于是链表和压缩列表的结合,整体还是一个链表,链表的每一个节点,是一个压缩列表(每个压缩列表,都不让他太大,同时再把多个压缩列表通过链式结构连起来)
(15)list类型的应用场景:用list作为“数组”这样的结构,来存储多个元素
MySQL表示学生和班级的信息:
redis
这里redis中的班级和学生的对应用list来表示
使用redis作为消息队列(生产者消费者模型):
brpop这个操作是阻塞操作,当列表为空的时候,brpop就会阻塞等待,一直等到其他客户端push了 元素
谁先执行这个brpop命令,谁就能拿到这个新来的元素,像这样的设定,就能构成一个“轮询”式的效果
(假设消费者执行的顺序是 1 2 3,当新的元素到达之后,首先是消费者1拿到数据(按照执行brpop命令的先后顺序来决定谁先获取到的消费者1拿到元素之后,也就从brpop中返回了(相当于这个命令就执行完了)如果消费者1还想继续消费,就需要重新执行brpop。此时再来一个新的元素过啦里,就是消费者2拿到该元素,也从brpop中返回,如果消费者2还想继续消费,也需要重新执行brpop)
多列表/频道场景:
日常使用的抖音:有一个通道,来传输短视频数据;还有一个通道,来传输弹幕;还可以有频道,来传输点赞,转发,收藏数据;还可以有频道,来传输,评论数据(搞成多个频道,就可以在某种数据发生问题的时候不会对其他数据造成影响(解耦合))
pipeline(流水线):虽然有多个redis命令,但是把这些命令合并成一个网络请求,进行通信,就大大降低了客户端和服务器的交互次数了(假设某个用户发了1w个微博,list的长度就是1w,就可以把这1w个微博拆成10份,每份就是1k)
4.Set:(1)集合:①集合中的元素是无序的 ②集合中的元素是不能重复的(唯一的)
(2)设置(和get相对应)
这里Set和list类似,集合中的每个元素也都是string类型(可以使用JSON这样的格式让string也能存储结构化数据)
有序:顺序很重要,变换一下顺序,就是不同的list了 list[1,2,3] 和[2,1,3]两个不同的list
无序:顺序不重要,变换一下顺序,集合还是那个集合 set:[1,2,3]和[2,1,3]是同一个集合
(1)sadd :sadd key member[member...](把集合中的元素叫member)返回值表示:本次操作,添加了几个元素
(2)smembers:smembers key查看key中所有的member
(3)sismember:sismember key member
(4)scard:scard key(即set中元素的个数)
(5)spop:spop key 随机删除一个元素(从set中删除并返回一个或者多个元素。由于set内的元素是无序的,所以取出那个元素是随机的)
(6)smove source destination member
(7)srem key member[member...](一次可以删除一个或者多个member,返回值:删除元素的个数)
(8)交集(sinter):此处每个key对应一个集合,返回值就是最终交集的数据O(N*M)
(9)并集(sunion):返回的是并集的数据结果O(N)
(10)差集(sdiff):
(11)编码方式:intset(为了节省空间做出的特定优化,当元素均为整数,并且元素个数不是很多时)和hashtable
(12)应用场景:
1)用户画像:分析出一个人的一些特征,分析特征之后,在投其所好
收集到用户的特征,就会转换成“标签”简短的字符串,此时就可以保存到redis中的set中了
(2)使用set来计算用户之间的共同好友(基于“集合求交集”)
比如:QQ,我这边加了很多好友,你这边也加了很多好友,基于上述可以做一些好友推荐
3.使用set统计UV:
一个互联网,如何衡量用户量:
主要是两个指标,是两方面:
1.PV page view 用户每次访问服务器会产生一个pv
2.UV user view 每个用户,访问服务器,都会产生一个uv,但是同一个用户多次访问
uv需要按照用户进行去重
上述去重过程,就可以使用set实现
5.Zset有序集合:给zset中的member同时引入一个属性分数score,浮点类型,每个member都会安排一个分数(进行排序的时候,就按此处的分数大小进行升序/降序)
分数相同,再按照字符串的字典序来排列,分数不同任然按照分数来排
zset中的member仍然要求是唯一的(score则可以重复)zset主要是用来存member的,score是次要的
member和score称为一个pair,不是把member和score理解成“键值对”(key-value pair)(简直对中,是有明确的“角色区分”,谁是键,谁是值,是明确的,一定是键->值)
对于有序集合来说,是可以通过member找到对应的score,有可以通过score找到匹配的member
(1)zadd:往有序集合中,添加元素和分数
zadd key [NX | XX] [GT | LT] [CH] [INCR] score member[score member...]
不添加NX|XX选项的时候:如果当前member不存在,此时就会达到“添加新member”的效果
如果当前member已经存在,此时就会跟新分数
NX:如果不存在就添加,存在就返回空
XX:如果不存在就返回,存在就更新分数(如果修改的分数影响到了之前的顺序,就会自动的移动元素位置,保持原有的升序顺序不变)
LT:less than 如果当前的member比之前的小,此时就更新成功,否则就不更新
GT:greater than 如果当前member比之前打,此时就更新成功,否则就不成功
INCR:此时命令类似ZINCRBY的效果,将元素的分数加上指定的分数。此时只能指定⼀个元素和分数。
CH:默认情况下,ZADD?返回的是本次添加的元素个数,但指定这个选项之后,就会还包含本次更新的元素的个数。
没有加CH之前
加了CH之后
之前hash,Set,List很多时候,添加一个元素,都是O(1),此时,
Zset则是logN(由于Zset是有序结构,要求新增元素,要放到合适的位置(找位置)之所以logN不是N,也充分的李永乐有序这样的特点(当然,Zset内部的数据结构,主要是跳表))
(2)zrange:查看有序集合中的元素详情(类似于lrange,可以指定一对下标构成的区间)
(3)zcard:zcard key 查询key中元素的个数
(4)zcount:zcount默认是一个闭区间 zcount key min max 时间复杂度是O(logN)
这里用(表示开区间 用[ 表示闭区间
注意:①这里zset的内部,会记录每个元素当前的“排行”/“次序”,查询元素就可以直接把max对应的元素次序和min对应的元素次序,减法即可
②max和min是可以写成浮点数(zset本身就是浮点数)在浮点数中存在两个特殊的数值:
inf:无穷大 -inf:负无穷大,分数也支持用inf 和-inf作为max和min
(5)zrangebyscore:按照分数来找元素,相当于刚才的count类似
zrangebyscore key min max [withscores]
(6)zpopmax:删除并返回分数最高的count个元素(返回值就是被删除的元素(包括member和score))时间复杂度是O(logN)使用的是通用的查找方式,,给定一个member值,查找元素位置,再删除
如果存在多个元素,分数相同,同时为最大值,zpopmax删的时候,仍然只删其中一个元素(分数虽然是主要因素,如果分数相同会按照么么比尔字符串的字典序决定先后)
注意:此处可以优化(标记最后一个元素进行删除),优化要用到刀刃上
(7)bzpopmax:bzpopmax key[key...] timeouot(这里的“有序集合”也可以视为是一个优先级队列,有时候也需要带有“阻塞功能”的优先级队列)
注意:①每个key都是一个有序集合
②阻塞也是在有序集合为空的时候出发阻塞,阻塞到有其他客户端插入元素,如果集合中有元素了,就直接能返回了,不会阻塞了
③时间复杂度:O(logN) 这里虽然有多个key,但是他只是从找到某个key中的一个值,所以是O(logN) 不是O(logN+M)
(8)bzpopmin:bzpopmin key[key...] timeout
(9)zrank:zrank key member(zrank得到的下标,是从前往后算的)O(logN)这里会有一个查询过程
(10)zrevrank:zrevrank key member(也是获取到memeber的下标,这里是从后往前算的)O(logN)
(11)zscore key member:根据元素找分数,这里有特殊优化,时间复杂度是O(1)
(12)zrem:zrem key member[member...]删除某个元素O(logN*M) M:参数中的member个数
N:有序集合中的元素个数
(13)zremrangebyrank:zremrangebyrank key start stop 使用这个下标描述的范围进行删除
O(logN+M) N:整个有序集合中元素的个数 M:start-stop中元素的个数
(14)zremrangebyscore:zremrangebyscore key min max(制定一个删除的区间,通过分数来描述)
(15)zincrby :zincrby key increment member(不光能修改分数,也能同时移动元素,保持整个有序集合仍然是有序的)
(16)交集:zinterstore destination numkeys key[key...][weights wight[weight...]][aggregate <sum|min|max>]
destination:把结果存储到那个key对应的zset中
numkeys:整数,描述了后续有几个key参与交集运算(防止出现粘包问题:确定发送消息的续航度等等)
weights:权重
aggregate:求分数
O(N*K)+O(M*logM):
N:输入若干个有序集合的最小元素个数
K:有几个有序集合求交集(近似于O(1))
M:最终结果的有序集合个数
(17)并集:zunionstore destination numkeys key[key...][weights wight[weight...]][aggregate <sum|min|max>] 用法与zinterstore基本一致
(18)编码方式:
ziplist:如果有序集合中袁术个数较少,或者单个体积较小,使用ziplist来存储
zkiplist:如果当前元素个数比较多,或者每个元素的单位体积非常大,就可以使用skiplist来存储了
跳表:简单来说就是一个“复杂链表”,查询元素的时间复杂度是logN ,像比于树形结构,更适合按照范围来获取元素
(19)zset应用场景:排行榜系统(1.微博热搜 2.游戏天梯排行 3.成绩排行榜。。)这里的原件要点:用来排行的分数是实时的
使用zset来完成上述操作就非常简单
比如:游戏天梯排行榜,只需要把玩家的信息和对应的分数放到有序集合即可,就自动形成了一个排行榜,随时可以按照排行榜(下标)进行范围查询,随着分数的改变,也可以比较方便的,zincrby修改分数,顺序也能自动调整logN
注意:KB相当于1k个字节 1million(百万) 相当于1MB 1billion(10亿)相当于1GB
对于复杂的排行榜,像是微博热搜:(综合数值:1.浏览量 2.点赞量 3.转发量 4.评论量。。。)
根据每个维度,计算得到的综合得分=》就是热度
此时,这里就可以借助zinterstore/zunionstore 按照加权方式处理(此时,就可以把上述每个维度的数值都放到一个集合中,member就是微博的id,score就是各自维度的数值,通过zinterstore/zunionstore把上述有序集合按照约定好的权重,进行集合间运算即可,得到的结果集合的分数就是热度(排行榜也就顺带出来了)
6.stream:就是一个队列(阻塞队列)redis作为一个消息队列的重要支撑,属于是List blpop/brpop 升级版本
事件:epoll/io多路复用(事件是干什么的?有些操作,我们也不知道他什么时候出现,只能等这个事情出现之后,再采取行动)
例如:数学家去应聘消防员(数学家擅长把一个未知的东西转化成一个已知问题)
一旦着火,立即用干粉灭火器;没着火,就一直等着
此处的“着火”就是“事件”;“使用干粉灭火器”就是“事件触发的回调函数”
7.Redid geospatial:用来存储坐标(经纬度)
存储一些点之后,就可以让用户给定一个坐标,去从刚才存储的店里进行查找(按照半径,矩形区域)
这个功能在“地图”中非常重要
8.Redis HyperLogLog:应用场景:估算集合中元素的个数
Set中有一个应用场景,统计服务器的UV(用户访问次数),如果使用Set统计最大的问题在于,如果UV的数据量非常大,Set就会消耗很多内存空间(假设Set存储userId,每个userId按照8个字节来算 1亿UV=》8亿字节 =》0.8G)
用HyperLogLog可以最多使用12KB空间,来实现上述效果
因为:HyperLogLog不存储元素的内容,但是能够记录“元素特征”,从而在新增元素的时候,能够知道当前新增的元素,是一个已经存在的元素,还是一个崭新的第一次出现的元素(这个东西具体还是得分析源码=》核心操作“位操作”=》精确性0.81%)
用来计数(记录当前集合中有多少个不同的元素,但是不能告述这些元素是什么)
hyperloglog在存储元素,提取特征的过程,是不可逆的(信息量丢失了)
9.Redis bitmaps:使用bit来表示整数(位图本质上,就还是一个集合,属于是Set类型针对整数的特化版本(节省空间))
比如:0000 0000 0000 0000 0000 0000 0000 0000(表示10的时候就从第0位开始,把第10表示为1)
10.Redis bitfields:位域(像是c语言中的位段=》也叫位域
struct{
int a:8;
int b:5;
iny c:3;
}此处的数字,就描述这个成员实际占几个bit位,位域本质上是让我们精确位操作的一种方法,上
)bitfields可以理解成一串二进制序列(字节数组),同时可以把这个字节数组中的某几个位,赋予特定的含义,并且可以进行 读取/修改/算术运算 相关操作
位域这个东西用来节省空间
10.scan(渐进式遍历):这样可以获取到所有的key,同时又不会卡死服务器(每执行一次命令,只获取到其中的一小部分,这样的话就能保证当前这次操作不卡,想要的到更多的可以,多遍历几次就可以了)
scan cursor [MATCH pattern] [Count count] [TYPE type]:
coursor:光标,就是指向当前的位置(不能理解成下标,不是一个连续递增的整数)
count:获取到几个元素(真正获取到的元素个数可能会个count的值不一样)默认是10
type:key的类型都是String value的类型就是上述的一些数据结构
优点:这里的渐进式遍历,在遍历过程中,不会在服务器这边存储任何状态信息,此处的遍历是可以终止的,不会对服务器产生任何的副作用
缺点:渐进式遍历scan虽然解决了阻塞的问题,但是如果在遍历期间有所变化(增加,阻塞,删除),可能导致遍历是键的重复遍历或者遗漏
11.Redis给我们提供了16个数据库,0-15,这16个数据库中的数据是隔离的(相互之间不会有影响)默认情况下就是0号
select dbindex切换数据库