每个布隆过滤器对应到 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 的分布式锁。
缓存雪崩
缓存雪崩是指在某一个时间段内,缓存集中过期失效,如果这个时间段内有大量请求,而查询数据量巨大,所有的请求都会达到存储层,存储层的调用量会暴增,引起数据库压力过大甚至宕机。
「原因:」
「举个例子理解下吧:」
比如我们基本上都经历过购物狂欢节,假设商家举办 23:00-24:00 商品打骨折促销活动。程序小哥哥在设计的时候,在 23:00 把商家打骨折的商品放到缓存中,并通过redis的expire设置了过期时间为1小时。这个时间段许多用户访问这些商品信息、购买等等。但是刚好到了24:00点的时候,恰好还有许多用户在访问这些商品,这时候对这些商品的访问都会落到数据库上,导致数据库要抗住巨大的压力,稍有不慎会导致,数据库直接宕机(over)。
「当商品没有失效的时候是这样的:」
当缓存GG(失效)的时候却是这样的:
「对于缓存雪崩有以下解决方案:」
「(1)redis高可用」
redis有可能挂掉,多增加几台redis实例,(一主多从或者多主多从),这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。
「(2)限流降级」
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,对某个key只允许一个线程查询数据和写缓存,其他线程等待。
「(3)数据预热」
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key。
「(4)不同的过期时间」
设置不同的过期时间,让缓存失效的时间点尽量均匀。
收藏 等于白嫖,点赞 才是真情!
本文分享自微信公众号 - JAVA日知录(javadaily)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。