redis所有知识点的详细笔记

#                                 redis笔记

## 一、什么是NoSQL

### 1、NoSQL概述

- NoSQL(NoSQL = Not Only SQL),意即“不仅仅是SQL”,是一项全新的数据库理念,泛指非关系型的数据库。    
- 随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。

### 2、NOSQL和关系型数据库比较

- 优点:
              1)成本:nosql数据库简单易部署,基本都是开源软件,不需要像使用oracle那样花费大量成本购买使用,相比关系型数据库价格便宜。
                    2)查询速度:nosql数据库将数据存储于缓存之中,关系型数据库将数据存储在硬盘中,自然查询速度远不及nosql数据库。
                    3)存储数据的格式:nosql的存储格式是key,value形式、文档形式、图片形式等等,所以可以存储基础类型以及对象或者是集合等各种格式,而数据库则只支持基础类型。
                    4)扩展性:关系型数据库有类似join这样的多表查询机制的限制导致扩展很艰难。

- 缺点:
          1)维护的工具和资料有限,因为nosql是属于新的技术,不能和关系型数据库10几年的技术同日而语。
            2)不提供对sql的支持,如果不支持sql这样的工业标准,将产生一定用户的学习和使用成本。
            3)不提供关系型数据库对事务的处理。

### 3、非关系型数据库的优势:

- 性能NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高。
- 可扩展性同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。

### 4、关系型数据库的优势:

- 复杂查询可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。
- 事务支持使得对于安全性能很高的数据访问要求得以实现。对于这两类数据库,对方的优势就是自己的弱势,反之亦然。

### 5、总结

关系型数据库与NoSQL数据库并非对立而是互补的关系,即通常情况下使用关系型数据库,在适合使用NoSQL的时候使用NoSQL数据库,让NoSQL数据库对关系型数据库的不足进行弥补。一般会将数据存储在关系型数据库中,在nosql数据库中备份存储关系型数据库的数据

### 6、主流的NOSQL产品

​    •    键值(Key-Value)存储数据库
​            相关产品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB
​            典型应用: 内容缓存,主要用于处理大量数据的高访问负载。 
​            数据模型: 一系列键值对
​            优势: 快速查询
​            劣势: 存储的数据缺少结构化
​    •    列存储数据库
​            相关产品:Cassandra, HBase, Riak
​            典型应用:分布式的文件系统
​            数据模型:以列簇式存储,将同一列数据存在一起
​            优势:查找速度快,可扩展性强,更容易进行分布式扩展
​            劣势:功能相对局限
​    •    文档型数据库
​            相关产品:CouchDB、MongoDB
​            典型应用:Web应用(与Key-Value类似,Value是结构化的)
​            数据模型: 一系列键值对
​            优势:数据结构要求不严格
​            劣势: 查询性能不高,而且缺乏统一的查询语法
​    •    图形(Graph)数据库
​            相关数据库:Neo4J、InfoGrid、Infinite Graph
​            典型应用:社交网络
​            数据模型:图结构
​            优势:利用图结构相关算法。
​            劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。

## 二、什么是Redis

### 1、redis概述

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI [C语言](https://baike.baidu.com/item/C语言)编写、支持网络、可基于内存亦可持久化的日志型、Key-Value[数据库](https://baike.baidu.com/item/数据库/103728),并提供多种语言的API。50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s ,且Redis通过提供多种键值数据类型来适应不同场景下的存储需求。

### 2、Redis支持的键值数据类型如下:

- 字符串类型 string            
- 哈希类型 hash
- 列表类型 list
-  集合类型 set
- 有序集合类型 sortedset

### 3、redis的应用场景

- ​    缓存(数据查询、短连接、新闻内容、商品内容等等)
- ​    聊天室的在线好友列表
- ​    任务队列。(秒杀、抢购、12306等等)
- ​    应用排行榜
- ​    网站访问统计
- ​    数据过期处理(可以精确到毫秒)
- ​    分布式集群架构中的session分离

### 4、redis基础知识

#### 1、redis数据库切换

>  redis 内置16个数据库
>
> 切换:select (0~15)

#### 2、redis数据库清除

- flushdb 清楚本数据库全部数据'
- flushAll 清楚全部数据库数据

#### 3、自带测试工具(redis-benchmark)

测试语句

> redis-benchmark -h localhost -p 6379 -c 100 -n 100000

![image-20201005203936276](D:\NULL\学习资源\md笔记\images\redis\image-20201005203936276.png)

#### 4、redis是单线程

- Redis是很快的,官方表示,Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带
  宽,既然可以使用单线程来实现,就使用单线程了!所有就使用了单线程了!

- Redis 是C语言写的,官方提供的数据为100000+的QPS,完全不比同样是使用key-vale的Memecache差!
  Redis为什么单线程还这么快?
- 误区一:高性能服务器一定是多线程!
  误区二:多线程(CPU上下文会切换!)一定比单线程效率高!
  先去CPU>内存>硬盘的速度要有所了解!
- 核心:redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文会切换:耗
  没有操作!!!),对于内存系统来说,如果没有上下文切换效率就是最高的!多次读写都是在一个CPU上的,在内存情况这个就是最佳的方案!

## 三、redis五大数据类型常用操作

> - redis存储的是:key,value格式的数据,其中key都是字符串,value有5种不同的数据结构
> - value的数据结构:
>       1) 字符串类型 string
>         2) 哈希类型 hash : map格式  
>         3) 列表类型 list : linkedlist格式。支持重复元素
>         4) 集合类型 set  : 不允许重复元素
>         5) 有序集合类型 sortedset:不允许重复元素,且元素有顺序

### 1、String常用命令

> 1. 计数器
>
>   string类型的incr和decr命令的作用是将key中储存的数字值加一/减一,这两个操作具有原子性,总能安全地进行加减操作,因此可以用string类型进行计数,如微博的评论数、点赞数、分享数,抖音作品的收藏数,京东商品的销售量、评价数等。
>
>   2. 分布式锁
>
>   string类型的setnx的作用是“当key不存在时,设值并返回1,当key已经存在时,不设值并返回0”,“判断key是否存在”和“设值”两个操作是原子性地执行的,因此可以用string类型作为分布式锁,返回1表示获得锁,返回0表示没有获得锁。例如,为了保证定时任务的高可用,往往会同时部署多个具备相同定时任务的服务,但是业务上只希望其中的某一台服务执行定时任务,当定时任务的时间点触发时,多个服务同时竞争一个分布式锁,获取到锁的执行定时任务,没获取到的放弃执行定时任务。定时任务执行完时通过del命令删除key即释放锁,如果担心del命令操作失败而导致锁一直未释放,可以通过expire命令给锁设置一个合理的自动过期时间,确保即使del命令失败,锁也能被释放。不过expire命令同样存在失败的可能性,如果你用的是Java语言,建议使用JedisCommands接口提供的String set(String key, String value, String nxxx, String expx, long time)方法,这个方法可以将setnx和expire原子性地执行,具体使用方式如下(相信其它语言的Redis客户端也应当提供了类似的方法)。
>
> `jedisCommands.set("IAmAKey", "1", "NX", "EX", 60);//如果"IAmAKey"不存在,则将其设值为1,同时设置60秒的自动过期时间`
>
> 3. 存储对象
>
>   利用JSON强大的兼容性、可读性和易用性,将对象转换为JSON字符串,再存储在string类型中,是个不错的选择,如用户信 息、商品信息等。
>

>   string类型的常用命令可参考http://www.runoob.com/redis/redis-strings.html。
>
>   加入string类型的应用场景后的思维导图如下。


![ ](D:\NULL\学习资源\md笔记\images\redis\967517-20190405015017041-746069400.png)

```bash
select 3 #切换至3号数据库
dbsize #查看当前数据库的 数据量  一般为 k-v 的对数
keys * #查看当前库中的所有 key
flushdb #清空当前数据库
flushall #清空所有数据库
set name zz #存入一个 name-zz的 k-v数据
get name    #将返回这个key对应的值 zz
exits name  #判断当前key是否存在  存在返回1否则返回0
move name 1 #移动 这个k-v 到指定数据库
expire name 10 #设置这个k-v的过期时间为10秒
ttl name #查看这个k-v剩余的有效期时间
type name #查看当前key的类型
append name "hh" #将name对应的value值拼接  再次get name时会返回  zzhh   如果key不存在则等价于set
strlen name #查看这个key对应的value值的长度
set views 0
incr views #自增1 下次get views将返回1
decr views #自减1 下次get views将又返回0
incrby views 5 #自增5 下次get views将返回5
decrby views 5 #自减5 下次get views将返回0
getrange name 1 2 #返回name对应value的一部分 从下标1开始到下标2结束  即zh
getrange name 0 -1 #返回name对应value的一部分 从下标0开始到末尾  即zzhh
setrange name 1 ab #将name下标1开始的位置 替换为ab  返回  zabh
setex name1 10 "aaa" #如果name1不存在则创建k-v并指定过期时间10秒,具有原子性。 如果存在则覆盖value指定过期时间10秒
setnx name2 bbb #如果不存在这个key则创建成功返回1,如果存在 则创建失败返回0
mset k1 v1 k2 v2 k3 v3 #批量设置这3个k-v对
mget k1 k2 k3 #批量返回相应k值的相应value值
msetnx k1 v1 k4 v4 #批量不存在时设置,具有原子性,如此时k1存在k4不存在,但会全部失败返回0
set user:1 {name:zhangsan,age:10} #保存对象
mset user:1:name zhangsan user:1:ange 10 #也可有上述效果
getset name ccc #先get再set 不存在时 返回nil,但set仍然会生效!等价与创建了一个新的k-v。存在时返回之前的value值且覆盖掉这个value,下次get会返回刚设置的新值
```

### 2、list常用命令

> list类型是简单的字符串列表,按照插入顺序排序。每个列表最多可以存储 232 - 1 个元素(40多亿) ,list类型主要有以下应用场景。。
>
>   1. 消息队列
>
>   list类型的lpop和rpush(或者反过来,lpush和rpop)能实现队列的功能,故而可以用Redis的list类型实现简单的点对点的消息队列。不过我不推荐在实战中这么使用,因为现在已经有Kafka、NSQ、RabbitMQ等成熟的消息队列了,它们的功能已经很完善了,除非是为了更深入地理解消息队列,不然我觉得没必要去重复造轮子。
>
>   2. 排行榜
>
>   list类型的lrange命令可以分页查看队列中的数据。可将每隔一段时间计算一次的排行榜存储在list类型中,如京东每日的手机销量排行、学校每次月考学生的成绩排名、斗鱼年终盛典主播排名等,下图是酷狗音乐“K歌擂台赛”的昨日打擂金曲排行榜,每日计算一次,存储在list类型中,接口访问时,通过page和size分页获取打擂金曲。但是,并不是所有的排行榜都能用list类型实现,只有定时计算的排行榜才适合使用list类型存储,与定时计算的排行榜相对应的是实时计算的排行榜,list类型不能支持实时计算的排行榜,之后在介绍有序集合sorted set的应用场景时会详细介绍实时计算的排行榜的实现。
>
> 3. 最新列表
>
>   list类型的lpush命令和lrange命令能实现最新列表的功能,每次通过lpush命令往列表里插入新的元素,然后通过lrange命令读取最新的元素列表,如朋友圈的点赞列表、评论列表。
>
>   但是,并不是所有的最新列表都能用list类型实现,因为对于频繁更新的列表,list类型的分页可能导致列表元素重复或漏掉,举个例子,当前列表里由表头到表尾依次有(E,D,C,B,A)五个元素,每页获取3个元素,用户第一次获取到(E,D,C)三个元素,然后表头新增了一个元素F,列表变成了(F,E,D,C,B,A),此时用户取第二页拿到(C,B,A),元素C重复了。只有不需要分页(比如每次都只取列表的前5个元素)或者更新频率低(比如每天凌晨更新一次)的列表才适合用list类型实现。对于需要分页并且会频繁更新的列表,需用使用有序集合sorted set类型实现。另外,需要通过时间范围查找的最新列表,list类型也实现不了,也需要通过有序集合sorted set类型实现,如以成交时间范围作为条件来查询的订单列表。之后在介绍有序集合sorted set类型的应用场景时会详细介绍sorted set类型如何实现最新列表。![ ](C:\Users\26321\Desktop\967517-20190415095729512-729127615.png)

```bash
#list的操作
lpush list one #往list顶端插入值
lrange list 0 -1 #返回list中所有值
rpush list four #往list底部插入值
lpop list #移除list顶端元素,并返回该元素
rpop list #移除list底部元素,并返回该元素
lindex list 0 #获取list中指定下标为0的值,同上理解就是顶端
llen list #返回list的长度
lrem list 1 value #移除list中指定个数的value值
ltrim list 1 2 #截断list,保留指定下标中的值
rpoplpush list1 haha2 #从list1底部移除一个元素并返回,且将该元素插入list2的顶端
exists list #判断list是否存在  存在返回1否则返回0
lset list 0 haha #修改列表指定位置的值, 需要列表和该位置元素已存在,否则报错
linsert list before hello new #往指定列表的指定元素的前面插入指定值
linsert list after hello new1 #往指定列表的指定元素的后面插入指定值
```

### 3、set集合

>1. 好友/关注/粉丝/感兴趣的人集合
>
>  set类型唯一的特点使得其适合用于存储好友/关注/粉丝/感兴趣的人集合,集合中的元素数量可能很多,每次全部取出来成本不小,set类型提供了一些很实用的命令用于直接操作这些集合,如
>
>    a. sinter命令可以获得A和B两个用户的共同好友
>
>     b. sismember命令可以判断A是否是B的好友
>
>     c. scard命令可以获取好友数量
>
>     c. 关注时,smove命令可以将B从A的粉丝集合转移到A的好友集合
>
>  需要注意的是,如果你用的是Redis Cluster集群,对于sinter、smove这种操作多个key的命令,要求这两个key必须存储在同一个slot(槽位)中,否则会报出 (error) CROSSSLOT Keys in request don't hash to the same slot 错误。Redis Cluster一共有16384个slot,每个key都是通过哈希算法CRC16(key)获取数值哈希,再模16384来定位slot的。要使得两个key处于同一slot,除了两个key一模一样,还有没有别的方法呢?答案是肯定的,Redis提供了一种**Hash Tag**的功能,在key中使用{}括起key中的一部分,在进行 CRC16(key) mod 16384 的过程中,只会对{}内的字符串计算,例如friend_set:{123456}和fans_set:{123456},分别表示用户123456的好友集合和粉丝集合,在定位slot时,只对{}内的123456进行计算,所以这两个集合肯定是在同一个slot内的,当用户123456关注某个粉丝时,就可以通过smove命令将这个粉丝从用户123456的粉丝集合移动到好友集合。相比于通过srem命令先将这个粉丝从粉丝集合中删除,再通过sadd命令将这个粉丝加到好友集合,smove命令的优势是它是原子性的,不会出现这个粉丝从粉丝集合中被删除,却没有加到好友集合的情况。然而,对于通过sinter获取共同好友而言,Hash Tag则无能为力,例如,要用sinter去获取用户123456和456789两个用户的共同好友,除非我们将key定义为{friend_set}:123456和{friend_set}:456789,否则不能保证两个key会处于同一个slot,但是如果真这样做的话,所有用户的好友集合都会堆积在同一个slot中,数据分布会严重不均匀,不可取,所以,**在实战中使用Redis Cluster时,sinter这个命令其实是不适合作用于两个不同用户对应的集合的**(同理其它操作多个key的命令)。
>
>2. 随机展示
>
>  通常,app首页的展示区域有限,但是又不能总是展示固定的内容,一种做法是先确定一批需要展示的内容,再从中随机获取。如下图所示,酷狗音乐K歌擂台赛当日的打擂歌曲共29首,首页随机展示5首;昨日打擂金曲共200首,首页随机展示30首。
>
>set类型适合存放所有需要展示的内容,而srandmember命令则可以从中随机获取几个。
>
>3. 黑名单/白名单
>
>  经常有业务出于安全性方面的考虑,需要设置用户黑名单、ip黑名单、设备黑名单等,set类型适合存储这些黑名单数据,sismember命令可用于判断用户、ip、设备是否处于黑名单之中。

![967517-20190417162215588-320188667](D:\NULL\学习资源\md笔记\images\redis\967517-20190417162215588-320188667.png)

```bash
# set操作(无序不重复集合)
sadd myset zhangsan lisi  SMEMBERS myset # set集合中添加值
SMEMBERS myset # 查看set集合的所有值
SISMEMBER myset lisi # 判断set集合中是否有这个值
scard myset # 获取set集合中的元素个数
srem myset lisi # 删除set集合指定的元素
SRANDMEMBER myset  # 随机抽取出一个元素
SRANDMEMBER myset 2 # 随机抽选出2个元素
spop myset # 随机删除set集合的元素
spop myset 2 # 随机删除set集合的2ge元素
SDIFF myset myset2 # 两个set集合的差集
SINTER myset myset2 # 两个set集合的交集
SUNION myset myset2 # 两个set集合的并集
```

### 4、Hash(哈希)

Map集合, key-map!时候这个值是一个map集合 !本质和string没有太大区别,还是一个简单的key-value!

>  hash类型是一个string类型的field和value的映射
>
> 表,每个 hash 可以存储 232 - 1 键值对(40多亿),hash类型主要有以下应用场景。
>
> 1. 购物车
>
>   以用户id为key,商品id为field,商品数量为value,恰好构成了购物车的3个要素。
>
> 2. 存储对象
>
>   hash类型的(key, field, value)的结构与对象的(对象id, 属性, 值)的结构相似,也可以用来存储对象。
>
>   在介绍string类型的应用场景时有所介绍,string + json也是存储对象的一种方式,那么存储对象时,到底用string + json还是用hash呢?
>
>   两种存储方式的对比如下表所示。
>
> |        | string + json | hash     |
> | ------ | ------------- | -------- |
> | 效率   | 很高          | 高       |
> | 容量   | 低            | 低       |
> | 灵活性 | **低**        | 高       |
> | 序列化 | 简单          | **复杂** |
>
>   当对象的某个属性需要频繁修改时,不适合用string+json,因为它不够灵活,每次修改都需要重新将整个对象序列化并赋值,如果使用hash类型,则可以针对某个属性单独修改,没有序列化,也不需要修改整个对象。比如,商品的价格、销量、关注数、评价数等可能经常发生变化的属性,就适合存储在hash类型里。
>
>   当然,不常变化的属性存储在hash类型里也没有问题,比如商品名称、商品描述、上市日期等。但是,当对象的某个属性不是基本类型或字符串时,使用hash类型就必须手动进行复杂序列化,比如,商品的标签是一个标签对象的列表,商品可领取的优惠券是一个优惠券对象的列表(如下图所示)等,即使以coupons(优惠券)作为field,value想存储优惠券对象列表也还是要使用json来序列化,这样的话序列化工作就太繁琐了,不如直接用string + json的方式存储商品信息来的简单。

![967517-20190405020705399-1272027899](D:\NULL\学习资源\md笔记\images\redis\967517-20190405020705399-1272027899.png)

```bash
# hash操作
#hash变更的数据user name age,尤其是是用户信息之类的,经常变动的信息! hash更适合于对象的存储, String更加适合字符串存储!
hset myhash k1 v1 # 设置一个key-value
hmget k1 k2 # 根据多个key获取value
hmset myhash l1 v2 l3 v4 # 设置多个key-value
hgetall myhash # 查询hash表的所有值
hdel myhash k1 # 删除指定的field
hlen myhash # 获取hash表的field数量
HEXISTS myhash k2 # 判断hash表中是否存在这个field
hkeys myhash # 只获得hash表中的所有field
HVALS myhash # 只获得hash表中的所有value
HINCRBY myhash k1 1 #自增1 
hsetnx myhash k1 v1 # 如果不存在这个key则创建成功返回1,如果存在 则创建失败返回0
```

### 5、Zset(有序集合)

- 销量排名、积分排名、成绩排名等...
  - 发表时间作为score存储,这样子获取的时候就是自动排好序的\
  - 成绩作为score
  - 带权重队列,让重要的任务优先执行

>1. 延时队列
>
>zset 会按 score 进行排序,如果 score 代表想要执行时间的时间戳。在某个时间将它插入zset集合中,它变会按照时间戳大小进行排序,也就是对执行时间前后进行排序。
>
>起一个死循环线程不断地进行取第一个key值,如果当前时间戳大于等于该key值的score就将它取出来进行消费删除,可以达到延时执行的目的。
>
>2. 排行榜
>
>经常浏览技术社区的话,应该对 “1小时最热门” 这类榜单不陌生。如何实现呢?如果记录在数据库中,不太容易对实时统计数据做区分。我们以当前小时的时间戳作为 zset 的 key,把贴子ID作为 member ,点击数评论数等作为 score,当 score 发生变化时更新 score。利用 ZREVRANGE 或者 ZRANGE 查到对应数量的记录。
>
>3. 限流
>
>滑动窗口是限流常见的一种策略。如果我们把一个用户的 ID 作为 key 来定义一个 zset ,member 或者 score 都为访问时的时间戳。我们只需统计某个 key 下在指定时间戳区间内的个数,就能得到这个用户滑动窗口内访问频次,与最大通过次数比较,来决定是否允许通过。

```bash
# Zset操作
zadd sarly 5000 wangwu # 添加操作
ZRANGE sarly 0 -1 # 输出zset的所有值
ZRANGEBYSCORE sarly -inf +inf  # 升序输出全部的用户
ZRANGEBYSCORE sarly -inf +inf withscores # 序输出全部的用户,且显示工资
ZRANGEBYSCORE sarly -inf +inf 2500 withscores # 序输出全部的用户,且显示大于2500的工资
ZREVRANGEBYSCORE sarly +inf -inf # 降序排序
zrem sarly lisi # 删除用户
zcard  sarly # 获取有序集合的元素个数
ZREVRANGE sarly 0 -1 # 反转
zcount sarly 500 2500 # 获取指定区间的元素的数
```

## 四、三大特殊数据类型常用操作

### 1、geospatial(地理位置)

> geospatial常用命令

- GEOADD

  ```bash
  # getadd 添加地理位置
  #规则:两级无法直接添加,我们一般会下载城市数据,直接通过java程序一 次性导入!
  #有效的经度从-180度到180度。
  #有效的纬度从-85. 05112878度到85.05112878度。
  #当坐标位置超出上述指定范围时,该命令将会返回一个错误。 
  GEOADD china:cipty  31.405 121.4894 shanghai # 添加地理位置
  
  ```

- GEODIST

  ```bash
   geodist china:cipty beijing shanghai km # 获取两个地理之间的距离,单位是km
  ```

- GEOHASH

  ```bash
  geohash china:cipty beijing shanghai # 将二维的经纬度转换为-维的字符串,如果两个字符串越接近,那么则距离越近!
  ```

- GEOPOS

  ```bash
  geopos china:cipty beijing  #获取指定的城市的经度和纬度!
  ```

- GEORADIUS

  ```bash
   GEORADIUS china:cipty 110 30 1000 km  # georadius以给定的经纬度为中心,找出某一半径内的元素
  ```

- GEORADIUSBYMEMBER

  ```bash
  GEORADIUSBYMEMBER china:cipty beijing 10000 km # 找出位于指定元素周围的其他元素!
  ```

总结

> GEO底层的实现原理其实就是Zset !我们可以使用Zset命令来操作geo !
>
> - 删除
> - 查询
> - ...等操作

### 2、Hyperloglog(基数统计)

> ### 应用场景
>
> 说明:
>
> - 基数不大,数据量不大就用不上,会有点大材小用浪费空间
> - 有局限性,就是只能统计基数数量,而没办法去知道具体的内容是什么
> - 和bitmap相比,属于两种特定统计情况,简单来说,HyperLogLog 去重比 bitmap 方便很多
> - 一般可以bitmap和hyperloglog配合使用,bitmap标识哪些用户活跃,hyperloglog计数
>
> 一般使用:
>
> - 统计注册 IP 数
> - 统计每日访问 IP 数
> - 统计页面实时 UV 数
> - 统计在线用户数
> - 统计用户每天搜索不同词条的个数

```bash
PFadd mykey aIa b C defghij #创建第一组元素mykey
PFCOUNT mykey # 统计mykey元素的基数数量
PFadd mykey2 i j z xcvbnm #创建第二组元素mykey2
PFMERGE mykey3 mykey mykey2 #合并两组mykey mykey2 => mykey3 并集
PFCOUNT mykey3 #看并集的数量!
```

如果允许容错,那么-定可以使用Hyperloglog !
如果不允许容错,就使用Iset或者自己的数据类型即可!

### 3、Bitmaps (位图)

- 统计用户信息,活跃,不活跃!登录、未登录!打卡, 365打卡!两个状态的,都可以使用Bitmaps !
- Bitmaps位图,数据结构!都是操作二进制位来进行记录,就只有0和1两个状态!

> ###### 使用场景一:用户签到
>
> 很多网站都提供了签到功能(这里不考虑数据落地事宜),并且需要展示最近一个月的签到情况.
>
> ###### 使用场景二:统计活跃用户
>
> 使用时间作为cacheKey,然后用户ID为offset,如果当日活跃过就设置为1那么我该如果计算某几天/月/年的活跃用户呢(暂且约定,统计时间内只有有一天在线就称为活跃),有请下一个redis的命令命令 `BITOP operation destkey key [key ...]`
> 说明:对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
>
> ###### 使用场景三:用户在线状态
>
> bitmap是一个节约空间效率又高的一种方法,只需要一个key,然后用户ID为offset,如果在线就设置为1,不在线就设置为0.
>
> ###### 

```bash
SETBIT sig 0 1 # 设置位图
GETBIT sig 6 # 获取位图
################################
`用户签到`:模拟用户打卡场景,0-6为一周,0为未打卡,1为打卡
BITCOUNT sig  # 统计用户一周打卡天数
```

![image-20201006212348625](D:\NULL\学习资源\md笔记\images\redis\image-20201006212348625.png)

## 五、redis事务

==Redis单条命令式保存原子性的,但是事务不保证原子性!==

==Redis事务没有没有隔离级别的概念!==
所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行! Exec

> Redis事务本质: 一组命令的集合! -一个事务中的所有命令都会被序列化,在事务执行过程的中,会按照顺序执行!
> 一次性、顺序性、排他性!执行- -些列的命令!

redis的事务:

- 开启事务( multi)

- 命令入队(-----)
- 执行事务(exec)

> redis事务操作步骤

![image-20201007084953524](D:\NULL\学习资源\md笔记\images\redis\image-20201007084953524.png)

> 放弃事务:` DISCARD`

![image-20201007085507640](D:\NULL\学习资源\md笔记\images\redis\image-20201007085507640.png)

> 编译型异常(代码有问题!命令有错! ) , 事务中所有的命令都不会被执行!

![image-20201007090026763](D:\NULL\学习资源\md笔记\images\redis\image-20201007090026763.png)

> 运行时异常( 1/0),如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常!

![image-20201007090449759](D:\NULL\学习资源\md笔记\images\redis\image-20201007090449759.png)

## 六、redis监控

> 监控! Watch
>
> UNWATCH

悲观锁:

- 很悲观,认为什么时候都会出问题,无论做什么都会加锁!
  乐观锁:
- 很乐观,认为什么时候都不会出问题,所以不会上锁!更新数据的时候去判断- - 下,在此期间是否有人修改过这个数据,
- 获取version
- 更新的时候比较version

![image-20201007164958729](D:\NULL\学习资源\md笔记\images\redis\image-20201007164958729.png)

#### 应用场景

> 利用redis来实现乐观锁 
>
> ​     1、利用redis的watch功能,监控这个redisKey的状态值
>  ​    2、获取redisKey的值
>  ​    3、创建redis事务
>  ​    4、给这个key的值+1
>  ​    5、然后去执行这个事务,如果key的值被修改过则回滚,key不+1

## 七、Jedis

### 1、什么是jedis

> jedis是Redis官方推荐的java连接开发工具!使用Java 操作Redis中间件!如果你要使用java操作redis ,那么一定要对Jedis十分的熟悉!

### 2 、jedis方法

> 方法和redis窗口命令基本相同

## 八、springboot整合redis

> springboot进阶已记录:链接:

## 九、redis.config详解

```bash
# 当配置中需要配置内存大小时,可以使用 1k, 5GB, 4M 等类似的格式,其转换方式如下(不区分大小写)
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# 内存配置大小写是一样的.比如 1gb 1Gb 1GB 1gB
 
 
# daemonize no 默认情况下,redis不是在后台运行的,如果需要在后台运行,把该项的值更改为yes
daemonize yes
 
# 当redis在后台运行的时候,Redis默认会把pid文件放在/var/run/redis.pid,你可以配置到其他地址。
# 当运行多个redis服务时,需要指定不同的pid文件和端口
pidfile /var/run/redis.pid
 
# 指定redis运行的端口,默认是6379
port 6379
 
# 指定redis只接收来自于该IP地址的请求,如果不进行设置,那么将处理所有请求,
# 在生产环境中最好设置该项
# bind 127.0.0.1
 
# Specify the path for the unix socket that will be used to listen for
# incoming connections. There is no default, so Redis will not listen
# on a unix socket when not specified.
#
# unixsocket /tmp/redis.sock
# unixsocketperm 755
 
# 设置客户端连接时的超时时间,单位为秒。当客户端在这段时间内没有发出任何指令,那么关闭该连接
# 0是关闭此设置
timeout 0
 
# 指定日志记录级别
# Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
# debug 记录很多信息,用于开发和测试
# varbose 有用的信息,不像debug会记录那么多
# notice 普通的verbose,常用于生产环境
# warning 只有非常重要或者严重的信息会记录到日志
loglevel debug
 
# 配置log文件地址
# 默认值为stdout,标准输出,若后台模式会输出到/dev/null
#logfile stdout
logfile /var/log/redis/redis.log
 
# To enable logging to the system logger, just set 'syslog-enabled' to yes,
# and optionally update the other syslog parameters to suit your needs.
# syslog-enabled no
 
# Specify the syslog identity.
# syslog-ident redis
 
# Specify the syslog facility.  Must be USER or between LOCAL0-LOCAL7.
# syslog-facility local0
 
# 可用数据库数
# 默认值为16,默认数据库为0,数据库范围在0-(database-1)之间
databases 16
 
################################ 快照  #################################
#
# 保存数据到磁盘,格式如下:
#
#   save
#
#   指出在多长时间内,有多少次更新操作,就将数据同步到数据文件rdb。
#   相当于条件触发抓取快照,这个可以多个条件配合
#   
#   比如默认配置文件中的设置,就设置了三个条件
#
#   save 900 1  900秒内至少有1个key被改变
#   save 300 10  300秒内至少有300个key被改变
#   save 60 10000  60秒内至少有10000个key被改变
 
save 900 1
save 300 10
save 60 10000
 
# 存储至本地数据库时(持久化到rdb文件)是否压缩数据,默认为yes
rdbcompression yes
 
 
# 本地持久化数据库文件名,默认值为dump.rdb
dbfilename dump.rdb
 
# 工作目录
#
# 数据库镜像备份的文件放置的路径。
# 这里的路径跟文件名要分开配置是因为redis在进行备份时,先会将当前数据库的状态写入到一个临时文件中,等备份完成时,
# 再把该该临时文件替换为上面所指定的文件,而这里的临时文件和上面所配置的备份文件都会放在这个指定的路径当中。
#
# AOF文件也会存放在这个目录下面
#
# 注意这里必须制定一个目录而不是文件
dir ./
 
################################# 复制 #################################
 
# 主从复制. 设置该数据库为其他数据库的从数据库.
# 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步
#
# slaveof
 
# 当master服务设置了密码保护时(用requirepass制定的密码)
# slav服务连接master的密码
#
# masterauth
 
 
# 当从库同主机失去连接或者复制正在进行,从机库有两种运行方式:
#
# 1) 如果slave-serve-stale-data设置为yes(默认设置),从库会继续相应客户端的请求
#
# 2) 如果slave-serve-stale-data是指为no,出去INFO和SLAVOF命令之外的任何请求都会返回一个
#    错误"SYNC with master in progress"
#
slave-serve-stale-data yes
 
# 从库会按照一个时间间隔向主库发送PINGs.可以通过repl-ping-slave-period设置这个时间间隔,默认是10秒
#
# repl-ping-slave-period 10
 
# repl-timeout 设置主库批量数据传输时间或者ping回复时间间隔,默认值是60秒
# 一定要确保repl-timeout大于repl-ping-slave-period
# repl-timeout 60
 
################################## 安全 ###################################
 
# 设置客户端连接后进行任何其他指定前需要使用的密码。
# 警告:因为redis速度相当快,所以在一台比较好的服务器下,一个外部的用户可以在一秒钟进行150K次的密码尝试,这意味着你需要指定非常非常强大的密码来防止暴力破解
#
# requirepass foobared
 
# 命令重命名.
#
# 在一个共享环境下可以重命名相对危险的命令。比如把CONFIG重名为一个不容易猜测的字符。
#
# 举例:
#
# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
#
# 如果想删除一个命令,直接把它重命名为一个空字符""即可,如下:
#
# rename-command CONFIG ""
 
################################### 约束 ####################################
 
# 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,
# 如果设置 maxclients 0,表示不作限制。
# 当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
#
# maxclients 128
 
# 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key
# Redis同时也会移除空的list对象
#
# 当此方法处理后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作
#
# 注意:Redis新的vm机制,会把Key存放内存,Value会存放在swap区
#
# maxmemory的设置比较适合于把redis当作于类似memcached的缓存来使用,而不适合当做一个真实的DB。
# 当把Redis当做一个真实的数据库使用的时候,内存使用将是一个很大的开销
# maxmemory
 
# 当内存达到最大值的时候Redis会选择删除哪些数据?有五种方式可供选择
#
# volatile-lru -> 利用LRU算法移除设置过过期时间的key (LRU:最近使用 Least Recently Used )
# allkeys-lru -> 利用LRU算法移除任何key
# volatile-random -> 移除设置过过期时间的随机key
# allkeys->random -> remove a random key, any key
# volatile-ttl -> 移除即将过期的key(minor TTL)
# noeviction -> 不移除任何可以,只是返回一个写错误
#
# 注意:对于上面的策略,如果没有合适的key可以移除,当写的时候Redis会返回一个错误
#
#       写命令包括: set setnx setex append
#       incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
#       sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
#       zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
#       getset mset msetnx exec sort
#
# 默认是:
#
# maxmemory-policy volatile-lru
 
# LRU 和 minimal TTL 算法都不是精准的算法,但是相对精确的算法(为了节省内存),随意你可以选择样本大小进行检测。
# Redis默认的灰选择3个样本进行检测,你可以通过maxmemory-samples进行设置
#
# maxmemory-samples 3
 
############################## AOF ###############################
 
# 默认情况下,redis会在后台异步的把数据库镜像备份到磁盘,但是该备份是非常耗时的,而且备份也不能很频繁,如果发生诸如拉闸限电、拔插头等状况,那么将造成比较大范围的数据丢失。
# 所以redis提供了另外一种更加高效的数据库备份及灾难恢复方式。
# 开启append only模式之后,redis会把所接收到的每一次写操作请求都追加到appendonly.aof文件中,当redis重新启动时,会从该文件恢复出之前的状态。
# 但是这样会造成appendonly.aof文件过大,所以redis还支持了BGREWRITEAOF指令,对appendonly.aof 进行重新整理。
# 你可以同时开启asynchronous dumps 和 AOF
 
appendonly no
 
# AOF文件名称 (默认: "appendonly.aof")
# appendfilename appendonly.aof
 
 
# Redis支持三种同步AOF文件的策略:
#
# no: 不进行同步,系统去操作 . Faster.
# always: always表示每次有写操作都进行同步. Slow, Safest.
# everysec: 表示对写操作进行累积,每秒同步一次. Compromise.
#
# 默认是"everysec",按照速度和安全折中这是最好的。
# 如果想让Redis能更高效的运行,你也可以设置为"no",让操作系统决定什么时候去执行
# 或者相反想让数据更安全你也可以设置为"always"
#
# 如果不确定就用 "everysec".
 
# appendfsync always
appendfsync everysec
# appendfsync no
 
# AOF策略设置为always或者everysec时,后台处理进程(后台保存或者AOF日志重写)会执行大量的I/O操作
# 在某些Linux配置中会阻止过长的fsync()请求。注意现在没有任何修复,即使fsync在另外一个线程进行处理
#
# 为了减缓这个问题,可以设置下面这个参数no-appendfsync-on-rewrite
#
# This means that while another child is saving the durability of Redis is
# the same as "appendfsync none", that in pratical terms means that it is
# possible to lost up to 30 seconds of log in the worst scenario (with the
# default Linux settings).
#
# If you have latency problems turn this to "yes". Otherwise leave it as
# "no" that is the safest pick from the point of view of durability.
no-appendfsync-on-rewrite no
 
# Automatic rewrite of the append only file.
# AOF 自动重写
# 当AOF文件增长到一定大小的时候Redis能够调用 BGREWRITEAOF 对日志文件进行重写
#
# 它是这样工作的:Redis会记住上次进行些日志后文件的大小(如果从开机以来还没进行过重写,那日子大小在开机的时候确定)
#
# 基础大小会同现在的大小进行比较。如果现在的大小比基础大小大制定的百分比,重写功能将启动
# 同时需要指定一个最小大小用于AOF重写,这个用于阻止即使文件很小但是增长幅度很大也去重写AOF文件的情况
# 设置 percentage 为0就关闭这个特性
 
 
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
 
################################## SLOW LOG ###################################
 
# Redis Slow Log 记录超过特定执行时间的命令。执行时间不包括I/O计算比如连接客户端,返回结果等,只是命令执行时间
#
# 可以通过两个参数设置slow log:一个是告诉Redis执行超过多少时间被记录的参数slowlog-log-slower-than(微妙),
# 另一个是slow log 的长度。当一个新命令被记录的时候最早的命令将被从队列中移除
 
 
# 下面的时间以微妙微单位,因此1000000代表一分钟。
# 注意制定一个负数将关闭慢日志,而设置为0将强制每个命令都会记录
slowlog-log-slower-than 10000
 
 
# 对日志长度没有限制,只是要注意它会消耗内存
# 可以通过 SLOWLOG RESET 回收被慢日志消耗的内存
slowlog-max-len 1024
 
################################ VM ###############################
 
### WARNING! Virtual Memory is deprecated in Redis 2.4
### The use of Virtual Memory is strongly discouraged.
 
# Virtual Memory allows Redis to work with datasets bigger than the actual
# amount of RAM needed to hold the whole dataset in memory.
# In order to do so very used keys are taken in memory while the other keys
# are swapped into a swap file, similarly to what operating systems do
# with memory pages.
#
# To enable VM just set 'vm-enabled' to yes, and set the following three
# VM parameters accordingly to your needs.
 
vm-enabled no
# vm-enabled yes
 
# This is the path of the Redis swap file. As you can guess, swap files
# can't be shared by different Redis instances, so make sure to use a swap
# file for every redis process you are running. Redis will complain if the
# swap file is already in use.
#
# The best kind of storage for the Redis swap file (that's accessed at random)
# is a Solid State Disk (SSD).
#
# *** WARNING *** if you are using a shared hosting the default of putting
# the swap file under /tmp is not secure. Create a dir with access granted
# only to Redis user and configure Redis to create the swap file there.
vm-swap-file /tmp/redis.swap
 
# vm-max-memory configures the VM to use at max the specified amount of
# RAM. Everything that deos not fit will be swapped on disk *if* possible, that
# is, if there is still enough contiguous space in the swap file.
#
# With vm-max-memory 0 the system will swap everything it can. Not a good
# default, just specify the max amount of RAM you can in bytes, but it's
# better to leave some margin. For instance specify an amount of RAM
# that's more or less between 60 and 80% of your free RAM.
vm-max-memory 0
 
# Redis swap files is split into pages. An object can be saved using multiple
# contiguous pages, but pages can't be shared between different objects.
# So if your page is too big, small objects swapped out on disk will waste
# a lot of space. If you page is too small, there is less space in the swap
# file (assuming you configured the same number of total swap file pages).
#
# If you use a lot of small objects, use a page size of 64 or 32 bytes.
# If you use a lot of big objects, use a bigger page size.
# If unsure, use the default :)
vm-page-size 32
 
# Number of total memory pages in the swap file.
# Given that the page table (a bitmap of free/used pages) is taken in memory,
# every 8 pages on disk will consume 1 byte of RAM.
#
# The total swap size is vm-page-size * vm-pages
#
# With the default of 32-bytes memory pages and 134217728 pages Redis will
# use a 4 GB swap file, that will use 16 MB of RAM for the page table.
#
# It's better to use the smallest acceptable value for your application,
# but the default is large in order to work in most conditions.
vm-pages 134217728
 
# Max number of VM I/O threads running at the same time.
# This threads are used to read/write data from/to swap file, since they
# also encode and decode objects from disk to memory or the reverse, a bigger
# number of threads can help with big objects even if they can't help with
# I/O itself as the physical device may not be able to couple with many
# reads/writes operations at the same time.
#
# The special value of 0 turn off threaded I/O and enables the blocking
# Virtual Memory implementation.
vm-max-threads 4
 
############################### ADVANCED CONFIG ###############################
 
# 当hash中包含超过指定元素个数并且最大的元素没有超过临界时,
# hash将以一种特殊的编码方式(大大减少内存使用)来存储,这里可以设置这两个临界值
# Redis Hash对应Value内部实际就是一个HashMap,实际这里会有2种不同实现,
# 这个Hash的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,对应的value redisObject的encoding为zipmap,
# 当成员数量增大时会自动转成真正的HashMap,此时encoding为ht。
hash-max-zipmap-entries 512
hash-max-zipmap-value 64
 
# list数据类型多少节点以下会采用去指针的紧凑存储格式。
# list数据类型节点值大小小于多少字节会采用紧凑存储格式。
list-max-ziplist-entries 512
list-max-ziplist-value 64
 
# set数据类型内部数据如果全部是数值型,且包含多少节点以下会采用紧凑格式存储。
set-max-intset-entries 512
 
# zsort数据类型多少节点以下会采用去指针的紧凑存储格式。
# zsort数据类型节点值大小小于多少字节会采用紧凑存储格式。
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
 
 
# Redis将在每100毫秒时使用1毫秒的CPU时间来对redis的hash表进行重新hash,可以降低内存的使用
#
# 当你的使用场景中,有非常严格的实时性需要,不能够接受Redis时不时的对请求有2毫秒的延迟的话,把这项配置为no。
#
# 如果没有这么严格的实时性要求,可以设置为yes,以便能够尽可能快的释放内存
activerehashing yes
 
################################## INCLUDES ###################################
 
# 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
 
# include /path/to/local.conf
# include /path/to/other.conf 
```

## 十、redis持久化操作

> 持久化的作用

Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么- -旦服务器进程退出 ,服务器中的数据库状态也会消失。所
以Redis提供了持久化功能!

### 1、持久化之RDB

> RDB概念

![image-20201008175837117](D:\NULL\学习资源\md笔记\images\redis\image-20201008175837117.png)

在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。
Redis会单独创建( fork ) -个子进程来进行持久化,会先将数据写入到-一个临时文件中,待持久化过程都结束了,再用这个临时文
件替换上次持久化好的文件。整个过程中,主进程是不进行任何I0操作的。这就确保了极高的性能。如果需要进行大规模数据的恢
复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后- -次持久化后的数据可能丢
失。

> 配置文件参数

```bash
 
################################ 快照  #################################
# 保存数据到磁盘,格式如下:
#   save
#   指出在多长时间内,有多少次更新操作,就将数据同步到数据文件rdb。
#   相当于条件触发抓取快照,这个可以多个条件配合
#   比如默认配置文件中的设置,就设置了三个条件
#   save 900 1  900秒内至少有1个key被改变
#   save 300 10  300秒内至少有300个key被改变
#   save 60 10000  60秒内至少有10000个key被改变
save 900 1
save 300 10
save 60 10000
# 存储至本地数据库时(持久化到rdb文件)是否压缩数据,默认为yes
rdbcompression yes
# 本地持久化数据库文件名,默认值为dump.rdb
dbfilename dump.rdb
```

==RDB保存的文件格式默认是dump.rdb==

> RDB文件保存触发规则

1. save的规则满足的情况下,会自动触发rdb规则
2. 执行flushall命令,也会触发我们的rdb规则!
3. 退出redis ,也会产生rdb文件!

> 如何恢复RDB文件

1、只需要将rdb文件放在我们redis启动目录就可以, redis启动的时候会自动检查dump.rdb恢复其中的数据!
2、查看需要存在的位置

![image-20201008180742770](D:\NULL\学习资源\md笔记\images\redis\image-20201008180742770.png)

> 优点

1、适合大规模的数据恢复!
2、对数据的完整性要不高!

> 缺点

1、需要一定的时间间隔进程操作!如果redis意外宕机了,这个最后一次修改数据就没有的了! .

2、fork进程的时候,会占用一定的内容空间! !

### 2、持久化之AOF ( Append Only File )

**概念**

> 将我们的所有命令都记录下来, history ,恢复的时候就把这个文件全部在执行一遍!

![image-20201008201007537](D:\NULL\学习资源\md笔记\images\redis\image-20201008201007537.png)

以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录) , 只许追加文件但不可以改写文件, redis启动之初会读取该文件重新构建数据,换言之, redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作Aof保存的是appendonly.aof文件。

> 开启aof

![image-20201008204214197](D:\NULL\学习资源\md笔记\images\redis\image-20201008204214197.png)

==默认是不开启的,我们需要手动进行配置!我们只需要将appendoly改为yes就开启了aof !==

> aof文件有错

如果这个aof文件有错位,这时候redis 是启动不起来的吗,我们需要修复这个aof文件
redis给我们提供了一个工具`redis-check-aof --fix`

> 优点和缺点!

```bash
appendonly no
#默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分所有的情况下,rdb完全够用!
appendfilename "appendonly.aof" # 持久化的文件的名字

# appendfsync always

#每次修改都会sync。消耗性能
appendfsync everysec
#每秒执行一次sync, 可能会丢失这1s的数据!

# appendfsync no

#不执行sync, 这个时候操作系统自己同步数据,速度最快!
```

**优点:**
1、每一次修改都同步,文件的完整会更加好!
2、每秒同步-次,可能会丢失-秒的数据
3、从不同步,效率最高的!

**缺点:**
1、相对于数据文件来说, aof远远大于rdb ,修复的速度也比rdb慢!
2、Aof 运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化!

> 扩展

1、RDB持久化方式能够在指定的时间间隔内对你的数据进行快照存储

2、AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据, AOF命令以Redis协
     议追加保存每次写的操作到文件末尾, Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。

3、只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化

4、同时开启两种持久化方式

- 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB
  文件保存的数据集要完整。
- RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者建议不要,因为RDB更适合
  用于备份数据库( AOF在不断变化不好备份) , 快速重启,而且不会有AOF可能潜在的Bug ,留着作为一一个万一 -的手段。

5、性能建议

- 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份- 次就够了,只保留save 9001这条
  规则。
- 如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价一是带来了持续的I0 ,二是AOF rewrite的最后将rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率, AOF重写的基础大小默认值64M太了.可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值。
- 如果不Enable AOF ,仅靠Master-Slave Repllcation实现高可用性也可以,能省掉一大笔I0 ,也减少了rewrite时带来的系统
  波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载
  入较新的那个,微博就是这种架构。

> RDB 和 AOF 对比

| RDB        | AOF    |              |
| ---------- | ------ | ------------ |
| 启动优先级 | 低     | 高           |
| 体积       | 小     | 大           |
| 恢复速度   | 快     | 慢           |
| 数据安全性 | 丢数据 | 根据策略决定 |

> 如何选择使用哪种持久化方式?

一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。

如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。

有很多用户都只使用 AOF 持久化, 但并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。

## 十一、发布和订阅

> 什么是发布和订阅

Redis 发布订阅(pub/sub)是一种==消息通信模式==:发送者(pub)发送消息,订阅者(sub)接收消息。类似于:微信、微博、关注系统!Redis 客户端可以订阅任意数量的频道。
订阅/发布消息图:

第一个:消息发送者, 第二个:频道 第三个:消息订阅者!

![2020092421593358](D:\NULL\学习资源\md笔记\images\redis\2020092421593358.png)

> 下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

![20200924215943362](D:\NULL\学习资源\md笔记\images\redis\20200924215943362.png)

> 当有新消息通过 PUBLISH 命令发送给频道 channel1 时,这个消息就会被发送给订阅它的三个客户端

![](D:\NULL\学习资源\md笔记\images\redis\20200924215943362.png)

>  命令:
>
> 这些命令被广泛用于构建即时通信应用,比如网络聊天室(chatroom)和实时广播、实时提醒等。

| 命令                                   | 描述                               |
| -------------------------------------- | ---------------------------------- |
| PSUBSCRIBE pattern [pattern…]          | 订阅一个或多个符合给定模式的频道。 |
| PUNSUBSCRIBE pattern [pattern…]        | 退订一个或多个符合给定模式的频道。 |
| PUBSUB subcommand [argument[argument]] | 查看订阅与发布系统状态。           |
| PUBLISH channel message                | 向指定频道发布消息                 |
| SUBSCRIBE channel [channel…]           | 订阅给定的一个或多个频道。         |
| UNSUBSCRIBE channel [channel…]         | 退订一个或多个频道                 |

> SUBSCRIBE 
>
> 订阅段

![image-20201008212002263](D:\NULL\学习资源\md笔记\images\redis\image-20201008212002263.png)

> PUBLISH 
>
> 发送端

![image-20201008212128975](D:\NULL\学习资源\md笔记\images\redis\image-20201008212128975.png)

> 原理

Redis是使用C实现的,通过分析Redis源码里的pubsub.c 文件,了解发布和订阅机制的底层实现,籍此加深对Redis的理解。
Redis通过PUBLISH、SUBSCRIBE 和PSUBSCRIBE等命令实现发布和订阅功能。
通过SUBSCRIBE命令订阅某频道后, redis-server 里维护了一个字典,字典的键就是一个个channel ,而字典的值则是一个链
表,链表中保存了所有订阅这个channel的客户端。SUBSCRIBE 命令的关键,就是将客户端添加到给定channel的订阅链表中。
通过PUBLISH命令向订阅者发送消息, redis-server 会使用给定的频道作为键,在它所维护的channel字典中查找记录了订阅这
个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者.
Pub/Sub从字面上理解就是发布( Publish )与订阅( Subscribe ) , 在Redis中,你可以设定对某-个key值进行消息发布及消息订
阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这-功能最明显的用法就是用作实时消息系
统,比如普通的即时聊天,群聊等功能。

>  使用场景:

1、实时消息系统!
2、事实聊天! (频道当做聊天室,将信息回显给所有人即可! )
3、订阅,关注系统都是可以的!

## 十二、Redis主从复制

> 概念

主从复制,是指将一-台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader) ,后者称为从节点
(slave/follower)==数据的复制是单向的,只能由主节点到从节点。==Master以写为主 , Slave以读为主。
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点) ,但一个从节点只能有一一个主节点。

> 主从复制的作用主要包括:

1、数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
2、故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是-种服务的冗余。
3、负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接
     主节点,读Redis数据时应用连接从节点) , 分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大
     大提高Redis服务器的并发量。
4、高可用基石:除了.上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

> 一般来说 ,要将Redis运用于工程项目中,只使用一台Redis是万万不能的,原因如下:

1、从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大;
2、从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G ,也不能将所有内存用作Redis存储内存,
    一般来说 ==,单台Redis最大使用内存不应该超过20G==。
    电商网站上的商品,一般都是一 -次 上传,无数次浏览的,说专业点也就是"多读少写"。
3、对于这种场景,我们可以使如下这种架构:

![image-20201009082159012](D:\NULL\学习资源\md笔记\images\redis\image-20201009082159012.png)

主从复制,读写分离! 80%的情况下都是在进行读操作!减缓服务器的压力!架构中经常使用! 一主二从!

> 环境配置
>
> 只配置从库,不配置主库

![image-20201009082917055](D:\NULL\学习资源\md笔记\images\redis\image-20201009082917055.png)

> 集群配置
>
> ​    复制3个配置文件,然后修改对应的信息

1、端口
2、pid 名字
3、log文件名字
4、dump.rdb 名字

> 配置重新启动后,查询进程

![image-20201009094730803](D:\NULL\学习资源\md笔记\images\redis\image-20201009094730803.png)

### 一主二从

==默认情况下,每台redis服务器都是主机==!![image-20201009152014572](D:\NULL\学习资源\md笔记\images\redis\image-20201009152014572.png)我们一般情况下只用配置从机就好了! 

> info replication

![image-20201009095626262](D:\NULL\学习资源\md笔记\images\redis\image-20201009095626262.png)

> 配置从机
>
> ==我们一般情况下只用配置从机就好了!==
>
> SLAVEOF 127.0.0.1 6379 (主机ip地址)

![image-20201009143735265](D:\NULL\学习资源\md笔记\images\redis\image-20201009143735265.png)

> 主机信息

![image-20201009144443949](D:\NULL\学习资源\md笔记\images\redis\image-20201009144443949.png)

> 真实的主从配置一般是在配置文件中配置,这样配置是永久的!
>
> 上边使用的是命令,是暂时的!
>
> 在里面添加主机ip`SLAVEOF 127.0.0.1 6379 (主机ip地址)`

![image-20201009145917261](D:\NULL\学习资源\md笔记\images\redis\image-20201009145917261.png)

> 细节配置

- 主机可以写,从机不能写只能读!主机中的所有信息和数据,都会自动被从机保存!
- 主机断开连接,从机依旧连接到主机的,但是没有写操作,这个时候,主机如果回来了,从机依旧可以直接获取到主机写的信息!
- 如果是使用命令行,来配置的主从,这个时候如果重启了,就会变回主机!只要变为从机,立马就会从主机中获取值! .

> 复制原理

Slave启动成功连接到master后会发送一个sync同步命令
Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后, master将传送
整个数据文件到slave ,并完成一次完全同步。
全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制:Master继续将新的所有收集到的修改命令依次传给slave ,完成同步
但是只要是重新连接master , 一次完全同步(全量复制)将被自动执行!我们的数据一定可以在从机中看到 !

> 层层链路

==上一个master链接下一个slaveof==

![image-20201009151811060](D:\NULL\学习资源\md笔记\images\redis\image-20201009151811060.png)

> 主机断掉,手动改变从机为主机
>
> ==SLAVEOF no one==

![](D:\NULL\学习资源\md笔记\images\redis\image-20201009152255318.png)

## 十三、哨兵模式(自动选举老大的模式)

> 概述

- 主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式 ,更多时候,我们优先考虑哨兵模式。Redis从2.8开始正式提供了Sentinel (哨兵)架构来解决这个问题。
- 谋朝篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
- 哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。==其原理是哨兵通
  过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。==

![哨兵](D:\NULL\学习资源\md笔记\images\redis\哨兵-1602229090055.jpg)

这里的哨兵有两个作用

- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到master宕机,会自动将slave切换成master ,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它
  们切换主机。
- 然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监
  控,这样就形成了多哨兵模式。`redis一主两从三哨兵模式搭建模式`

![多哨兵png](D:\NULL\学习资源\md笔记\images\redis\多哨兵png-1602229101048.png)

假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为**主观下线**。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为**客观下线**。

> Redis配置哨兵模式

```bash
# 禁止保护模式
protected-mode no
# 配置监听的主服务器,这里sentinel monitor代表监控,mymaster代表服务器的名称,可以自定义,
#192.168.11.128代表监控的主服务器,6379代表端口,
#2代表只有两个或两个以上的哨兵认为主服务器不可用的时候,才会进行failover操作。
sentinel monitor mymaster 192.168.11.128 6379 2
# sentinel author-pass定义服务的密码,mymaster是服务名称,123456是Redis服务器密码
# sentinel auth-pass
sentinel auth-pass mymaster 123456

```

![2020091223104574](D:\NULL\学习资源\md笔记\images\redis\2020091223104574.png)

> 启动哨兵

![image-20201009155557755](D:\NULL\学习资源\md笔记\images\redis\image-20201009155557755.png)

>6379殆机
>
>6381自动成为主机

![image-20201009160148140](D:\NULL\学习资源\md笔记\images\redis\image-20201009160148140.png)

![image-20201009160216113](D:\NULL\学习资源\md笔记\images\redis\image-20201009160216113.png)

> 如果主机此时回来了,只能归并到新的主机下,当做从机,这就是哨兵模式的规则!

优点:
1.哨兵集群,基于主从复制模式,所有主从复制的优点,它都有
2.主从可以切换,故障可以转移,系统的可用性更好
3.哨兵模式是主从模式的升级,手动到自动,更加健壮
缺点:
1.Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
2.实现哨兵模式的配置其实是很麻烦的,里面有很多配置项

==完整的哨兵模式配置文件 sentinel.conf==

```bash
# Example sentinel.conf
 
# 哨兵sentinel实例运行的端口 默认26379
port 26379
 
# 哨兵sentinel的工作目录
dir /tmp
 
# 哨兵sentinel监控的redis主节点的 ip port 
# master-name  可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor
sentinel monitor mymaster 127.0.0.1 6379 
 
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
 
 
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds
sentinel down-after-milliseconds mymaster 30000
 
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
# 这个数字越小,完成failover所需的时间就越长,
# 但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
# 可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs
sentinel parallel-syncs mymaster 1
 
 
 
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面: 
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。  
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout
sentinel failover-timeout mymaster 180000
 
# SCRIPTS EXECUTION
 
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
 
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
#这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
#一个是事件的类型,
#一个是事件的描述。
#如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script
  sentinel notification-script mymaster /var/redis/notify.sh
 
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
#
# 目前总是“failover”,
# 是“leader”或者“observer”中的一个。 
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

```

> Java中使用哨兵模式

```java
/**
 * 测试Redis哨兵模式
 * @author liu
 */
public class TestSentinels {
    @SuppressWarnings("resource")
    @Test
    public void testSentinel() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(10);
        jedisPoolConfig.setMaxIdle(5);
        jedisPoolConfig.setMinIdle(5);
        // 哨兵信息
        Set sentinels = new HashSet<>(Arrays.asList("192.168.11.128:26379",
                "192.168.11.129:26379","192.168.11.130:26379"));
        // 创建连接池
        JedisSentinelPool pool = new JedisSentinelPool("mymaster", sentinels,jedisPoolConfig,"123456");
        // 获取客户端
        Jedis jedis = pool.getResource();
        // 执行两个命令
        jedis.set("mykey", "myvalue");
        String value = jedis.get("mykey");
        System.out.println(value);
    }
}

```

> spring中使用

```xml
   
       
       
       
       
       
       
   

    
   
       
       
       
   

    
   
   
    
   
   
    
   
       
       
       
       
   

    
   
   
       
       
           
               
           

       

       
       
           
               
                   
                   
               

               
                   
                   
               

               
                   
                   
               

           

       

   

```
## 十三、redis缓存穿透与雪崩

### 缓存穿透(查不到)

> 概念

缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中(秒杀!),于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。

![image-20201009163545508](D:\NULL\学习资源\md笔记\images\redis\image-20201009163545508.png)

> 解决方案

**布隆过滤器**

布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;

![2020061622522144](D:\NULL\学习资源\md笔记\images\redis\2020061622522144.png)

**缓存空对象**

当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;

![20200616225243110](D:\NULL\学习资源\md笔记\images\redis\20200616225243110.png)

但是这种方法会存在两个问题:
1、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多
的空值的键;
2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于
需要保持一致性的业务会有影响。

### 缓存击穿(量太大,缓存过期)

> 概述

这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。

> 解决方案

**设置热点数据永不过期**
从缓存层面来看,没有设置过期时间,所以不会出现热点 key 过期后产生的问题。

**加互斥锁**
分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。

![202006162254369](D:\NULL\学习资源\md笔记\images\redis\202006162254369.png)

### 缓存雪崩

> 概述

缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis 宕机!
产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。

![20200616225521165](D:\NULL\学习资源\md笔记\images\redis\20200616225521165.png)

其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。

> 解决方案

**redis高可用**
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。(异地多活!)

**限流降级**
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

**数据预热**
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

## 十四、其他

### Redis 内置集群cluster

你可能感兴趣的:(nosql,数据库,redis)