目录
[注意:Redis 整体存储:基于 "全局哈希表" 管理所有键值对](#注意:Redis 整体存储:基于 “全局哈希表” 管理所有键值对)
[ttl(time to live)命令](#ttl(time to live)命令)
[setnx setex psetex 命令](#setnx setex psetex 命令)
[blpop brpop命令](#blpop brpop命令)
sinter(交集)、sunion(并集)、sdiff(差集)
安装Redis5

对于centos7版本,需要更换软件源scl源

创建符号连接==快捷方式


这样你直接执行的时候就不需要输入一长串的路径了
修改配置文件
打开.conf结尾的配置文件(redis.conf)(配置文件就是你可以根据一些选项来配置你的软件的一些功能,注意一般配置文件你需要重启服务)

绑定的是127.0.0.1ip地址也就是本地环回,跨主机访问不了,所以改成0.0.0.0
保护模式设为no,免密码(否则外部访问需要密码和本地换回才能访问)(前期学习先设免密码,数据也不重要)

设置守护进程,这样才能以后台运行

设置工作目录,持久化的数据都放在这里

日志目录

启动redis服务:redis-server /etc/redis/redis.conf
查看守护进程:netstat -anp | grep redis
停掉守护进程:kill pid即可
redis客户端:redis-cli
退出客户端:ctrl+d
在进程中ping一下发现回pong,说明本地连接服务器是可以的
redis客户端
redis是一个客户端-服务器结构的程序(Mysql也是),便于解耦,支持多客户端,远程访问

客户端形态:
1.自带了命令行客户端:redis-cli
2.图形化界面的客户端:桌面程序,web程序

3.基于redis的api自行开发客户端(工作中最主要的形态)
非常类似于mysql的c语言的api和jdbc(也就是调用接口)
Redis命令
通过redis-cli客户端和redis服务器交互,会涉及到很多的redis命令
1.掌握常用命令(多操作多练习)
2.学会使用redis的文档
注意:Redis 整体存储:基于 "全局哈希表" 管理所有键值对
- Redis 的全局哈希表(
dict
结构)中,每个键值对的 "键(key)" 是字符串类型,而 "值(value)" 并非直接存储数据内容,而是一个指向 Redis 对象(redisObject) 的指针。
- 这个
redisObject
结构体中包含了三个核心信息:- 类型(type):标识当前对象属于哪种对外类型(如 string、hash、list 等)。
- 编码(encoding):标识该对象底层使用的具体数据结构(如 int、ziplist、skiplist 等)。
- 指针(ptr):指向实际存储数据的内存地址(例如字符串的字节数组、哈希表的节点等)。
举个简化的例子:当执行 SET num 100
时,全局哈希表中 "键" 是字符串 "num","值" 是一个 redisObject
指针,该对象的 type 是 string,encoding 是 int,ptr 直接指向整数 100 的内存地址。
所以我们接下来的讲解是讲的value中的数据类型,而不是全局的哈希表
接下来讲解的set和get命令是为了后续建立key-value,但是注意这个是string类型的
简单提前了解一些命令
set命令
作用:把key和value存储进去
用法:

注意:key和value都是字符串,key和value不需要加上引号,就是表示字符串的类型(加引号也可以)
get命令
作用:根据key来取value,如果当前key不存在则返回nil(nil和null一样都是表示无之类的意思)
用法:

注意:

注意:set和get是针对string类型的
Redis全局命令
redis支持很多种数据结构,整体上来说,Redis是键值对结构,key固定是字符串,value实际上会有很多种类型,也就是key-value是以哈希存储,value实际上是一个指针,指向实际的数据结构
不同的数据结构有不同的命令 ,但是全局命令就是任意一个数据结构都能来使用的命令
keys命令
作用:用来查询当前服务器上匹配的key,通过通配符来描述key的模样,把能匹配的key查出来
用法:
?:匹配任意一个字符
*:匹配0个或者多个任意字符
\]:只能匹配括号中的字符,别的不行,也就是给出匹配的范围,只能匹配一个 \[\^e\]:排除e,这个e不匹配,其他的都能匹配 \[a-b\]:匹配a-b这个范围内的字符,这是闭集  注意:keys命令的时间复杂度是O(N) 所以,在生产环境上,一般都会禁止使用keys命令中的key \* 如果生产环境中的可能非常多,而redis是一个单线程的服务器,执行key \*的时间非常长,就会导致redis服务器被阻塞了,无法给其他客户端提供服务,一般来说redis都是来做缓存,如果redis阻塞了,那就会去查询mysql,mysql接不住一大波请求,就会挂掉,这个系统就会瘫痪   #### exists命令 作用:判定key是否存在 用法:   可以同时测多个key,返回值为key存在的个数,针对多个key来说,是非常有用的 注意:时间复杂度为O(1),因为是哈希表来存储key-value redis是一个客户端-服务器结构的程序,他们之间是通过网络来进行通信的,一般来说查询最好减少查询次数,不要一次一次查,可以exists后面跟多个key,分开查会产生更多轮次的网络通信  #### del(delete)命令 作用:删除指定的key 用法:   支持一次删除多个key,并且返回值是返回成功删除的个数 注意:时间复杂度为O(1)  #### expire命令 作用:给指定的key设置过期时间,设置的时间单位是秒,key超过这个时间就自动被删除  用法:   设定过期时间,必须是针对已经存在的key设置,设置成功返回1,失败返回0 注意:时间复杂度O(1) pexpire key是设置毫秒 #### ttl(time to live)命令 作用:查看当前key的过期时间还剩多少 用法:  注意:pttl是对应pexpire的,ttl是对应expire的 **经典面试题:redis的key的过期策略是怎么实现的???** Redis 采用了**惰性删除** 和**定期删除**两种策略来处理过期的 key * **惰性删除** : 这是一种被动的删除策略。当客户端尝试访问一个键时,Redis 会检查该键是否过期,如果过期了,就删除该键,并返回键不存在的结果。比如客户端执行 `GET key_name` ,如果 `key_name` 已经过期,Redis 会先删除 `key_name` ,然后返回 `(nil)` ,表示键不存在。这种策略的优点是对 CPU 友好,不会主动消耗 CPU 资源去删除过期键 ;缺点是如果过期键一直没有被访问,就会一直占用内存,造成内存浪费。(简单来说就是用的时候在查看有没有过期) * **定期删除** :这是一种主动的删除策略。Redis 会周期性地随机从数据库中取出一定数量的键,检查它们是否过期,并删除其中过期的键。Redis 会通过配置文件中的 `hz` 参数(默认值是 10 ,表示每秒执行 10 次过期扫描)来控制定期删除操作的执行频率。每次扫描时,Redis 会随机选择一些数据库,然后从每个数据库中随机取出一定数量的键进行检查。这种策略能有效减少过期键对内存的占用 ,不过如果执行过于频繁,会消耗较多 CPU 资源;如果执行频率过低,又会导致过期键长时间占用内存。 此外,在 Redis 进行持久化操作(如 RDB 快照和 AOF 重写)时,也会处理过期键。在生成 RDB 文件或者进行 AOF 重写时,已经过期的键不会被写入新的 RDB 文件或者 AOF 文件中,从而保证持久化数据的一致性。  **内存淘汰策略可以自行搜索一下(比如不进行数据淘汰、随机删除任意键、随机删除过期键等)**  注意:redis并没有采用定时器哈   #### type命令 作用:返回key对应的数据类型,此处redis所有的key都是string类型,但是value对应多种 用法:  注意:时间复杂度为O(1)   ### value存储结构详解   也就是我们之前讲的通过value指向一个结构体指针,指针里面有属性描述   使用object encoding key:就可以查看当前的key采用的是哪种编码     **重要面试题:redis虽然是单线程模型,为啥效率这么高,速度这么快呢???(注意这里的快是有参照物的,相比于mysql,oracle,sql server)** Redis 的 "快" 是 **"内存存储 + 单线程无锁 + I/O 多路复用 + 精简数据结构"** 共同作用的结果 1.redis所有数据(默认)存储在内存中,读写操作无需访问磁盘。内存的访问速度(微秒级)比磁盘(毫秒级)快 **10 万倍以上**。 2.单线程模型,避免了一些不必要的线程竞争开销,每个基本操作,都是短平快的,就是简单操作一下内存的数据,不是什么特别消耗cpu的操作,就是搞多线程,也提示不大,多线程也会有一定的消耗的(上下文切换,锁等) 3.处理网络IO的时候,使用了epoll这样的IO多路复用机制(具体的epoll可以看我的Linux网络编程部分)通过一个线程同时监听多个 Socket 连接,当某个连接有数据可读 / 可写时,才触发相应的 I/O 操作。单线程可同时处理 **数万甚至数十万** 个客户端连接,且不会因等待某个连接的 I/O 而阻塞,兼顾了单线程的简洁性和高并发处理能力。 4.redis核心功能,比mysql等数据库的核心功能更简单,mysql等的数据库对于数据的插入删除查询操作,都有更复杂的功能支持,这样的功能势必要花费更多的开销,比如针对插入,数据库中的各种约束,都会使数据库做额外的工作(redis干的活少,提供的功能相比于mysql也少) **接下来围绕每种数据结构进行不同的命令讲解**  **注意:** FLUSHALL命令:删掉redis上所有的键值对,这个千万千万不敢敲,尤其是生产环境的数据库 等价于mysql中的drop database  ### string类型命令  redis的处理:比如你存"你好",只要你客户端用的是utf-8编码集,经过客户端变成二进制数据之后,经过网络传给服务端,这期间都是字节流的,服务端直接存储不做任何修改,取的时候直接从服务端取出来,然后客户端需要单独使用对应的编码集解析一下就是"你好" mysql的处理:存"你好"的时候,要受到表的字段的影响,需要定义列的字符集,明确存储时候使用的编码,你插入的时候要检查是否符合这个编码,后面存储到磁盘的时候需要将你好转换,取出来的时候按照字段定义的字符集解码返回给客户端,如果前面存的时候使用utf-8,但是此时你取的时候,服务端也会按照utf-8,如果你客户端使用gbk去读就会出现乱码 注意:redis的string类型最大存储为512M #### set命令  作用:存一个键值对,value的类型是string 用法: NX:如果key不存在,才设置;如果key存在,不设置(返回nil) XX:如果key存在,则更新;如果key不存在,则不设置(返回nil) ex:设置过期时间,set key value ex 10 == set key value + expire key10 ,秒级别 px:设置过期时间,set key value px 10 == set key value + pexpire key10 这个是毫秒级别 注意:如果对于key存在,覆盖的情况,可能会改变原来的数据类型,原来key的ttl也会失效 #### get命令 作用:获取一个字符串类型的value 用法:  注意:这个get只是支持字符串类型的value,如果是其他类型,就会报错 #### mset和mget命令 作用:一次操作多组键值对 用法:    注意:  #### setnx setex psetex 命令 这是针对一些常见的用法,进行了缩写,就是为了让操作更符合人的直觉,使用者的门槛就越低,背的东西就越少 #### 自增自减命令 incr:针对value+1 incrby:针对value+n decr:针对value-1 decrby:针对value-n incrbyfloat:针对value+/-小数 注意:这里操作的value必须是整数类型,返回值就是+1/n之后的值,前面四个必须是整数,最后一个整数或者浮点都可以,如果你执行命令的时候key不存在就会把这个key创建出来,并且value设为0,然后再执行+- 这里的整型范围是64位,如果你到了上限还执行自增那就会报错 上述的操作的时间复杂度都是O(1) 由于redis处理命令的时候,是单线程模型,多个客户端同时针对同一个key进行incr操作,不会引起线程安全问题 #### 拼接/截取/修改字符串的部分内容命令 ##### append 作用:追加字符串 用法:  注意:这个返回值,返回的是字节,也就是8字节,redis的字符串不会对字符编码做任何处理(redis不认识字符,只认识字节)(当前的xshell终端默认的字符编码是utf8,在终端输入汉字之后,也是按照utf8编码的,你好占了6个字节,10占了2个字节) 在启动redis客户端的时候,加上一个--raw这样的选项,就可以使客户端能够自动的把二进制数据尝试翻译。 ##### getrange命令 作用:截取字符串的某一部分 用法:   注意:这里的切字符串的下标是前闭后闭的,也就是包括0和3这个下标,并且支持负数-1就是倒数第一个下标 ##### setrange命令 作用:从offset位置开始覆盖成value,包括offset位置 用法:   注意:  对于不存在的,可以创建出来,但是前面会+\\x00,因为是从3位置开始,所有前面0,1,2是\\x00 ##### strlen命令 作用:获取字符串的长度,单位是字节 用法:  #### string内部的三种编码方式 1.int 64位/8字节的整数 2.embstr 压缩字符串,适用于表示比较短的字符串 3.raw 普通字符串,适用于表示更长的字符串,只是单纯的持有字节数组 可以通过object encoding key来查询某个key的内部编码方式  一般来说长度大于39这样的,就需要raw,但是这个是可以更改的 我们应该考虑具体的应用场景,而不是单单去记这个数字,并且这个数字也可以通过配置文件修改 如果某个业务场景,很多很多的key,类型都是string,每个长度基本都是100左右,考虑用embstr来存也不是不可以  #### string类型的应用场景    也就是你可以redis中使用incr等操作自增播放量,但是如果要进行排序,你可以异步到mysql等专门统计数据的仓库,使用排序一下就能查出来,因为redis没有专门的排序机制 实际中要开发一个成熟的、稳定的真实计数系统,要面临的挑战远不止如此简单:比如防作弊、按照不同维度计数、避免单点问题、数据持久化到底层数据源等。  假设一个客户端,但是服务器肯定是有很多的,这些服务器是以负载均衡的方式来提供服务的,所谓的会话,就是客户端和服务器在交互过程中产生的一些专属于该客户端的中间状态的数据,如果下一次负载均衡不是之前原来那台服务器,没有会话就会导致类似重新登录之类的,如果有会话,就能记录一个客户端的一些中间信息    用户点击前端按钮,响应给后端服务器生成随机数并且存储起来,然后通过短信通道发送短信 像发送短信这样的操作,都是有专门的SDK来实现的(第三方提供的短信平台服务) 然后用户发送验证码,服务器在验证 ### hash类型命令  注意接下来讲解的如果是key那就是外层,如果是field就是内层hash中的key #### hset命令 作用:设置hashi类型的键值对,value的类型是hash 用法:   返回值,是设置成功的键值对(field-value)的个数,这里内部的哈希是两对键值对 注意:key是外层的hash表,field-value是内层的hash #### hget命令 作用:查询hash类型的键值对 用法:  如果没有找到对应的value,就返回nil #### hexists命令 作用:判断一个field是否存在 用法:   #### hdel命令 作用:删除hash中指定的字段 用法:   返回值:本次操作删除的field的个数 注意:这里hdel删除的是内部的field,外部的键没有删除,del才是删除key #### hkeys命令 作用:根据key获取hash中所有的field 用法:   注意:注意这个操作也有一定的风险,类似之前介绍过的keys \* #### hvals命令 用法:和hkeys相对,根据key获取到hash中所有的value   注意:也是有一定的风险,因为我们不知道hash究竟有多大 #### hgetall命令 作用:根据key把哈希表,一行field一行value的列出来 用法:  注意:也是有一定的风险 上述的操作,都是比较大的,大多数情况下不需要查询所有的field,可能只需要查其中几个 #### hmget命令 作用:类似之前的mget,可以一次查询多个field 用法:   注意:这个value是根据field的顺序一一对应的 有hmset,但是hset可以设置多个field和value,所以就不用记也行 #### hscan命令  作用:渐进式的遍历hash,避免因一次性遍历大量元素而阻塞 Redis 服务 用法:   第一个参数是游标,也就是首次要传入0,直到返回的第一项也是0的时候就表示遍历完全部了 #### hlen命令 作用:获取hash中的field的元素个数,不需要遍历的 用法:  注意:时间复杂度为O(1),因为有字段专门维护这个大小 #### hsetnx命令 作用:不存在的时候,才能设置成功,存在则失败,这里类似于setnx 用法:   之前设过key1,所以失败了 #### 自增自减命令  #### hash内部的两种编码  **ziplist**  这个ziplist是以时间换取空间,也就是在增和删的时候都需要移动节点  zIbytes是表示整个长度,zItail是表示尾巴,也就是相对于zIend的偏移位置,zllen是表示节点的长度 这是一个节点的表示 由长度(是前一个结点的),编码格式(用来区分字符还是整数)、数据类型来构成   prevlen是长度可变的,是由前一个结点的长度来决定的,如果前一个长度能用一个字节表示就一个,如果不行就五个字节,并且第一个字节固定了11111110,后四字节表示前一个字节的长度 encoding:是用来区分字符串还是整数的,00,01,10都是表示字符串,11开头表示整数 字符串内部有3中编码  整数内部有6种编码   对于增加和删除都是很浪费时间的 #### 应用场景  如果是hash结构就可以这样存储    意思就是这样各个属性就不是通过一个键来约束,而是将不同的属性拆开成单独的,低内聚会造成功能不集中,维护性差,复用性低,扩展性受限等问题 我们应该做到高内聚--低耦合,两个模块/代码之间的关联关系越大,越容易受影响,这样就是耦合度高,如果是低耦合,就可以避免这一边改动影响到另一边 ### List类型命令    #### lpush命令 作用:头插一个数据,一次可以插入单个元素或者多个元素 用法:   返回值:list的长度 注意:时间复杂度O(1),如果不是list类型的话你使用这个命令lpush就会报错(redis中所有的这些各种数据类型的操作都是如此) #### lrange命令 作用:查看list中指定范围的元素 用法:   此处描述的区间也是闭区间,下标支持负数 注意:左边这里的序号是专门给结果集使用的序号,和list下标无关 鲁棒性   #### lpushx命令 作用:对已存在的key才会头插,不存在的key不做操作 用法:   #### rpush命令 作用:尾插一个value 用法:   返回值:列表的长度 #### rpushx命令 作用:对已存在的key才会尾插,不存在的key不做操作 用法:  #### lpop和rpop命令 作用:一个头删,一个尾删 用法:   注意:在当前的redis5版本中,都是没有count参数的,但是从redis6.2版本之后,新增了一个count参数,这个参数就是描述这次要删几个元素的 #### 双端队列  具体的双端队列可以看我数据结构章节 #### lindex命令 作用:给定下标,获取到对应的元素 用法:   返回值:如果下标非法,返回的是nil 注意:时间复杂度为O(N),此处的N指的是list中的元素个数 #### linsert命令 作用:在pivot元素(基准值)之前/之后插入一个value 用法:   返回值:如果key不存在,则不创建不插入; 如果key存在,pivot不存在,则报-1 如果key存在,pivot存在,则返回插入之后list的长度 注意:如果基准值存在多个,从左往右找,找到第一个符合基准值的位置即可 #### llen命令 作用:返回链表的长度 用法:  #### lrem命令 作用:lrem==list remove 也就是删除从哪一侧(count)开始的value 用法:   如果count\>0,则代表从列表的左侧开始删除count个value 如果count\<0,则代表从列表的右侧开始删除-count个value 如果count=0,则代表删除列表中所有与value相等的元素 #### ltrim命令 作用:保留范围之内的元素,去掉范围之外的元素 用法:   #### ACL标签  简单来说就是你可以给每个命令打上一定的标签,比如@write,写操作,还有一些执行慢的等等,不同的用户给一定的权限(权限配置) Redis 6+ 用 "给命令贴标签" 的方式,让管理员能更灵活地控制不同用户能使用哪些 Redis 命令。 #### lset命令 作用:根据下标,修改元素 用法:   注意:lindex可以很好处理下标越界的情况,直接返回nil lset如果越界的话就会报错 #### blpop brpop命令 阻塞:当前的线程不走了,代码不继续执行了,会在满足一定的条件之后被唤醒 b:block(阻塞) 如果list中存在元素,blpop和brpop就和lpop以及rpop作用完全相同 如果list中为空,blpop和brpop就会产生阻塞,一直阻塞到队列不空为止  生产者消费者模型可去我的Linux系统编程章节    注意:在redis6中timeout是可以设置小数的,但是redis5只能是整数,timeout=0表示一直阻塞  针对一个非空的列表操作:  之前的key1是 10 3 2 这里返回的是一个二元组pair,也就是什么key,key中的什么value 针对一个空的列表操作:  阻塞了10秒  如果在10s内有数据了就能直接返回 针对多个key进行操作:  从左往右检查,哪个先有就返回哪个,key1和key2本来就有,但是key1在左边就先返回key1  #### pipeline(流水线)(通道)  使用lrange 遍历key(user:1:mblogs)10个key(keylist),然后在使用for循环遍历每个keylist中的key(hgetall) 每次执行一次hgetall就需要发起一次网络请求,也就是执行10次hgetall,10次网络请求 如果这里的keylist非常多,那执行的hgetall就会非常多次,就会有很多次的网络请求 pipeline的作用就是把多个命令合并成一个网络请求进行通信,执行完之后再把结果返回给客户端 Pipeline 是 Redis 优化 "多命令批量执行" 的重要手段,通过减少网络往返次数,解决循环执行命令时的性能瓶颈问题。 这是在编程方面可以做的,本身是没有提供对应的命令的,但是你可以通过一开始启动选项执行 cat commands.txt \| redis-cli --pipe 这里就是把所有的命令打包在txt文件中,然后给客户端redis-cli --pipe(加上这个选项) #### list内部的编码方式 ##### zipliset(压缩列表) 把数据按照更紧凑的压缩形式进行表示的,节省空间,当元素个数多了,操作起来效率会降低 这里具体了解前面讲hash的编码的时候已经提过了 ##### linkedlist(链表) 双向带头链表 ##### quicklist(跳表) 相当于链表和压缩列表的结合,整体还是一个链表,链表的每个结点,是一个压缩列表 每个压缩列表,都不让它太大,同时把多个压缩列表通过链式结构连起来  #### list类型的应用场景      ### set类型命令  注意:list中的有序是你头插4 3 2 1,那你遍历出来的就是1 2 3 4,顺序和你插入的是有关系的 set这里的无序是指1 2 3这个集合和 3 2 1这个集合是一样的 就是我们数学当中的集合无序性和唯一性(会去重)  注意:这里的list存储的string类型就是之前所讲的redis中的string类型,是value的类型,也就是每个结点所存储的类型,是不能存hash等结构化数据的,如果对于结构化数据,你是可以使用序列化json这样的格式后再存入(set也同理) 注意:对于set我们把集合中的元素叫做member,就像hash类型中,叫做field类似 对于集合的操作开头都是带s的 #### sadd命令 作用:创建set集合 用法:   返回值:表示本次操作,成功添加了几个元素  注意:时间复杂度O(1) 因为set集合是唯一性的,所以这里添加1的时候由于原来有了,所以添加失败 #### smembers命令 作用:查看某个key中的元素 用法:   注意:这里是无序的,内部存储就是无序的,这是"表象有序" #### sismember命令 作用:判断当前元素是否在集合当中 用法:   #### scard命令 作用:判断当前集合有几个元素 用法:   #### spop命令 作用:随机删除count个元素(因为set是无序的,所以不知道删除的哪个) 用法:   注意:如果count这个不写就是随机删除一个元素 由于set内的元素是无序的,所以取出哪个元素实际是未定义行为,即可以看作随机的  #### smove命令 作用:从soure集合中move某个member元素到destination中 用法:   可以看到key1集合当中已经没有了1 注意:这个smove相当于进行删除--插入操作,如果要移动的元素在soure中不存在则返回0 #### srem命令 作用:一次删除1个或者多个member 用法:   返回值:表示成功删除的元素个数 注意:不同操作的返回值,含义差别大,不需要可以去背,而是多翻文档,经常用的操作,自然就会记住了 #### sinter(交集)、sunion(并集)、sdiff(差集) 注意:这里的集合也就类似数学中的集合,所以这里的集合关系跟数学中类似  sinter命令 用法:   注意:时间复杂度O(N\*M),N是最小的集合元素个数,M是集合的个数 sinterstore命令 作用:把直接算好的交集,放到destination这个集合当中 用法:   sunion命令 用法:  注意:时间复杂度为O(N) sunionstore命令就是把结果放到destination集合当中 sdiff命令 用法:  注意:时间复杂度为O(N) sdiffstore命令就是把结果放到destination集合当中  #### set的内部编码方式 ##### intset(整数集合) 为了节省空间,做出的特定优化,当元素均为整数时,并且元素个数不是很多的时候, 适用于存储少量整数的场景,内存紧凑、有序存储(仅因实现特性,非 Set 本身有序)。 ##### hashtable(哈希表) 适用于存储非整数元素或大量元素的场景,兼容性强、无序存储。 #### set的应用场景 ##### 使用set来保存用户的"标签"   ##### 使用set来计算用户之间的共同好友  ##### 使用set统计uv  ### zset数据类型 set集合是一个唯一的,无序的,zset是一个唯一的,有序集合(升序/降序) 排序的规则是啥:   #### zadd命令 作用:使用zadd往有序集合中,添加元素和分数 用法:  这里添加的时候既有score,又有member,所以既要添加元素又要添加分数  NX:仅当成员不存在于有序集合中时,才添加该成员。如果成员已存在,则忽略此次添加操作。 XX::仅当成员已存在于有序集合中时,才更新其分数。如果成员不存在,则忽略此次操作 CH:默认情况下,`ZADD` 的返回值是 "新添加的成员数量"。使用 `CH` 后,返回值变为 "被修改的成员数量"(包括新添加的成员和分数被更新的现有成员)。 INCR:将成员的分数视为增量,而不是直接设置。即如果成员已存在,其分数会被加上指定的值;如果不存在,则新增成员并设置分数为指定值。注意使用了这个选项,一次只能添加一个成员  注意:时间复杂度为O(logN),之前的hash set list等添加一个元素,都是O(1)   #### zrange命令 作用:查看有序集合中的元素详情,(类似于lrange,可以指定一对下标构成的区间) 对于有序集合,本身元素就是有先后顺序的,谁在前,谁在后,都是很明确的,因此也就可以给这个有序集合赋予下标这样的概念了 用法:   注意:当前查询结果,就是有序的(升序) #### zcard命令 作用:查看一个有序集合当中,有多少个元素 用法:   #### zcount命令 作用:查看一个有序集合的以score为标准的一个闭区间的元素有多少个 用法:   注意:  zcount是支持开区间的,如果你不想要一边就需要加(左括号,如果右边加了右括号是错的,语法规定就是这样,不符合直觉 时间复杂度为O(log(N)+M),N是有序集合中成员的总数,M是在指定分数范围内的成员数量  #### zrevrange命令 作用:rev即reverse(逆序),默认是升序,所以使用这个命令就是降序 用法:   #### zrangebyscore命令 作用:按照分数来找元素的 用法:  limit offset count:用于分页,`offset` 表示起始偏移量(从 0 开始),`count` 表示返回的最大数量。 注意:  zpopmax命令 作用:删除并返回分数最高的count个元素,(topK问题) 用法:   返回值:被删除的元素 注意:如果存储多个元素,分数相同,同时为最大值,zpopmax删除的时候,仍然只删除其中一个元素,分数虽然是主要因素,但是如果分数相同会按照member中的字符串的字典序决定先后(字典序回顾c语言c进阶,字符串和内存函数,strcmp(介绍字典序)) 时间复杂度:O(log(N)\*M) N:是有序集合的元素个数,M:count要删除的元素个数  #### bzpopmax命令 作用:  用法:  阻塞即在有序集合为空的时候触发阻塞,阻塞到有其他客户端插入元素 timeout表示最多阻塞多久,单位是s,支持小数形式,写作0.1就是100ms 注意:这里是可以同时阻塞多个key的,如果某个有序集合已经有了元素了,就直接就能返回,不会再阻塞了 时间复杂度为O(logN) #### zpopmin命令 作用:删除有序集合中最小的元素 用法:  注意:  #### bzpopmin命令 和之前的bzpopmax一样 #### zrank命令 作用:获取一个member的下标 用法:    注意:时间复杂度为O(logN) zcount再计算的时候,就是先根据分数找到元素,再根据元素获取到排名,再把排名一减,得到了元素的个数 这里zrank得到的下标,是从前往后算的(升序) #### zrevrank命令 作用:rev即reverse(逆序),逆序获取一个member的下标 用法:   注意:这里是按照降序来算 #### zscore命令 作用:根据member查找分数 用法:   注意:时间复杂度为O(1),此处相当于redis对于这样的查询操作做了特殊的优化,付出了额外的空间代价,针对这里进行了优化到了O(1)的实现 #### zrem命令 作用:删除某个元素 用法:   注意:时间复杂度为O(logN\*M) #### zremrangebyrank命令 作用:删除start-stop闭区间的元素 用法:  注意:时间复杂度为O(logN+M)N是整个有序集合的元素个数,M是start-stop区间的元素个数 #### zremrangebyscore命令 作用:删除max-min闭区间的元素,这个区间使用分数来描述的 用法:   注意:时间复杂度为O(logN+M) #### zincrby命令 作用:为有序集合(zset)中的指定成员增加一个增量分数(可以是正数或负数)。如果该成员不存在,则会先创建该成员并将其分数设为增量值。 用法:   注意:  修改完之后可能会移动元素,保持原来的升序的状态 #### zinter、zunion命令 这三个分别就是交集,并集,和数学当中的概念是一样的 这几个命令是从redis6.2开始支持的,5版本不支持,差集是没有的,因为可能会member一样score不一样,所以没有加进去 zinterstore、zunionstore都是将结果保存到另一个key当中  这个权重如果没写就是默认1,后续交集的时候member相同,如何计算score的意思   #### zset的内部编码方式 ##### ziplist: 如果有序集合中的元素个数较少,或者单个元素体积较小,使用ziplist来存储 ##### skiplist: 如果当前元素个数较多,或者单个元素体积非常大,就使用skiplist来存储  #### zset的应用场景 ##### 1、排行榜系统  