Redis的相关命令

引入: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切换数据库

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值