以前总觉得,写得一手好Java代码,走遍天下都不怕,后来随着时间得推移,才意识到程序的高效性流畅性才是最重要最重要的。 所有的技术都是前人不断实践突破革新留下的产物,它们的存在以及运用一定有它的道理。 于是博主最近将重心向程序的高效性和流畅性的基础学习上偏移了一下,而今天为大家分享的就是Redis在面对小数据量下的大量数据请求的时候,所可能遇到的一些问题。
Redis全称:Remote Dictionary Server,它的字面意思是远程+字典+服务,本质上是一个Key-Value的内存数据库,它把数据缓存在物理内存里,由于直接访问内存,所以速度极快,每秒可处理超过10万+的数据量,是我们已知性能最快的Key-Value形Database。 那么Redis为什么这么快呢?首先他是存内存操作,其次他是单线程操作,避免了频繁的上下文切换,再就是采用了非阻塞I/O多路复用机制。 由于Redis直接访问内存,因此我们可以推断出,内存的配置才是影响Redis性能的瓶颈,正应为受到物理内存的限制,所以不能用作海量数据的高性能读写,所以Redis主要运用在小数据量的高性能读写上,而数据要缓存,带宽也是影响Redis性能的因素之一。Redis最出色的地方就是它的 单个value最大限制是1GB! 不像memcached(分布式高速缓存系统)只能保存1MB的数据。另外,Redis可以对存入的Key-Value设置expire即过期时间,因此可以当作一个功能加强版的memcached。Redis提供了五种数据类型,分别是String、hash、list、set、sorted set。
缓存雪崩:
用通俗的话来讲就是既然Redis可以给存入的Key-Value设置过期时间,那么是不是就会存在设置了相同过期时间的数据,如果这个数据量过大,那么如果在同一时间下,大量的缓存过期,就会导致所有原本应该访问缓存的请求将请求压力全部给到数据库,进而对数据库的CPU和内存造成巨大压力,严重的情况还可能会有宕机的情况发生。
怎么解决呢?这里有一个最简单的方法就是将缓存的失效时间给分散开,但是大部分情况下我们都会选择加锁或者队列(先进先出)的方式来保证不会有大量的线程对数据库进行一次性读写,进而避免当雪崩时大量的并发请求压力落到底层的存储系统上。
缓存穿透:
缓存穿透指的是如果用户请求一条数据,而这条数据数据库中不存在,自然这条数据也不会存在于缓存中,当有一个关于这条数据的查询请求产生时,首先会去缓存中查结果是没有,然后再进入到数据库中查没有,再返回为空,相当于进行了两次无用的查询。像这样的请求,绕过缓存直接查询数据库,就涉及到一个缓存命中率 (缓存命中率=从缓存中读取数据的次数/所有访问数据次数) 的问题。
最简单的解决办法就是只要这个查询结果为空(无论是则很难的为空还是系统故障),我们都将则会个值设置默认值并且存入缓存,但是它的过期时间很短,最高只有五分钟,于是在二次查询的时候,就能获取到值了,从而避免了缓存穿透。
最常见的就是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的Bitmap(典型的哈希表,Bitmap对于每个元素只能记录1bit信息,如果还想完成额外的功能,只能消耗更大的时间空间来完成了)中去,一定不存在的数据会直接被这个bitMap拦截掉,从而避免了底层存储系统的查询压力。对空间的运用到达了一种极致就是Bitmap和布隆过滤器了。
既然这里提到了布隆过滤器,那我们就简单地聊聊它的原理一级运作方式吧。简单来说就是,在引入了(n+1)个相互独立的哈希函数时,保证在给定的空间、误判率下,去完成元素的判重过程。优点是空间效率和查询时间都远超一般的算法,缺点就是有一定的错误识别率和删除困难。总所周知Hash存在一个hash碰撞的问题,用同一个hash得到的两个URL的值可能会存在相同的情况,为了减少冲突,可以多引入几个Hash,如果通过其中一个Hash值得出某元素不在集合中,那么这个元素就一定不在集合中。只有所有的Hash函数告诉我们该元素存在于集合时,才能确定该元素存在于集合。这就是Bloom-Filter(布隆过滤器)的基本思想,布隆过滤器主要用于在大数据量的集合中去判断某元素是否存在。
缓存预热:
预热相信大家通过字面意思就能理解,好比一个零度的玻璃杯,你突然向里面加入一百度的开水,肯定会对玻璃杯造成偌大的承受压力,所以就得提前预热。预热就是当系统上线以后,将相关的缓存直接加载到缓存系统,这样可以避免用户在发送请求的时候先查询数据库,然后再将数据缓存的问题。这样用户在发送请求的时候就可以访问实现被预热的缓存数据。
缓存预热可以通过写一个定时器去定时刷新缓存,既然涉及到了定时器刷新,那么建议再写一个方法与定时器调用同一个接口,进而可以实现手动刷新。当项目中数据量不大的时候,还可以在项目启动的时候自行加载一下。
缓存更新:
常见的处理策略:①定时去清理过期的缓存。但是维护大量缓存的Key是比较麻烦的。②当一个请求产生时,再去判断这个请求所用到的缓存是否过期,过期的话就去底层系统中得到新数据并更新缓存。这种办法显然每次都去判断,逻辑相对来说要复杂一点。
其实缓存服务器是自带了缓存失效策略的,Redis则提供了定时、定期、惰性删除等过期策略,有六种策略机制:
①volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
②volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
③volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
④allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
⑤allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
⑥no-enviction(驱逐):禁止驱逐数据,新写入操作会报错
如果没有一条数据没有设置expire的key,那么这条数据就不满足先决条件,那么volatile-lru、volatile-ttl、volatile-random就默认其为不删除。大家可以到 Redis包中的redis.conf(windows版本的redis叫redis.windows.conf,找.conf文件就对了) 中查看并设置一下自己的缓存策略。
缓存降级:
当访问量剧增,服务器就会受到访问量的影响例如响应时间慢或者不响应的情况,当非核心的服务影响到了核心流程的性能时,我们要保证核心服务仍然是可用的,即使这个操作有损系统服务。它的最终目的就是保证核心服务可用。像购物车、结算等是不可降级的。
以参考日志级别设置预案:
①一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
②警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
③错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
④严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。
所以对于一些不重要的缓存数据在程序性能遇到高压的时候可以通过缓存服务降级策略保证主要核心服务仍然可用。
好了,今天的分享就到这里,欢迎大家一起交流学习!
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/qq_51250453/article/details/119685856
内容来源于网络,如有侵权,请联系作者删除!