Redis高级知识

​发布式订阅,集群,读写分离,缓存。。。​


文章目录


一、Redis发布式订阅

1.1 简介

Redis发布式订阅(pb/sub)是一种消息通信机制,发送者(pub)发送消息,接收者(sub)接收消息。
​​Redis客户端可以订阅任意数量的消息

Redis发布订阅(pub/sub)是一种消息通信模式:下图展示了频道 channel1,以及订阅的这个频道的三个客户端--client2、client5和client1之间的关系

Redis04_数据


​当新的消息通过PUBLISH命令发送消息给频道​​​ ​channel1​​ ​​的时候,这个消息就会被发送给订阅他的三个客户端。​​见下图:`

Redis04_redis_02


配置订阅和发布

1.2 常用命令

订阅频道:

  1. SUBSCRIBE channel1 [channel ...]:
  2. PSUBSCRIBE pattern [pattern ...]:

发布频道:

  1. PUBLISH channel1 message:

退订频道:

  1. UNSUBCRIBE [channel [channel1 ...]]:
  2. PUNSUBSCRIBE [pattern [pattern ...]]:

1.2.1 应用场景

这一功能最明显的用法就是构建实时的消息系统,比如普通的即时聊天,群聊等功能
1、在一个博客网站中,有100个粉丝订阅了你,当你发布新文章的时候,就可以推送消息到你的粉丝们了
2、微信公众号推送模式

使用两个客户端进行订阅CCTV5、CCTV6和CCTV5频道

Redis04_数据_03


Redis04_缓存_04


当一个用户往CCTV5频道发送了一条消息的时候

Redis04_数据_05


此时订阅了CCTV5频道的两个客户端即可接收到该用户发送的消息

Redis04_redis_06


Redis04_数据_07

`

二、Redis多数据库配置

Redis下,数据库是有一个整数的数据索引来标识的。而不是通过数据库的名称来完成,默认情况下,一个客户端连接到数据库0
Redise配置文件下面的参数来控制数据库总数
database 16 |(// 从0开始 1 2 3 4 … 15)
select 数据库 // 数据库的切换
移动数据库中的数据(将当前的key移动到另一个库)

move key名称 数据库

数据库清空:

flushdb :清空当前数据库中的所有的KEY
flushall :

三、Redis事务

Redis事务可以一次执行多条命令(按照顺序地串行化执行,执行中不会被其他命令所插入,不许加塞)

3.1 简介


Redis事务可以一次性执行多条命令(允许再一次的单独的执行步骤中执行一组命令),并且带有以下两个重要的保证:

1批量操作在发送EXEC命令前被放入缓存中。
收到EXEC命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行
在事务执行过程中,其他客户端提交的命令请求不会被插入到其他的事务命令序列中

  1. redis会将一个事务中的所有命令序列化,然后按顺序执行
  2. 执行中不会被其他命令插入,不允许出现加塞行为

3.2 常用命令

  • DISCARD:
  • EXEC:
  • MULTI:
  • UNWATCH:
  • WATCH key [key ...]:

3.3 示例1:MULT & EXEC

DISCARD放弃队列运行

192.168.188.129:6379> MULTI
OK
192.168.188.129:6379> set num 10
QUEUED
192.168.188.129:6379> get num
QUEUED
192.168.188.129:6379> incr num
QUEUED
192.168.188.129:6379> EXEC
1) OK
2) "10"
3) (integer) 11
192.168.188.129:6379>

取消事务

192.168.188.129:6379> MULTI
OK
192.168.188.129:6379> SET num1 1
QUEUED
192.168.188.129:6379> set num2 2
QUEUED
192.168.188.129:6379> get num1
QUEUED
192.168.188.129:6379> get num2
QUEUED
192.168.188.129:6379> DISCARD
OK
192.168.188.129:6379> EXEC
(error) ERR EXEC without MULTI
192.168.188.129:6379> keys *
1) "userName:1001"
2) "userName:1003"
3) "num"
4) "User:1001"
5) "java"
6) "User"
192.168.188.129:6379>

3.4 事务的错误处理

事务的错误处理;
​​​如果执行的某个命令爆粗了错误,则指对该出现错误的命令不会被执行,也不会别回滚。​​ 异常处理

192.168.188.129:6379> MULTI
OK
192.168.188.129:6379> set num1 aagjs
QUEUED
192.168.188.129:6379> get num1
QUEUED
192.168.188.129:6379> INCR num1
QUEUED
192.168.188.129:6379> EXEC
1) OK
2) "aagjs"
3) (error) ERR value is not an integer or out of range
192.168.188.129:6379>

3.5 watch命令

需求:转账的操作
小明:100元
小李:3元
小明向小李转账100元,如果小明的余额不足100元,则转账失败,否则转账成功。

注意:在Redis中,如果我们这时候小明像小李转账100元,但是此之前别人向小明借走了50元,那么此时小明在想转100元给小李,显然是不合逻辑的,但是Redis中却可以,所以会出现-50的现象。

解决问题–watch命令

在事务执行之前,该事务所操作的对象如果被改动,则事务块内的命令将自动被取消执行。

示例:​​

现在完成转账操作—> 小明转账小李100元,但是在此之前有另外一个人提前转走了小明19元,那么此时通过监视小明,来达到事物的隔离,保证事务操作数据的一致性和完整性

两台服务器代表两个业务流程

Redis04_数据_08


小明:键名设置为a

小李:键名设置成b

执行业务转账

192.168.188.129:6379> set a 100
OK
192.168.188.129:6379> set b 3
OK
192.168.188.129:6379> WATCH a
OK
192.168.188.129:6379> MULTI
OK
192.168.188.129:6379> GET a
QUEUED
192.168.188.129:6379> DECRBY a 100
QUEUED
192.168.188.129:6379> INCRBY b 100
QUEUED
192.168.188.129:6379> get a
QUEUED
192.168.188.129:6379>

事务中断

192.168.188.129:6379> get a 
"100"
192.168.188.129:6379> get b
"3"
192.168.188.129:6379> DECRBY a 19
(integer) 81
192.168.188.129:6379> get a
"81"
192.168.188.129:6379>

执行转账操作

192.168.188.129:6379> EXEC
(nil)
192.168.188.129:6379>

发现此时Redis执行后为nil,表示该事务未执行,这就是Redis的事务的隔离级别

Redis04_缓存_09

四、Redis的数据淘汰策略redis.conf

redis官方给出的警告,当内存不足的时候,redis会根据配置的缓存策略淘汰部分的key,以保证写入成功,当无法淘汰策略时或者没有找个合适的淘汰key的时候,redis直接返回out of memory错误

最大缓存配置

在Redis中,允许用户设置的最大内存大小是

maxmemory 512G

在篇一我有写过

Redis04_缓存_10

redis提供的5种数据淘汰策略:

  1. volatile-lru:
  2. volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。
  3. volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。
  4. volatile-lfu:从已设置过期时间的数据集挑选使用频率最低的数据淘汰。
  5. allkeys-lru:从数据集中挑选最近最少使用的数据淘汰
  6. allkeys-lfu:从数据集中挑选使用频率最低的数据淘汰。
  7. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  8. no-enviction(驱逐):禁止驱逐数据,这也是默认策略。意思是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失。

八种大体上可以分为4中,lru、lfu、random、ttl。

五、Redis持久化

什么是Redis持久化?
持久化就是把内存的数据写到磁盘上,防止服务宕机了内存数据丢失。
Redis提供了两种持久化方式RDB(默认)、AOF

简介

数据存放于:
​​内存:​​ 高效、断电(宕机)、内存数据会丢失
​​硬盘:

redis持久化存储支持两种方式,RDB|AOF。 RDB是一种一定时间内存储文件,AOF是一种每秒都会存储历时命令
redis是支持持久化的内存数据库,也就是说Redis需要经常将内存中的数据同步到硬盘中保证数据的一致性和持久性

5.1 RDB

rdb是Redis DataBase 缩写

功能是核心函数rdbSave(生成RDB文件和rdbLoad文件加载内存),两个函数

Redis04_redis_11


RDB:是Reids中的默认的持久化机制

快照是默认的持久化方式,这种方式就是在内存中数据已快照的方式写入到二进制的文件中,默认的文件名dump.rdb

优点: 快照保存的数据块,还原数据快,适用于灾难备份

缺点: 小内存机器不适合使用,RDB符合要求的就会照快照

快照条件

  1. 服务器正常关闭时 shutdown
  2. key满足一定条件时,会进行快照

​vim /opt/redis-5.0.14/redis.conf 搜索save 执行/save​

Redis04_缓存_12

5.2 AOF

由于快照的方式是在间隔一段时间进行一次的,所以如果是redis意外的宕机,就会丢失掉最后一次快照后的所有的修改数据,如果应用要求不能丢失一个数据,可以采用aof的持久化方式

Append-only file :aof 比快照方式有更好的持久化性,是由于AOF持久化方式时,redis会将每一个收到的写入命令通过write的方式追加到appendonly.aof的文件中,当redis重启后会通过重新执行文件中保存的写命令在内存中重新构建整个数据库的内容。

Redis04_缓存_13


​​缺点:aof文件会随着持久化操作,文件会越来越大,而且会造成许多冗余数据

​​优点:不会造成服务器宕机而使数据丢失

六、缓存穿透、缓存击穿、缓存雪崩问题

6.1 Key的过期淘汰机制

Redis可以对存储在Redis中的缓存数据设置过期时间,比如我们获取的短信验证码一般十分钟过期, 我们这时候
就需要在验证码存进Redis时添加一个key的过期时间,但是这里有一个需要格外注意的问题就是:并非key过期时
间到了就一定会被Redis给删除。

6.1.1 定期删除

Redis默认是每隔100ms就随机抽取一些设置 了过期时间的Key,检查其是否过期,如果过期就删除。为什么是随机抽取而不是检查所有key?因为你如果设置的key成千上万,每100毫秒都将所有存在的key检查-遍,会给CPU
带来比较大的压力。

6.1.2 惰性删除

定期删除由于是随机抽取可能会导致很多过期Key到了过期时间并没有被删除。所以用户在从缓存获取数据的候,redis会检查这个key是否过期了,如果过期就删除这个key。这时候就会在查询的时候将过期key从缓存中清除

6.1.3 内存淘汰机制

仅仅使用定期删除+惰性删除机制还是会留下一个严重的隐患:如果定期删除留下了很多已经过期的key,而且用户长时间都没有使用过这些过期key,导致过期key无法被惰性删除,从而导致过期key- -直堆积在内存里,最终造成Redis内存块被消耗殆尽。那这个问题如何解决呢?这个时候Redis内存淘汰机制应运而生了。Redis内存淘汰机制提供了6种数据淘汰策略:

6.2 缓存击穿🔥🔥🔥

首先我们来看下请求是如何取到数据的:当接收到用户请求,首先先尝试从Redis缓存中获取到数据,如果缓存
中能取到数据则直接返回结果,当缓存中不存在数据时从DB获取数据,如果数据库成功取到数据,则更新Redis,
然后返回数据

Redis04_redis_14

**定义:**高并发的情况下,某个热门的key突然过期导致大量请求在Redis中未找到缓存数据, 进而全部去访问DB请求数据,引起DB压力瞬间增大。

**解决方案:**缓存击穿的情况下一般不容易造成DB的宕机,只是会造成对DB的周期性压力。对缓存击穿的解决方
案-般可以这样:

  • Redis中的数据不设置过期时间,然后在缓存的对象上添加一个属性标识过期时间,每次获取到数据时,校验对象中的过期时间属性,如果数据即将过期,则异步发起一个线程主动更新缓存中的数据。但是这种方案可能导致有些请求会拿到过期的值,就得看业务能否可以接受。
  • 如果要求数据必须是新数据,则最好的方案则为热点数据设置为永不过期,然后加一个互斥锁保证缓存的单线
    程写。

6.3 缓存穿透🔥🔥🔥

定义: 缓存穿透是指查询缓存和DB中都不存在的数据。比如通过id查询商品信息,id一般大于0,攻击者会故意传id为-1去查询,由于缓存是不命中则从DB中获取数据,这将会导致每次缓存都不命中数据导致每个请求都访问DB,造成缓存穿透。

解决方案:

  • 利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试
  • 采用异步更新策略,无论key是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,异
    步去一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。
  • 提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的key。 迅
    速判断出,请求所携带的Key是否合法有效。如果不合法,则直接返回。
  • 如果从数据库查询的对象为空,也放入缓存,只是设定的缓存过期时间较短,比如设置为60秒。

6.4 缓存雪崩🔥🔥🔥

定义:缓存中如果大量缓存在一段时间内集中过期了,这时候会发生大量的缓存击穿现象,所有的请求都落在
了DB上,由于查询数据量巨大,引起DB压力过大甚至导致DB宕机

解决方案:

  • 分级设置失效时间,比如说在第一阶梯的实现时间统一设置,在第二阶梯的失效时间设置到第一阶梯之后一段时间
  • 尽量平均设置失效时间,将热点数据均匀分布在不同的失效时间
  • 使用互斥锁,但是吞吐量明显降低
  • 设置热点数据永不过期
  • 双缓存。我们有两个缓存,缓存A和缓存B。缓存A的失效时间为20min,缓存B不设置失效时间。自己做缓存预热操作,然后细分到以下几个小点
  • 从缓存A读取数据库,有则直接返回
  • A没有数据,直接从B读数据,直接返回,并且异步起动一个更新线程
  • 更新线程同时更新缓存A和缓存B