likes
comments
collection
share

【进阶篇】Redis深入理解与实践指南(十二)之Redis高并发之缓存穿透、击穿与雪崩

作者站长头像
站长
· 阅读数 51

Redis缓存的使用,极大地提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要命的问题,就是数据一致性问题。从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。

另外的一些典型的问题就是,缓存穿透、缓存击穿和雪崩。目前,业界也都有比较流行的解决方案。

1、缓存穿透

概念

缓存穿透(查不到)的概念很简单,用户想要查询一个数据;发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。

【进阶篇】Redis深入理解与实践指南(十二)之Redis高并发之缓存穿透、击穿与雪崩

解决方案

缓存空对象

当存储层(Redis缓存)不命中后,即使返回的空对象也将其缓存起来,同时设置一个过期时间(避免元素删除不了以及占用内存空间的问题),之后再访问这个数据将会从缓存中获取,保护了后端数据源。

但是这种办法会存在两个问题:

1、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有更多的空值的键(设置 ttl 即可解决);

2、即使对空值设置了过期时间,还是会存在缓存层(本地)和存储层(服务器缓存)的数据会有一段时间窗口不一致,这对于需要保持一致性的业务有影响(临时的不一致)。

布隆过滤器

布隆过滤器(Bloom Filter)的核心实现是一个超大的位数组和几个哈希函数。布隆过滤器是一种数据结构,类似于hash set可以用来判断一个元素是否在一个集合中。很常用的一个功能是用来去重。对所有可能查询的参数以hash的形式存储,在控制层先进行校验,不符合则丢弃,从而避免了系统的查询压力。

【进阶篇】Redis深入理解与实践指南(十二)之Redis高并发之缓存穿透、击穿与雪崩

  • 巴顿.布隆于一九七零年提出
  • 一个很长的二进制向量 (位数组)
  • 一系列随机函数 (哈希)
  • 空间效率和查询效率高
  • 有一定的误判率(哈希表是精确匹配)

实现原理

HashMap 的问题

讲述布隆过滤器的原理之前,我们先思考一下,通常你判断某个元素是否存在用的是什么?应该蛮多人回答 HashMap 吧,确实可以将值映射到 HashMap 的 Key,然后可以在 O(1) 的时间复杂度内返回结果,效率奇高。但是 HashMap 的实现也有缺点,例如存储容量占比高,考虑到负载因子的存在,通常空间是不能被用满的,而一旦你的值很多例如上亿的时候,那 HashMap 占据的内存大小就变得很可观了。

还比如说你的数据集存储在远程服务器上,本地服务接受输入,而数据集非常大不可能一次性读进内存构建 HashMap 的时候,也会存在问题。

应用场景

某些存储系统(例如Redis)的设计中,会存在空查询缺陷:当查询一个不存在的key时,需要访问慢设备,导致效率低下。 比如一个前端页面的缓存系统,可能这样设计:先查询某个页面在本地(利用字典Dict)是否存在,如果存在就直接返回,如果不存在,就从后端获取。但是当频繁从缓存系统查询一个页面时,缓存系统将会频繁请求后端,把压力导入后端。当然,布隆过滤器是基于Redis实现的较多,一般是用一个布隆过滤器当做一个单独的挡板使用。而且,布隆过滤器不适合 "零错误" 的场景,可以考虑使用布谷鸟过滤器!

基本使用场景

(1)google guava包中有对Bloom Filter的实现

(2)通常使用布隆过滤器去解决redis中的缓存穿透,解决方案是redisbitmap的实现,

(3)钓鱼网站垃圾邮件检测

Bloom Filter 具体用例

Google 著名的分布式数据库 Bigtable 使用了布隆过滤器来查找不存在的行或列,以减少磁盘查找的IO次数。

Squid 网页代理缓存服务器在 cache digests 中使用了也布隆过滤器。

Google Chrome浏览器使用了布隆过滤器加速安全浏览服务。

Venti 文档存储系统也采用布隆过滤器来检测先前存储的数据。

SPIN 模型检测器也使用布隆过滤器在大规模验证问题时跟踪可达状态空间。

在很多Key-Value系统中也使用了布隆过滤器来加快查询过程,如 HbaseAccumuloLeveldb,一般而言,Value 保存在磁盘中,访问磁盘需要花费大量时间,然而使用布隆过滤器可以快速判断某个Key对应的Value是否存在,因此可以避免很多不必要的磁盘IO操作,只是引入布隆过滤器会带来一定的内存消耗。

优缺点

优点:不需要存储key,节省空间

缺点

  1. 算法判断key在集合中时,有一定的概率key其实不在集合中
  2. 无法删除

布隆与布谷鸟过滤器对比

布隆过滤器

通过将已经查询过的数据放进Hash数组中,并且通过Hash计算分配多个数组位置,确保元素位置进入时不一定会替换原数据位置。当多个新元素进入时,旧元素的数据可能会被替换也可能不会被替换。

个人思考,这里存在多个问题:

  • 第一个问题就是布隆过滤器的拦截不完全一致性。因为元素删除的策略是LRU的思想,但是可能旧的数据进入时只使用了一次,但是依旧还使用着位置(要依靠概率删除);旧元素的频繁使用的数据,可能保存不久就被新元素给替换了。当然,这存在一种设计思想,就是布隆过滤器适用于大数据场景的;因此,不管新旧元素,只要使用不频繁一定会被替换(尤其是数组满了的情况下,参考硬币在高次数实验下的正反比例)。这就衍生了一个问题,如果是马上需要使用的场景(例如:秒杀场景)是不是就不能可靠地保证数据的拦截,还需要进行数据的提前预热?是的,因此,即使在提前预热数据也不一定能保证所有的数据都访问可靠。因此,布隆过滤器Redis等内存数据库常常配合使用,因为内存数据库也都不是追求强一致性的
  • 第二个问题就是 :布隆过滤器数据不能删除。因为,布隆过滤器的数据位置存放策略分散Hash分配;同时,一个位置能映射多个数据节点。因此,当将一个位置置为0,那么删除的可能是多个元素节点(因为其实现是Bitmap,参考指针的全局性效果)。因此,布隆过滤器就不支持删除策略,这也是作者的一种设计思想
  • 第三个问题就是数据分布的随机性。数据分布随机性,这是一种设计思想;作者考虑到布隆过滤器是应用于大数据场景下,因此将数据分布策略设计为随机分布。这有好处,当然也有坏处。好处是:设计简单,程序实现简单,开始时数据拦截快(是不是作者想通过这种方式解决慢启动问题?)。坏处是:拦截数据位置随机,可能一个高热点数据在初期被替换掉了,这就会导致高反应的热点数据会被放跑;而且,过滤器的存储空间也不一定能合理分配,空间利用率低,完全看运气!
  • 然后,另外一个问题:就是数据拦截策略的慢启动性。在使用拦截热点数据的初期,因为其数据替换策略是直接替换,因此,热点数据不能保证马上存在,只能在拦截了大量数据后才慢慢接近最大比例。因此,数据的高速峰流数据不能保证马上拦截。
  • 最后一个问题是布隆过滤器的查询效率低。布隆过滤器在初期插入数据和查询数据的确快,因为是基于全局查找的,相当于一个字典表。但是,数据量大了以后,全局查找的速度明显会慢的多。

布隆过滤器底层采用Bitmap(二进制位)实现。如果我下次把这个不存在的数据插入到数据库了,那么也需要将布隆过滤器的bitmap刷新,因为数据库写了一遍,还需要在redis再写一遍,就涉及到缓存一致性了。

布隆过滤器的优点

  1. 由一段二进制的数据来存储数据,所以它的占用空间非常小
  2. 插入和查询速度快,通过计算hash值找到所在二进制的位置;将0改为1即可,基于数组的特性,所以非常快
  3. 保密性非常好,因为里面存储都是0和1,别人根本就不知道它存储的是什么

布隆过滤器的缺陷

  1. 布隆过滤器会有误判的情况
  2. 无法删除元素,也不是无法删除,只是在hash冲突的情况下会将其他相同位置的元素数据一起删除

布谷鸟过滤器

布谷鸟过滤器源于布谷鸟哈希算法,是为了解决布隆过滤器不能删除元素的特性存在的,能大大提高CPU缓存命中率。布谷鸟过滤器的实现原理是:同样是基于LRU的缓存策略,也是通过Hash算法分配位置,但是替换策略本质上不一样。布谷鸟过滤器在新加入数据位置发生冲突时,会替换掉原数据元素并递归查询被替换元素的位置(如果有多个备选数据)。实质就是:先霸占位置,再一个一个踢走元素,直到元素存放唯一且存储空间占满。

区别:相比布谷鸟过滤器而言布隆过滤器有以下不足 :查询性能弱空间利用效率低不支持反向操作(删除) 以及不支持计数。当然,布谷鸟过滤器因为也是基于Hash算法存储,因此空间利用率也不高

Hash算法

Hash算法是通过某种基于二进制位的算法,将一串字符串转化(加密)为一个固定长度的整数(Hashcode)。整数可以解密,同时包含了原加密前的所有信息。并且,一般只能通过原加密策略进行数据的解密,使用时只校验加密后的整数即可!保存方式是哈希表,一个一维数组表(使用K,V存储),K为顺序数组,V为地址空间。

查询速度的快慢 :哈希表 > 有序数组=平衡二叉树 > 二叉树 > 数组=链表=有序链表

插入速度快慢 :哈希表=数组=链表 > 有序数组=平衡二叉树(红黑树是其变种) > 二叉树 > 数组

删除速度快慢 :平衡二叉树 > 二叉树 > 数组 = 链表 >> 哈希表

遍历速度快慢 :数组 ≈ 链表 ≈ 树 >> 哈希表

下面是几种数据的查找、插入、删除的时间复杂度对比:

数据结构查找插入删除
数组O(n)O(n)O(n)
有序数组O(logn)(二分查找)O(n)O(n)
单链表O(n)O(n)O(n)
有序单链表O(n)O(n)O(n)
双链表O(n)O(n)O(n)
有序双链表O(n)O(n)O(n)
二叉树O(n)O(n)O(n)
二叉搜索树O(logn)O(logn)O(logn)
红黑树O(logn)O(logn)O(logn)
平衡二叉树O(logn)O(logn)O(logn)
哈希表O(1)O(1)O(1)

因为哈希表对于数据的遍历以及删除的高度缺失,因此Hash表一般不用于数据库存储,只用于会自带索引的文件系统或者过滤器。综合考虑,平衡二叉树(AVL树)是目前综合速度最快的数据结构(左右最深节点差不大于1),并且支持增删改查以及遍历所有功能;B树是AVL树的变种,而B+树是将B树的查询、插入分离,使B树性能进一步提高,因此MySQL将数据存储结构更换为了B+树。面试高频考点 :MySQL的索引——B+树的原理,以及B+树与红黑树的区别(B+树适合外存,),平衡二叉树与红黑树的区别(平衡二叉树更稳定)

面试例子

为什么说B+树比B 树更适合实际应用中操作系统的文件索引和数据库索引?

B+树的磁盘读写代价更低 B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。 举个例子,假设磁盘中的一个盘块容纳16bytes,而一个关键字2bytes,一个关键字具体信息指针2bytes。一棵9阶B-tree(一个结点最多8个关键字)的内部结点需要2个盘快。而B+树内部结点只需要1个盘快。当需要把内部结点读入内存中的时候,B 树就比B+树多一次盘块查找时间(在磁盘中就是盘片旋转的时间)。

B+树的查询效率更加稳定 由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

【进阶篇】Redis深入理解与实践指南(十二)之Redis高并发之缓存穿透、击穿与雪崩 演变过程 :二叉树 => 平衡二叉树 => 红黑树 => B树 => B-树 => B+树 => B*树

分别是 :二叉树多叉树的演变之路

其中的B就表示平衡(Balance)

B+树适合扫库,解决的是查询某一范围内的数据。它的数据只存在叶子结点,非叶子结点存的是索引,所以显而易见,B+数不适合搜索某一特定的值,因为到叶子结点的路径肯定要比B-树的非叶子结点要短。

B*树的空间利用率高。相比B+树结点满了就建新结点的做法,B*树是先往兄弟结点中放,都放满了在开辟新的结点,创建新结点少,所以空间利用率高。

总结:二叉查找红黑树最强,多叉查找B*树最强。红黑树深度大,比较瘦高,IO频繁;B+树宽度大(B*树目前因为较复杂,还没有应用广泛),比较矮胖。因此,B+树一般用于外存,红黑树一般用于内存

Hash随机分配算法

  • 除余法
  • 乘余取整法
  • 平方取中法
  • 数字分析法
  • 基数转换法
  • 折叠法

地址冲突解决策略

  • 分离链表法

  • 闭散列方法(开放地址法)

    (1) 线性探测法

    (2) 二次探查法

    (3) 随机探查法

    (4) 双散列探查法

因此,所谓Hash,一般是一个整数,通过某种算法,可以把一个字符串"压缩" 成一个整数。当然,无论如何,一个32位整数是无法对应回一个字符串的,但在程序中,两个字符串计算出的Hash值相等的可能非常小,下面看看在MPQ中的Hash算法:

函数一、 以下的函数生成一个长度为0x500(合10进制数:1280)的cryptTable[0x500]

void prepareCryptTable()
{ 
unsigned long seed = 0x00100001, index1 = 0, index2 = 0, i;
​
for( index1 = 0; index1 < 0x100; index1++ )
{ 
for( index2 = index1, i = 0; i < 5; i++, index2 += 0x100 )
{ 
unsigned long temp1, temp2;seed = (seed * 125 + 3) % 0x2AAAAB;
temp1 = (seed & 0xFFFF) << 0x10;seed = (seed * 125 + 3) % 0x2AAAAB;
temp2 = (seed & 0xFFFF);
​
cryptTable[index2] = ( temp1 | temp2 ); 
} 
}

布隆过滤器与布谷鸟过滤器都是基于Hash算法实现存储的,既然Hash存储空间利用率不高,为什么还要使用呢?

答案就是 :快

Hash算法的速度无限接近于O(1) ,因此最强的存储策略一般就是指Hash存储。比如:以太坊的分布式存储、以及各种负载均衡策略,都是使用Hash算法。

写入速度:B树 < B+树 < LSM树 < Hash表

读取性能:

Hash的随机存储策略确正好贴合硬盘的存储方式——随机存储,因为硬盘是旋转存储的,保证了数据存储均匀;因此,Hash存储速度是最快的!哈希算法存取之所以快,是因为其存储数据结构是一维散列数组散列表,顺序存储,连续的地址空间)和不连续的双向链表地址空间,直接通过关键字key得到要存取的记录内存存储位置value。

我想大家都在想一个很严重的问题:“如果两个字符串在哈希表中对应的位置相同怎么办?”,毕竟一个数组容量是有限的,这种可能性很大。解决该问题的方法很多,我首先想到的就是用“链表”,感谢大学里学的数据结构教会了这个百试百灵的法宝,我遇到的很多算法都可以转化成链表来解决,只要在哈希表的每个入口挂一个链表,保存所有对应的字符串就OK了。事情到此似乎有了完美的结局,如果是把问题独自交给我解决,此时我可能就要开始定义数据结构然后写代码了。

然而Blizzard的程序员使用的方法则是更精妙的方法。基本原理就是:他们在哈希表(HashTable) 中不是用一个哈希值而是用三个哈希值来校验字符串。

总结布隆过滤器适用于大数据拦截的主力,不保证可靠性,只能做大概率分流;而布谷鸟过滤器是适用于小数据量的拦截,可以单独作为一个高一致性的拦截器,也可以作为高一致性的大数据场景下布隆过滤器的补充。

扩展

Counting filters

基本的布隆过滤器不支持删除(Deletion) 操作,但是 Counting filters 提供了一种可以不用重新构建布隆过滤器但却支持元素删除操作的方法。在Counting filters中原来的位数组中的每一位由 bit 扩展为 n-bit 计数器,实际上,基本的布隆过滤器可以看作是只有一位的计数器Counting filters。原来的插入操作也被扩展为把 n-bit 的位计数器加1,查找操作即检查位数组非零即可,而删除操作定义为把位数组的相应位减1,但是该方法也有位的算术溢出问题,即某一位在多次删除操作后可能变成负值,所以位数组大小 m 需要充分大。另外一个问题是Counting filters不具备伸缩性,由于Counting filters不能扩展,所以需要保存的最大的元素个数需要提前知道。否则一旦插入的元素个数超过了位数组的容量,false positive的发生概率将会急剧增加。当然也有人提出了一种基于 D-left Hash 方法实现支持删除操作的布隆过滤器,同时空间效率也比Counting filters高。

Data synchronization

Byers等人提出了使用布隆过滤器近似数据同步

Bloomier filters

Chazelle 等人提出了一个通用的布隆过滤器,该布隆过滤器可以将某一值与每个已经插入的元素关联起来,并实现了一个关联数组Map。与普通的布隆过滤器一样,Chazelle实现的布隆过滤器也可以达到较低的空间消耗,但同时也会产生false positive,不过,在Bloomier filter中,某 key 如果不在 map 中,false positive在会返回时会被定义出的。该Map 结构不会返回与 key 相关的在 map 中的错误的值。

2、缓存击穿

概念

这里需要注意和缓存穿透的区别,缓存击穿(量太大,缓存过期),是指一个key非常热点,在不停的扛着大并发并集中对这一个点进行访问;当这个key失效的瞬间,持续的大并发直接请求数据库(MySQL),就像在一个屏障上凿开了一个洞。

当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据;由于缓存过期,会同时访问数据库来查询最新数据,并写回缓存,会导致数据库瞬间压力过大。

比如:热搜、爆款商品秒杀

解决方案

  • 设置热点数据永不过期(首选)

    从缓存层面来看,没有设置过期时间,所以不会出现热点 key过期后产生的问题。

  • 加互斥锁(没必要,浪费性能)

    分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获取分布式锁的权限。因此,只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。

3、缓存雪崩

概念

概念(发生雪崩时,没有一片雪花是无辜的!)

缓存雪崩(批量失效),是指在某一个时间段,缓存集中过期失效,Redis宕机。

产生雪崩的原因之一,比如在抢商品的时候,马上就要到双11零点,很快就会迎来一波抢购。这波商品时间比较集中地放入了缓存,假设缓存一个小时,那么到了凌晨1点钟的时候,这批商品的缓存就都过期了。而对于这批商品的访问查询,都落到了数据库上;对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会到达存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。

【进阶篇】Redis深入理解与实践指南(十二)之Redis高并发之缓存穿透、击穿与雪崩

其中集中过期,倒不是非常致命的;比较致命的是它导致的结果(缓存雪崩),是指缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性道德压力而已,而缓存服务器节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮!

例子:

双11:停掉一些服务,即服务熔断(开源方案-断路器和哨兵:Spring Cloud Hystrix,Spring Cloud Alibaba Sentinel)保证主要的服务可用!

解决方案

解决方案

redis集群

这个思想的含义是高可用,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他还可以继续工作,其实就是搭建的集群(异地多活)。

限流降级

这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库和写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

限流算法

并发数限流
  1. 计数器并发数限流:使用共享变量实现

    并发数/线程数: 共享变量:不同版本用了不同实现

    • v1.6实现:private AtomicInteger curThreadNum = new AtomicInteger(0);
    • v1.7实现:private LongAdder curThreadNum = new LongAdder();

    volitle 与 LongAdder

  2. 信号量:使用java中的Semaphore

QPS限流
  1. 计数器法:实现简单,精度不高,重置节点时无法处理突发请求(例:每次处理请求1000个请求后重置,但存在重置的时间内的请求临界问题,也叫固定窗口算法)
  2. 滑动窗口:滑动窗口的窗口越小,则精度越高,相应的资源消耗也更高。(例:将窗口时间片分为6个,每一个粒度内都能处理相同的并发数,并且上一个粒度划分与下一个粒度的请求数会累加,切换较为平滑)
  3. 漏桶算法 : 限制的是流出速率,突发请求要排队,对服务保护较好;流入随机,流出固定(按照速率防护,拦截突发请求)
  4. 令牌桶算法 :限制的是平均流入速率,允许一定程度突发请求(发令牌的方式,无需排队)。

限流算法实现 - 简书

限流降级实现方案

下面介绍常用的限流降级实现方案

redis

redis+lua限流

Guava RateLimiter

Guava RateLimiter使用和源码分析

Hystrix(热点)

spring cloud:Hystrix

Sentinel(热点)

sentinel概念

方案对比

方案原理功能适合场景
Redis Lua计数主要针对入口限流,降级方式单一网关
Guaua Ratelimiter令牌桶算法主要针对入口限流,缺少可配置合集群控制,无法根据异常数/异常化比例降级简单限流
Hystix线程池隔离,断路器只针对出口限流和降级限流、降级
Sentinel流量,节点入口和出口都可以做防护流量防护
限流降级(熔断)常用方案对比

滑动窗口:Sentinel 底层采用高性能的滑动窗口数据结构来统计实时的秒级指标数据。

最大不同点: Hystrix通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。Sentinel 并发线程数限流不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目,如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。

动态配置支持: Hystrix只提供了配置修改的入口,没有将配置界面化,如果想在页面上动态调整配置,还需要自己实现。

限流降级(熔断)对比表:

Sentinel 与 Hystrix 的对比

最后用表格来进行对比总结:

SentinelHystrix
隔离策略信号量隔离线程池隔离/信号量隔离
熔断降级策略基于响应时间或失败比率基于失败比率
实时指标实现滑动窗口滑动窗口(基于 RxJava)
规则配置支持多种数据源支持多种数据源
扩展性多个扩展点插件的形式
基于注解的支持支持支持
限流基于 QPS,支持基于调用关系的限流有限的支持
流量整形支持慢启动、匀速器模式不支持
系统负载保护支持不支持
控制台开箱即用,可配置规则、查看秒级监控、机器发现等不完善
常见框架的适配Servlet、Spring Cloud、Dubbo、gRPC 等Servlet、Spring Cloud Netflix

数据预热

数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍(解决冷启动问题)。这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载热数据,缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀(Random 随机设置ttl)。

例如:微博热搜“关晓彤和鹿晗结婚”话题的冷启动将微博打挂了

思路

缓存预热的思路

1.提前给redis中嵌入部分数据,再提供服务

2.肯定不可能将所有数据都写入redis,因为数据量太大了,第一耗费的时间太长了,第二redis根本就容纳不下所有的数据

3.需要更具当天的具体访问情况,试试统计出频率较高的热数据

4.然后将访问频率较高的热数据写入到redis,肯定是热数据也比较多,我们也得多个服务并行的读取数据去写,并行的分布式的缓存预热

5.然后将嵌入的热数据的redis对外提供服务,这样就不至于冷启动,直接让数据库奔溃了

简单讲 不要让所有的请求都去数据库查 提前把一些热点数据存入 至于哪些是热数据 这就得实时分析(采用Storm/Flink)了呀

实施方案

具体的实施方案:

  1. nginx+lua将访问量上报到kafka中,要统计出来当前最新的实时的热数据是哪些,我们就得将商品详情页访问的请求对应的流量,日志,实时上报到kafka中,

  2. storm从kafka中消费数据,实时统计出每个商品的访问次数,访问次数基于LRU内存数据结构的存储方案 (LRU 看次数)

  3. 每个storm task启动的时候,基于Zookeeper分布式锁,将自己的id写入zk的一个节点中

  4. 每个storm task负责完成自己这里的热数据的统计,比如每次计数过后,维护一个钱1000个商品的list,每次计算完都更新这个list

  5. 写一个后台线程,每个一段时间,比如一分钟,将排名前1000的热数据list,同步到zk中。

欢迎点赞关注评论,感谢观看ヾ(◍°∇°◍)ノ゙

转载自:https://juejin.cn/post/7133985954503032862
评论
请登录