最近都没看Redis,如今回来温习下,如今从Redis的三大缓存开始从新探一探有多深有多浅(^▽^)程序员
让我来开始知识的醍醐灌顶把!是时候表演真正的技术了。(哔哔哔哔....)面试
铁子们,看在二哈这么卖力的份上,若是以为本文对你有帮助的话,请动动你的小手,比个❥(^_-)爱心推荐哟。redis
接下来就开始咱们的Redis的三大缓存问题之旅,让咱们坐上二哈的小飞船游一游这圣女峰。算法
在Redis缓存中有三个必需要知道概念:缓存穿透、缓存击穿和缓存雪崩。数据库
缓存穿透数组
那什么是缓存穿透,它就是指当用户在查询一条数据的时候,而此时数据库和缓存却没有关于这条数据的任何记录,而这条数据在缓存中没找到就会向数据库请求获取数据。它拿不到数据时,是会一直查询数据库,这样会对数据库的访问形成很大的压力。缓存
如:用户查询一个 id = -1 的商品信息,通常数据库 id 值都是从 1 开始自增,很明显这条信息是不在数据库中,当没有信息返回时,会一直向数据库查询,给当前数据库的形成很大的访问压力。数据结构
这时候咱们要想想,该如何解决这个问题呢?o(╥﹏╥)o并发
通常咱们能够想到从缓存开始出发,想若是咱们给缓存设置一个若是当前数据库不存在的信息,把它缓存成一个空对象,返回给用户。分布式
^_^没错,这是一个解决方案,也就是咱们常说的缓存空对象(代码维护简单,可是效果不是很好)。
Redis 也为咱们提供了一种解决方案,那就是布隆过滤器(代码维护比较复杂,效果挺好的)。
那接下来,二哈先解释下这两种方案:
缓存空对象
那什么是缓存空对象呀,二哈!别急,缓存空对象它就是指一个请求发送过来,若是此时缓存中和数据库都不存在这个请求所要查询的相关信息,那么数据库就会返回一个空对象,并将这个空对象和请求关联起来存到缓存中,当下次仍是这个请求过来的时候,这时缓存就会命中,就直接从缓存中返回这个空对象,这样能够减小访问数据库的压力,提升当前数据库的访问性能。咱们接下来能够看下面这个流程呀~
这时候,咱们就会问了呀 ~,若是大量不存在的请求过来,那么这时候缓存岂不是会缓存许多空对象了吗~~~
没错哦!这也是使用缓存空对象会致使的一个问题:若是时间一长这样会致使缓存中存在大量空对象,这样不只会占用许多的内存空间,还会浪费许多资源呀!。那这有没有什么能够解决的方法呢?咱们来想想:咱们能够将这些对象在一段时间以后清理下不久能够了吗 ~
嗯嗯,没错!在想一想 Redis 里是否是给咱们提供了有关过时时间的命令呀(^▽^),这样咱们能够在设置空对象的时间,顺便设置一个过时时间,就能够解决个问题了呀!
号外号外,你们能够关注公众号Java技术栈在后台回复福利能够获取一份我整理的最新面试题资料。
setex key seconds valule:设置键值对的同时指定过时时间(s)
在Java 中直接调用 API 操做便可:
redisCache.put(Integer.toString(id), null, 60) //过时时间为 60s
布隆过滤器
那布隆过滤器是否是不是一个过滤器,过滤东西的呀!哎呀,你太聪明了,没错它就是用来过滤东西的,它是一种基于几率的数据结构,主要使用爱判断当前某个元素是否在该集合中,运行速度快。咱们也能够简单理解为是一个不怎么精确的 set 结构(set 具备去重的效果)。
可是有个小问题是:当你使用它的 contains 方法去判断某个对象是否存在时,它可能会误判。也就是说布隆过滤器不是特别不精确,可是只要参数设置的合理,它的精确度能够控制的相对足够精确,只会有小小的误判几率(这是能够接受的呀 ~)。当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就确定不存在。
这里有个典型的例子呀,来自钱大:
打个比方,当它说不认识你时,确定就不认识;当它说见过你时,可能根本就没见过面,不过由于你的脸跟它认识的人中某脸比较类似 (某些熟脸的系数组合),因此误判之前见过你。
在上面的使用场景中,布隆过滤器能准确过滤掉那些已经看过的内容,那些没有看过的新内容,它也会过滤掉极小一部分 (误判),可是绝大多数新内容它都能准确识别。这样就能够彻底保证推荐给用户的内容都是无重复的。
说了这么久,那布隆过滤器到底有什么特色呢:
特色吗,多多来让一个个跟你吹吹(吹到你怀疑人生(≧∇≦)ノ)
一个很是大的二进制位数组(数组中只存在 0 和 1)
拥有若干个哈希函数(Hash Function)
在空间效率和查询效率都很是高
布隆过滤器不会提供删除方法,在代码维护上比较困难。
每一个布隆过滤器对应到 Redis 的数据结构里面就是一个大型的位数组和几个不同的无偏 hash 函数。所谓无偏就是可以把元素的 hash 值算得比较均匀。具体能够参考这篇文章:布隆过滤器到底有什么用?。
向布隆过滤器中添加 key 时,会使用多个 hash 函数对 key 进行 hash 算得一个整数索引值而后对位数组长度进行取模运算获得一个位置,每一个 hash 函数都会算得一个不一样的位置。再把位数组的这几个位置都置为 1 就完成了 add 操做。( 每个 key 都经过若干的hash函数映射到一个巨大位数组上,映射成功后,会在把位数组上对应的位置改成1。)
那为何布隆过滤器会存在误判率呢?
误判吗?人生哪有不摔跤,只要锄头挥得好,照样能挖到。(咳咳咳,说偏了...)
其实它会误判是以下这个状况:
当 key1 和 key2 映射到位数组上的位置为 1 时,假设这时候来了个 key3,要查询是否是在里面,刚好 key3 对应位置也映射到了这之间,那么布隆过滤器会认为它是存在的,这时候就会产生误判(由于明明 key3 是不在的)。
O(∩_∩)O哈哈~,这时候你会问了:如何提升布隆过滤器的准确率呢?
要提升布隆过滤器的准确率,就要说到影响它的三个重要因素:
哈希函数的好坏
存储空间大小
哈希函数个数
hash函数的设计也是一个十分重要的问题,对于好的hash函数能大大下降布隆过滤器的误判率。
(这就比如优秀的配件之因此可以运行这么顺畅就在于其内部设计的得当。)
同时,对于一个布隆过滤器来讲,若是其位数组越大的话,那么每一个key经过hash函数映射的位置会变得稀疏许多,不会那么紧凑,有利于提升布隆过滤器的准确率。
同时,对于一个布隆过滤器来讲,若是key经过许多hash函数映射,那么在位数组上就会有许多位置有标志,这样当用户查询的时候,在经过布隆过滤器来找的时候,误判率也会相应下降。
对于其内部原理,有兴趣的同窗能够看看关于布隆过滤的数学知识,里面有关于它的设计算法和数学知识。(其实也挺简单~)
缓存击穿
缓存击穿是指有某个key常常被查询,常常被用户特殊关怀,用户很是 love 它 (^▽^),也就类比“熟客” 或者 一个key常常不被访问。推荐阅读:缓存三大问题及解决方案。
可是这时候,若是这个key在缓存的过时时间失效的时候或者这是个冷门key时,这时候忽然有大量有关这个key的访问请求,这样会致使大并发请求直接穿透缓存,请求数据库,瞬间对数据库的访问压力增大。
概括起来:形成缓存击穿的缘由有两个。
(1)一个“冷门”key,忽然被大量用户请求访问。
(2)一个“热门”key,在缓存中时间刚好过时,这时有大量用户来进行访问。
对于缓存击穿的问题:咱们经常使用的解决方案是加锁。对于key过时的时候,当key要查询数据库的时候加上一把锁,这时只能让第一个请求进行查询数据库,而后把从数据库中查询到的值存储到缓存中,对于剩下的相同的key,能够直接从缓存中获取便可。
若是咱们是在单机环境下:直接使用经常使用的锁便可(如:Lock、Synchronized等),在分布式环境下咱们可使用分布式锁,如:基于数据库、基于Redis 或者 zookeeper 的分布式锁。
缓存雪崩
缓存雪崩是指在某一个时间段内,缓存集中过时失效,若是这个时间段内有大量请求,而查询数据量巨大,全部的请求都会达到存储层,存储层的调用量会暴增,引发数据库压力过大甚至宕机。
缘由:
Redis忽然宕机
大部分数据失效
举个例子理解下吧:
好比咱们基本上都经历过购物狂欢节,假设商家举办 23:00-24:00 商品打骨折促销活动。程序小哥哥在设计的时候,在 23:00 把商家打骨折的商品放到缓存中,并经过redis的expire设置了过时时间为1小时。
这个时间段许多用户访问这些商品信息、购买等等。可是恰好到了24:00点的时候,刚好还有许多用户在访问这些商品,这时候对这些商品的访问都会落到数据库上,致使数据库要抗住巨大的压力,稍有不慎会致使,数据库直接宕机(over)。
当商品没有失效的时候是这样的:
当缓存GG(失效)的时候倒是这样的:
对于缓存雪崩有如下解决方案:
(1)redis高可用
Redis有可能挂掉,多增长几台redis实例,(一主多从或者多主多从),这样一台挂掉以后其余的还能够继续工做,其实就是搭建的集群。
(2)限流降级
在缓存失效后,经过加锁或者队列来控制读数据库写缓存的线程数量,对某个key只容许一个线程查询数据和写缓存,其余线程等待。
(3)数据预热
数据加热的含义就是在正式部署以前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不一样的key。
(4)不一样的过时时间
设置不一样的过时时间,让缓存失效的时间点尽可能均匀。
【编辑推荐】
【责任编辑:庞桂玉 TEL:(010)68476606】