杨彪网站建设,wordpress菜单突然拉不出,建e网室内设计网怎么用,wordpress alt 空Redis 最常用的一个场景就是作为缓存#xff0c;本文主要探讨Redis作为缓存#xff0c;在实践中可能会有哪些问题#xff1f;比如一致性、击穿、穿透、雪崩、污染等。
为什么要理解Redis缓存问题
在高并发业务场景下#xff0c;数据库大多数情况都是用户并发访问最薄弱的…Redis 最常用的一个场景就是作为缓存本文主要探讨Redis作为缓存在实践中可能会有哪些问题比如一致性、击穿、穿透、雪崩、污染等。
为什么要理解Redis缓存问题
在高并发业务场景下数据库大多数情况都是用户并发访问最薄弱的环节。所以使用Redis做一个缓冲操作让请求先访问到Redis而不是直接访问数据库。这样可以大大缓解数据库的压力。
当使用缓存库时必须要考虑如下问题
缓存穿透
缓存击穿
缓存雪崩
缓存污染或者满了
缓存和数据库一致性
缓存穿透
问题来源
缓存穿透是指缓存和数据库中都没有的数据而用户不断发起请求。由于缓存是不命中时被动写的并且出于容错考虑如果从存储层查不到数据则不写入缓存这将导致这个不存在的数据每次请求都要到存储层去查询失去了缓存的意义。
在流量大时可能DB就挂掉了要是有人利用不存在的key频繁攻击我们的应用这就是漏洞。
如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者攻击会导致数据库压力过大。
解决方案
接口层增加校验如用户鉴权校验id做基础校验id0的直接拦截
从缓存取不到的数据在数据库中也没有取到这时也可以将key-value对写为key-null缓存有效时间可以设置短点如30秒设置太长会导致正常情况也没法使用。这样可以防止攻击用户反复用同一个id暴力攻击
布隆过滤器。bloomfilter就类似于一个hash set用于快速判某个元素是否存在于集合中其典型的应用场景就是快速判断一个key是否存在于某容器不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小。
缓存击穿
问题来源
缓存击穿是指缓存中没有但数据库中有的数据一般是缓存时间到期这时由于并发用户特别多同时读缓存没读到数据又同时去数据库去取数据引起数据库压力瞬间增大造成过大压力。
解决方案
1、设置热点数据永远不过期。
2、接口限流与熔断降级。重要的接口一定要做好限流策略防止用户恶意刷接口同时要降级准备当接口中的某些 服务 不可用时候进行熔断失败快速返回机制。
3、加互斥锁
缓存雪崩
问题来源
缓存雪崩是指缓存中数据大批量到过期时间而查询数据量巨大引起数据库压力过大甚至down机。和缓存击穿不同的是缓存击穿指并发查同一条数据缓存雪崩是不同数据都过期了很多数据都查不到从而查数据库。
解决方案
缓存数据的过期时间设置随机防止同一时间大量数据过期现象发生。
如果缓存数据库是分布式部署将热点数据均匀分布在不同的缓存数据库中。
设置热点数据永远不过期。
缓存污染或满了
缓存污染问题说的是缓存中一些只会被访问一次或者几次的的数据被访问完后再也不会被访问到但这部分数据依然留存在缓存中消耗缓存空间。
缓存污染会随着数据的持续增加而逐渐显露随着服务的不断运行缓存中会存在大量的永远不会再次被访问的数据。缓存空间是有限的如果缓存空间满了再往缓存里写数据时就会有额外开销影响Redis性能。这部分额外开销主要是指写的时候判断淘汰策略根据淘汰策略去选择要淘汰的数据然后进行删除操作。
最大缓存设置多大
系统的设计选择是一个权衡的过程大容量缓存是能带来性能加速的收益但是成本也会更高而小容量缓存不一定就起不到加速访问的效果。一般来说我会建议把缓存容量设置为总数据量的 15% 到 30%兼顾访问性能和内存空间开销。
对于 Redis 来说一旦确定了缓存最大容量比如 4GB你就可以使用下面这个命令来设定缓存的大小了
1.CONFIG SET maxmemory 4gb
不过缓存被写满是不可避免的, 所以需要数据淘汰策略。
缓存淘汰策略
Redis共支持八种淘汰策略分别是noeviction、volatile-random、volatile-ttl、volatile-lru、volatile-lfu、allkeys-lru、allkeys-random 和 allkeys-lfu 策略。
怎么理解呢主要看分三类看 不淘汰 noeviction v4.0后默认的 对设置了过期时间的数据中进行淘汰 随机volatile-random ttlvolatile-ttl lruvolatile-lru lfuvolatile-lfu 全部数据进行淘汰 随机allkeys-random lruallkeys-lru lfuallkeys-lfu noeviction
该策略是Redis的默认策略。在这种策略下一旦缓存被写满了再有写请求来时Redis 不再提供服务而是直接返回错误。这种策略不会淘汰数据所以无法解决缓存污染问题。一般生产环境不建议使用。
其他七种规则都会根据自己相应的规则来选择数据进行删除操作。
volatile-random
这个算法比较简单在设置了过期时间的键值对中进行随机删除。因为是随机删除无法把不再访问的数据筛选出来所以可能依然会存在缓存污染现象无法解决缓存污染问题。
volatile-ttl
这种算法判断淘汰数据时参考的指标比随即删除时多进行一步过期时间的排序。Redis在筛选需删除的数据时越早过期的数据越优先被选择。
volatile-lru
LRU算法LRU 算法的全称是 Least Recently Used按照最近最少使用的原则来筛选数据。这种模式下会使用 LRU 算法筛选设置了过期时间的键值对。
Redis优化的LRU算法实现
Redis会记录每个数据的最近一次被访问的时间戳。在Redis在决定淘汰的数据时第一次会随机选出 N 个数据把它们作为一个候选集合。接下来Redis 会比较这 N 个数据的 lru 字段把 lru 字段值最小的数据从缓存中淘汰出去。通过随机读取待删除集合可以让Redis不用维护一个巨大的链表也不用操作链表进而提升性能。
Redis 选出的数据个数 N通过 配置参数 maxmemory-samples 进行配置。个数N越大则候选集合越大选择到的最久未被使用的就更准确N越小选择到最久未被使用的数据的概率也会随之减小。
volatile-lfu
会使用 LFU 算法选择设置了过期时间的键值对。
LFU 算法LFU 缓存策略是在 LRU 策略基础上为每个数据增加了一个计数器来统计这个数据的访问次数。当使用 LFU 策略筛选淘汰数据时首先会根据数据的访问次数进行筛选把访问次数最低的数据淘汰出缓存。如果两个数据的访问次数相同LFU 策略再比较这两个数据的访问时效性把距离上一次访问时间更久的数据淘汰出缓存。Redis的LFU算法实现:
当 LFU 策略筛选数据时Redis 会在候选集合中根据数据 lru 字段的后 8bit 选择访问次数最少的数据进行淘汰。当访问次数相同时再根据 lru 字段的前 16bit 值大小选择访问时间最久远的数据进行淘汰。
Redis 只使用了 8bit 记录数据的访问次数而 8bit 记录的最大值是 255这样在访问快速的情况下如果每次被访问就将访问次数加一很快某条数据就达到最大值255可能很多数据都是255那么退化成LRU算法了。所以Redis为了解决这个问题实现了一个更优的计数规则并可以通过配置项来控制计数器增加的速度。
参数
lfu-log-factor 用计数器当前的值乘以配置项 lfu_log_factor 再加 1再取其倒数得到一个 p 值然后把这个 p 值和一个取值范围在01间的随机数 r 值比大小只有 p 值大于 r 值时计数器才加 1。
lfu-decay-time 控制访问次数衰减。LFU 策略会计算当前时间和数据最近一次访问时间的差值并把这个差值换算成以分钟为单位。然后LFU 策略再把这个差值除以 lfu_decay_time 值所得的结果就是数据 counter 要衰减的值。
lfu-log-factor设置越大递增概率越低lfu-decay-time设置越大衰减速度会越慢。
我们在应用 LFU 策略时一般可以将 lfu_log_factor 取值为 10。如果业务应用中有短时高频访问的数据的话建议把 lfu_decay_time 值设置为 1。可以快速衰减访问次数。
volatile-lfu 策略是 Redis 4.0 后新增。
allkeys-lru
使用 LRU 算法在所有数据中进行筛选。具体LFU算法跟上述 volatile-lru 中介绍的一致只是筛选的数据范围是全部缓存这里就不在重复。
allkeys-random
从所有键值对中随机选择并删除数据。volatile-random 跟 allkeys-random算法一样随机删除就无法解决缓存污染问题。
allkeys-lfu
使用 LFU 算法在所有数据中进行筛选。具体LFU算法跟上述 volatile-lfu 中介绍的一致只是筛选的数据范围是全部缓存这里就不在重复。
allkeys-lfu 策略是 Redis 4.0 后新增。
数据库和缓存一致性
问题来源
使用redis做一个缓冲操作让请求先访问到redis而不是直接访问MySQL等数据库 读取缓存步骤一般没有什么问题但是一旦涉及到数据更新数据库和缓存更新就容易出现缓存(Redis)和数据库MySQL间的数据一致性问题。
不管是先写MySQL数据库再删除Redis缓存还是先删除缓存再写库都有可能出现数据不一致的情况。举一个例子
1.如果删除了缓存Redis还没有来得及写库MySQL另一个线程就来读取发现缓存为空则去数据库中读取数据写入缓存此时缓存中为脏数据。
2.如果先写了库在删除缓存前写库的线程宕机了没有删除掉缓存则也会出现数据不一致情况。
因为写和读是并发的没法保证顺序,就会出现缓存和数据库的数据不一致的问题。
Cache Aside Pattern
更新缓存的的Design Pattern有四种Cache aside, Read through, Write through, Write behind caching; 我强烈建议你看看这篇左耳朵耗子的文章缓存更新的套路
节选最最常用的Cache Aside Pattern, 总结来说就是
读的时候先读缓存缓存没有的话就读数据库然后取出数据后放入缓存同时返回响应。
更新的时候先更新数据库然后再删除缓存。
其具体逻辑如下
失效应用程序先从cache取数据没有得到则从数据库中取数据成功后放到缓存中。
命中应用程序从cache中取数据取到后返回。
更新先把数据存到数据库中成功后再让缓存失效。 注意我们的更新是先更新数据库成功后让缓存失效。那么这种方式是否可以没有文章前面提到过的那个问题呢我们可以脑补一下。
一个是查询操作一个是更新操作的并发首先没有了删除cache数据的操作了而是先更新了数据库中的数据此时缓存依然有效所以并发的查询操作拿的是没有更新的数据但是更新操作马上让缓存的失效了后续的查询操作再把数据从数据库中拉出来。而不会像文章开头的那个逻辑产生的问题后续的查询操作一直都在取老的数据。
这是标准的design pattern包括Facebook的论文《Scaling Memcache at Facebook (opens new window)》也使用了这个策略。为什么不是写完数据库后更新缓存你可以看一下Quora上的这个问答《Why does Facebook use delete to remove the key-value pair in Memcached instead of updating the Memcached during write request to the backend? (opens new window)》主要是怕两个并发的写操作导致脏数据。
那么是不是Cache Aside这个就不会有并发问题了不是的比如一个是读操作但是没有命中缓存然后就到数据库中取数据此时来了一个写操作写完数据库后让缓存失效然后之前的那个读操作再把老的数据放进去所以会造成脏数据。
但这个case理论上会出现不过实际上出现的概率可能非常低因为这个条件需要发生在读缓存时缓存失效而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多而且还要锁表而读操作必需在写操作前进入数据库操作而又要晚于写操作更新缓存所有的这些条件都具备的概率基本并不大。
所以这也就是Quora上的那个答案里说的要么通过2PC或是Paxos协议保证一致性要么就是拼命的降低并发时脏数据的概率而Facebook使用了这个降低概率的玩法因为2PC太慢而Paxos太复杂。当然最好还是为缓存设置上过期时间。
方案队列 重试机制
流程如下所示 更新数据库数据 缓存因为种种问题删除失败 将需要删除的key发送至消息队列 自己消费消息获得需要删除的key 继续重试删除操作直到成功
然而该方案有一个缺点对业务线代码造成大量的侵入。于是有了方案二在方案二中启动一个订阅程序去订阅数据库的binlog获得需要操作的数据。在应用程序中另起一段程序获得这个订阅程序传来的信息进行删除缓存操作。
方案异步更新缓存(基于订阅binlog的同步机制) MySQL binlog增量订阅消费消息队列增量数据更新到redis
1读Redis热数据基本都在Redis
2写MySQL: 增删改都是操作MySQL
3更新Redis数据MySQ的数据操作binlog来更新到Redis
技术整体思路
Redis更新
1数据操作主要分为两大块
一个是全量(将全部数据一次写入到redis)
一个是增量实时更新
这里说的是增量,指的是mysql的update、insert、delate变更数据。
2读取binlog后分析 利用消息队列,推送更新各台的redis缓存数据。
这样一旦MySQL中产生了新的写入、更新、删除等操作就可以把binlog相关的消息推送至RedisRedis再根据binlog中的记录对Redis进行更新。
其实这种机制很类似MySQL的主从备份机制因为MySQL的主备也是通过binlog来实现的数据一致性。
这里可以结合使用canal(阿里的一款开源框架)通过该框架可以对MySQL的binlog进行订阅而canal正是模仿了mysql的slave数据库的备份请求使得Redis的数据更新达到了相同的效果。
当然这里的消息推送工具你也可以采用别的第三方kafka、rabbitMQ等来实现推送更新Redis。
参考文章
https://coolshell.cn/articles/17416.html
https://www.cnblogs.com/rjzheng/p/9041659.html
https://my.oschina.net/jiagouzhan/blog/2990423
https://blog.csdn.net/qq_38261137/article/details/106949963
转自Redis进阶-缓存问题