缓存穿透:查询一个不存在的数据,mysql查询不到数据也不会写入缓存,就会导致每次请求都会去查询数据库。
解决方案一:缓存空数据,查询数据为空的时候,仍然把空结果返回。
优点:简单
缺点:消耗内存,可能会发生数据不一致问题(加入数据库中存入数据,但是Redis中缓存依然是null)
解决方案二:使用布隆过滤器,查询布隆过滤器,存在查Redis。不存在直接返回。
那么什么是布隆过滤器?
(百度百科)它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
布隆过滤器通俗的来讲就是查看一个元素是否存在于集合中,这个原理和HashMap存储的原理有那么一点点类似
大概示例如下图:
但是他会存在一个误判率的问题,如果id为3经过三次hash之后得到值都为1但是实际上id为3的数据其实并不存在,但是他也会默认id为3的数据是存在的,具体可以看下面的图,图片更为直观一点:
误判率:误判率也是有一定的规律的,一般数组越小误判率越大,数据越大误判率越小,但是这个在项目开发的过程中是可以控制的,一般是控制在0.05
布隆过滤器优点:内存占用较少,没有多余的key
布隆过滤器缺点:实现复杂,存在误判。
缓存击穿:给某一个热点的key设置了过期时间,当key过期的时候,恰好这个时间点这个key有大量的请求过来,这些并发请求可能会瞬间将DB压垮。
通俗的解释就是为了提高数据库的性能,我们通常在一些情况下回去使用Redis缓存,比如一个热门的新闻文章等等数据,那么通常会给这个热点数据设置一个过期的时间,那么刚好过期时间到的时候,有大量并发请求过来,就会直接穿过Redis进去DB中,如果超过了DB的最大访问压力是完全可以将DB直接压垮的。
解决缓存击穿的两种方式:
互斥锁:(强一致性的效果,但是性能比较差)
原理:当两个线程同时去查询数据的时候,当线程1查询缓存未命中(缓存过期),获取互斥锁,查询数据库重建缓存数据,如果这个时候线程2查询缓存未命中(缓存获取),那么就会去获取互斥锁,如果获取互斥锁失败,那么就进入休眠,休眠一会儿再进行重试,等待线程1写入缓存,释放锁,线程2获取缓存。
遇到和钱挂钩的业务这里一般采用互斥锁的方式
逻辑过期:(高可用,性能优,但是不能保证数据的一致性)
原理:同时有两个线程,线程1查询缓存时发现逻辑时间过期,获取互斥锁并且开启线程2,随后线程1直接返回已经过期的数据,线程2查询数据库之后重建缓存,写入缓存重置逻辑,过期时间;在线程2执行的期间,如果线程3查询缓存发现缓存时间过期,那么他会去获取缓存锁,如果获取缓存锁失败,他直接返回过期数据,在线程2释放锁之后,如果线程4恰好获取缓存命中,缓存没有过期。
缓存雪崩:是指当同一时间有大量的缓存key同时失效或者Redis宕机的时候,如果有大量的请求直接到达数据库,会给数据库带来极大的压力。
解决的方案:
针对大量的缓存key同时过期的问题:
针对Rredis宕机的问题:
降级限流策略可以作为缓存穿透,缓存击穿,缓存雪崩的保底层略。
当系统中同时使用了Redis和Mysql的时候,如何保证数据的一致性呢?
尤其是针对具有高并发需求的系统,在大量并发来临的时候,难免会出现数据不一致的问题
双写一致性:当修改了数据库的数据之后同时更新缓存,缓存和数据库的数据要保持一致性。
问题出现的分析:
那么在高并发的情况下其实无论是先清除缓存,或者先删除删除数据库都会出现问题:
理想情况下:线程1先删除缓存,然后更新数据库,线程2查询缓存,未命中,查询数据库,写入缓存。
当然如果都这样的话那么就没必要说了不是,那么接下来看看异常情况
异常情况下:线程1删除缓存,那么正好这个时候线程2来了,查询缓存未命中,然后去查询数据库,写入缓存10,然后线程1这个时候正好将数据库数据更新到20,那么就会出现一个问题,缓存为10,数据库为20。
如下图:
理想情况下:线程2更新了数据库,随后线程2删除缓存,线程1查询缓存,未命中,查询数据库,写入缓存。
还是那句话,想象很美好,现实会让你哭泣,哈哈。
异常情况下:当线程1查询数据的时候缓存中数据过期了,表示缓存中没有数据,线程1查询缓存,未命中,查询数据库10.这个时候线程2更新数据库,然后删除缓存,这个时候切换回线程1,线程1直接将从数据库中查询到的10写入了缓存中。
如下图所示
上面这些问题基本上是在高并发情况下,数据一致性问题中经常会出现的情况,那么就会导致很严重的数据不一致性的问题,因此这个问题是必须解决的,那么最常用的方法就是
延时双删
的策略,但是延时双删只是极大的控制了数据不一致性的风险,同时也由于延迟时间的不确定性,他也会产生一定的脏数据,做不到绝对的强一致性。
那么如何才能做到强一致性呢?
分布式锁的方式:
通俗的讲就是加锁,只有一个线程能去操作他,操作完成之后释放锁,让另一个线程去操作他。
如下图所示:
写入缓存的数据一般是读多写少,读少写多的话不建议存入缓存。
读写锁的控制:
读写锁的实现需要基于以下两种锁来进行实现:
共享锁:也叫读锁ReadLock,加锁之后其他线程可以共享读的操作,不能进行写的操作。
排它锁:也叫独占锁WriteLock,加锁之后,阻塞其他线程的读写操作。
相对与分布式锁的话,这种读写锁的方式效率会更加高一点。
以上是强一致性的解决方案,主要是强调的数据的一致性
异步通知保证数据一致性的方案:
原理使用MQ进行消息的发布和订阅的方式来保证数据的最终一致性:
Redis怎么可以实现数据持久化,这个肯定是有的,大佬对这个中间件的设计的非常的全面的,对于我们这些菜鸟来说,哈哈。
下面是实现数据持久化的两种方式:
首先先说RDB的方式,他是Redis默认的数据备份的的方式,默认是开启的
save
bgsave
bgsave
在开始的时候会去fork主进程得到一个子进程,然后子进程是可以共享主进程的内存数据的,完成fork后读取内存数据写入RDB文件中,fork采用的是copy-on-write技术:AOF在Redis中是默认关闭的,需要修改redis.conf来配置文件开启AOF
# 是否开启AOF功能 默认no
appendonly yes
# AOF文件名称
appendfilename "appendonly.aof"
AOF的命令记录的频率可以通过redis.conf文件来配置:
# 表示每执行一次写命令,立即记录到aof文件
appendfsync always
# 写命令执行完先放入AOF缓存区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案
appendfsync everysec
# 写命令执行完毕之后先放入缓冲区,由操作系统决定将缓冲区内容写回磁盘。
appendfsync no
配置项 | 刷盘同步 | 优点 | 缺点 |
---|---|---|---|
Always | 同步刷盘 | 可靠性高,几乎不丢失数据 | 性能影响大 |
everysec | 每秒刷盘 | 性能适中 | 最多丢失1秒数据 |
no | 操作系统控制 | 性能最好 | 可靠性差,可能丢失大量的数据 |
因为AOF是记录命令,所以AOF文件回避RDB文件大的很多,而且AOF会记录同一个key的多次写操作,但是只有最后一次写操作才有意义,通过执行bgrewriteof,可以让AOF文件执行重写功能,用最少得命令达到相同的效果。
Redis也会触发阈值自动去重写AOF文件,阈值也可以在redis.conf中配置:
# AOF文件比删词文件 增多超过百分比触发重写
auto-aof-rewrite-percentage 100
# AOF文件体积最小多大以上才会触发重写
auto-aof-rewrite-min-size 64mb
RDB和AOF对比
RDB和AOF各有自己的优缺点,如果数据安全性要求高,在实际开发中往往会结合两者来使用。
加入redis中的key过期之后会立即删除吗?
redis对数据设置数据的有效时间,数据过期之后,就需要将数据从内存中删除掉,可以按照不同的规则进行删除,这种删除方式称为删除策略。
惰性删除策略
定义:设置key过期了之后,我们不去管他,当需要key的时候,我们再检查key是否过期,如果过期,我们就删除,反之返回key
优点:对cpu友好,只会使用可以时才会进行定期的检查。对于很多用不到的key不会浪费时间进行定期检查
缺点:对内存不友好,如果key已经过期,但是一直没有使用,那么该key就会一直存在内存中,内存永远不会释放。
定期删除策略:
定义:每隔一段时间我们就对key进行检查,删除过期的key(从一定数量的数据库中取出一定数量的随机key进行检查,并删除其中过期的key)
定期删除的两种模式:
redis缓存过期删除策略,惰性删除+定时删除两种策略配合使用
如果缓存过多的话,redis内存是有限的,内存是完全有可能会占满的
那么这里就需要用到redis的数据淘汰策略:
Redis中支持8
中不同的策略来删除key:
LRU:最近最少使用,用当前时间减去最后一次访问的时间,这个值越大则淘汰的优先级越高
LFU:最少频率使用,会统计每隔key的访问频率,值越小淘汰优先级越高。
这里有一个常见问题:
数据库中有1000万数据,redis只能缓存20w条数据,如何保证redis中的数据都是热点数据?
使用allkeys-lru(挑选最近最少使用的策略淘汰,留下来的都是经常访问的热点数据)
redis的内存用完会发生什么?
默认策略的话noevication不删除任何数据,内存不足会直接报错。
使用场景:集群情况下定时任务,抢单,幂等性问题
Redis分布式锁细节:
setnx
命令,setnx
命令是set if not exists(如果不存在则set)的缩写redisson实现分布式锁的执行流程:
redisson实现的分布式锁是可重入的,但是会出现主从数据不一致的问题,两个线程同时持有一把锁,那么锁就失去了互斥行的特性。
这里可以使用红锁RedLock
,不能在一个redis实例上创建锁(n/2 + 1),避免redis实例上加锁
但是:
红锁部署的成本高,维护的成本高,运维成本高
高并发的情况下性能很差,官方也不建议使用红锁解决主从不一致问题
Redis是AP思想,保证是高可用性
如果保证强一致性建议使用CP思想的Zookeeper
Redis集群中主从数据同步的流程是什么?
主从复制:单节点的redis的并发能力是有上限的,要进一步提高redis的并发能力,就需要搭建主从集群,实现读写分离。
Replication Id
:简称replid,是数据集的标记,id一致则说明同一个数据集,每个master都有唯一的replid,slave节点则会集成master节点的replid
offset
:偏移量,随着记录在repl_backlog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的oddset。如果slave的offset小于master的offset,说明slave数据落后于mater,需要更新。
介绍一下redis的主从同步
单节点的redis并发能力有限,要进一步提高redis的并发能力,就需要搭建主从集群,实现读写分离,一般都是一主多从,主节点负责写入数据,从节点负责读取数据。
能说一下,主从数据同步的流程:
全量同步:
增量同步:
Redis提供了哨兵(Sentinel)机制实现了主从集群的自定故障恢复,哨兵的作用如下:
Redis集群(哨兵模式)脑裂问题:
一个正常的redis哨兵模式,如果主节点与从节点网络出现问题,那么sentinel可能访问不到主节点,那么就可能会从从节点选择一个节点作为主节点,可是这个时候原来的主节点并没有挂,RedisClient还在继续写入数据,这样就出现了脑裂问题,当网络恢复了之后,原来的master会被强制降为slave节点,然后从新的master节点中同步数据,删除本节点的数据,这样的话,RedisClient客户端写入的数据就会丢失。
解决脑裂问题的方案:
脑裂问题无法彻底解决,但是可以通过以下两个参数进行配合来解决:
min-replicas-to-write 1
表示最少得slave节点为1个min-replicas-max-lag 5
表示数据复制和同步的延迟不能超过5秒如何保证Redis的高并发可用?
哨兵模式:实现主从集群的自动故障恢复(监控,自动故障恢复,通知)
Redis集群的脑裂问题,如何解决?
集群脑裂:是由于主节点和从节点和sentinel处于不同的网络分区,使得sentinel没有能够心跳感知到主节点,所以通过选举的方式提升一个新节点作为主节点,这样就存在了两个master,就像大脑分裂一样,这样就导致了客户端还在往主节点写数据,新的节点无法同步数据,当网络恢复之后,sentinel会将老的主节点降为从节点,然后再从新的master中读取数据,导致数据丢失。
解决:我们可以通过修改redis的配置,可以设置最少节点数量和缩短主从数据同步的延迟时间,达不到要求就拒绝请求,就可以避免大量的数据丢失。
主从和哨兵可以解决高并发,高并发读的问题,但是依然有两个问题没有解决:
海量数据存储的问题
高并发写的问题
使用分片集群的方式可以解决上述的问题,分片集群的特征:
集群中有多个master,每个master保存不同的数据
每个master都可以有多个slave节点
master之间通过ping监测彼此的健康状态
客户端请求可以访问集群的任意节点,最终都会转发到正确的节点上
Redis分片集群-数据读写
Redis分片集群中引入哈希槽的概念,Redis集群中有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置那个槽,集群的每个节点负责一部分的哈希槽
Redis分片集群中有什么作用
集群中有多个master,每个master保存不同的数据
每个master都可以有多个slave节点
master之间通过ping监测彼此的健康状态
客户端请求可以访问集群的任意节点,最终都会被转发到正确的节点
Redis分片集群中数据是怎么储存和读取的?
Redis是单线程的,但是为什么还是那么快?
能解释下I/O多路复用模型吗?
Redis是纯内存操作,执行速度快,他的性能瓶颈是网络延迟而不是执行速度,I/O多路复用模型主要实现了高效的网络请求
笔记是对黑马课程中的知识进行的个人总结,图片借鉴了课程视频中的资料,感谢黑马程序员的开源精神,哈哈,如有问题联系我删除!