龙空技术网

缓存灾难问题解决

九言啊 95

前言:

现在看官们对“nginx缓存怎么清”可能比较看重,咱们都想要分析一些“nginx缓存怎么清”的相关知识。那么小编在网上搜集了一些有关“nginx缓存怎么清””的相关资讯,希望姐妹们能喜欢,咱们快快来学习一下吧!

缓存穿透解决方案理解布隆过滤器原理缓存击穿解决方案缓存雪崩解决方案缓存一致性解决方案RESTful规范安全控制方案1 缓存穿透实战

有些数据查询频率很高的时候,我们会将数据存入到缓存,用户每次查询直接查询缓存即可,从而提高用户访问数据的效率。

比如获取用户为 lisi 的抢红包记录,此时如果每次查询数据库效率都很低,我们可以第1次从数据库查询 lisi 最近的前10条抢红包记录,然后将记录存入到Redis缓存,下次直接查询Redis缓存即可。

每次用户抢红包,谁抢到了红包,我们会将抢到红包的用户信息按照抢红包的金额大小的前100名用户信息公示出去,这里也可以采用这种方式来做。

当然,也不是所有数据都适合做缓存,需要根据数据特点来决定,如下图:

1.1 缓存穿透介绍

上面查询最近红包记录提升用户访问效率,这种操作是一种正常操作,但也存在一些非正常操作,比如 wangwu 没有抢到红包,但用户恶意频繁去查询抢红包记录,此时Redis缓存中将一直没有数据,每次都会查询数据库,这种现象叫缓存穿透。

缓存穿透该如何解决呢?我们提供这一种思路,如下图所示:

1.2 穿透问题解决1.3 布隆过滤器

防止缓存穿透,有多种方案,上面所实现的是一种最简单的方案,也有其他方案,比如布隆过滤器也是缓存穿透方案之一。布隆过滤器主要是解决大规模数据下不需要精确过滤的业务场景,如检查垃圾邮件地址,爬虫URL地址去重,解决缓存穿透问题等。

布隆过滤器:在一个存在一定数量的集合中过滤一个对应的数据,判断该数据是否在该集合中。

1.3.1 原理

布隆过滤器的巨大用处就是,能够迅速判断一个元素是否在一个集合中。因此他有如下三个使用场景:

网页爬虫对URL的去重,避免爬取相同的URL地址反垃圾邮件,从数十亿个垃圾邮件列表中判断某邮箱是否垃圾邮箱(同理,垃圾短信)缓存穿透,将所有可能存在的数据缓存放到布隆过滤器中,当黑客访问不存在的缓存时迅速返回避免缓存及DB挂掉。

我们来谈谈布隆过滤器的原理

其内部维护一个全为0的bit数组,需要说明的是,布隆过滤器有一个误判率的概念,误判率越低,则数组越长,占空间越大。误判率越高则数组越小,所占的空间越小。

假设,根据误判率,我们生成一个10位的bit数组,以及2个hash函数((f_1,f_2)),如下图所示(生成的数组的位数和hash函数的数量,我们不用去关心是如何生成的,有数学论文进行过专业的证明)。

假设输入集合为((N_1,N_2)),经过计算(f_1(N_1))得到的数值得为2,(f_2(N_1))得到的数值为5,则将数组下标为2和下表为5的位置置为1,如下图所示

同理,经过计算(f_1(N_2))得到的数值得为3,(f_2(N_2))得到的数值为6,则将数组下标为3和下表为6的位置置为1,如下图所示

这个时候,我们有第三个数(N_3),我们判断(N_3)在不在集合((N_1,N_2))中,就进行(f_1(N_3),f_2(N_3))的计算

若值恰巧都位于上图的红色位置中,我们则认为,(N_3)在集合((N_1,N_2))中若值有一个不位于上图的红色位置中,我们则认为,(N_3)不在集合((N_1,N_2))中

以上就是布隆过滤器的计算原理。

1.3.2 布隆过滤器案例

引入依赖包

编写测试类

我们可以发现有330个被误判,误判的概率为0.03,源码中也有说明。

这里的误判概率是可以调整的,每次创建 BloomFilter 的时候,指定误判概率值即可,这个值必须大于0。

优点

思路简单保证一致性性能强

缺点

代码复杂度增大需要另外维护一个集合来存放缓存的Key布隆过滤器不支持删值操作2 缓存击穿实战

上面案例我们实现了某个用户抢红包的信息查询,接下来我们实现公示抢到红包并且按照红包金额大小排序查询出前100名用户信息,这块数据并发量将更大,我们需要做缓存处理。

2.1 抢红包排行查询

Service 这里做了缓存操作,缓存1分钟,1分钟过后,会再次查询数据库

测试结果如下:

2.2 击穿现象分析

我们先来了解下缓存击穿,缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

上面查询红包排名就存在击穿现象,比如10万用户请求,此时缓存刚好过期,10万用户同时到达了第②个步骤,而且此时Redis中都判断没有数据,那么此时就都查询数据库,给数据库带来巨大的压力,甚至是宕机。

2.3 击穿解决方案

针对缓存击穿现象,可以有多重解决方案。我们这里给大家讲解一下实用的5种方案。

2.3.1 定时器

后台定义一个job(定时任务)专门主动更新缓存数据.比如,一个缓存中的数据过期时间是1分钟,那么job每隔25秒刷新数据(将从数据库中查到的数据更新到缓存中),或者缓存不过期,直接写定时任务定时更新即可。定时器需要择优选择,比如可以用 elastic-job , xxl-job 。

这种方案比较容易理解,但会增加系统复杂度。比较适合那些 key 相对固定,cache 粒度较大的业务,key 比较分散的则不太适合,实现起来也比较复杂。

2.3.2 多级缓存

采用多级缓存也可以有效防止击穿现象,首先通过程序将缓存存入到Redis缓存,且永不过期,用户查询的时候,先查询Nginx缓存,如果Nginx缓存没有,则查询Redis缓存,并将Redis缓存存入到Nginx一级缓存中,并设置更新时间。这种方案不仅可以提升查询速度,同时又能防止击穿问题,并且提升了程序的抗压能力。

2.3.3 分布式锁

解决上面超卖问题,我们可以采用分布式锁来控制,分布式锁的原理很简单。

分布式锁主要是实现在分布式场景下保证数据的最终一致性。在单进程的系统中,存在多个线程可以同时改变某个变量(可变共享变量)时,就需要对变量或代码块做同步(lock—synchronized),使其在修改这种变量时能够线性执行消除并发修改变量。但分布式系统是多部署、多进程的,开发语言提供的并发处理API在此场景下就无能为力了。

目前市面上分布式锁常见的实现方式有三种:

大部分网站使用的分布式锁是基于缓存的,有更好的性能,而缓存一般是以集群方式部署,保证了高可用性。而Redis分布式锁官方推荐使用redisson。

Redission分布式锁说明:

基于Redisson分布式锁实现

步骤:

1)引入依赖

2)锁操作方法实现

要想用到分布式锁,我们就必须要实现获取锁和释放锁,获取锁和释放锁可以编写一个 DistributedLocker 接口,代码如下:

实现上面接口中对应的锁管理方法,编写一个锁管理类 RedissonDistributedLocker ,代码如下:

3)配置Redis链接

在resources下新建文件 redisson.yml ,主要用于配置redis集群节点链接配置,代码如下:

4)创建Redisson管理对象

Redisson管理对象有2个,分别为 RedissonClient 和 RedissonConnectionFactory ,我们只用在项目的

RedisConfig 中配置一下这2个对象即可,在 RedisConfig 中添加的代码如下:

5)分布式锁实现

2.3.4 队列术

上面我们已经使用过队列术了,队列术在面对零点洪峰流量时,是相当有效,可以直接将所有流量存入到队列中,让后台不用同时处理很多请求,而是从队列中逐个消费逐个处理,上图是实现流程,由于前面抢单已经实现过该流程,所以这里不再重复讲解。

基于Nginx缓存队列术

针对一些特定操作,如果请求并发量极高,我们可以采用Nginx自身的队列术,在上一章我们已经学过了Nginx的代理缓存,其中有一个属性叫 proxy_cache_lock ,该属性的意思是:当多个客户端请求一个缓存中不存在的文件(或称之为一个MISS),只有这些请求中的第一个被允许发送至服务器。其他请求在第一个请求得到满意结果之后在缓存中得到文件。如果不启用 proxy_cache_lock ,则所有在缓存中找不到文件的请求都会直接与服务器通信。

proxy_cache_lock 的作用其实和队列术及其类似,只不过发生的地方以及处理的语言不同而已。我们正好可以使用代理缓存来处理一些查询量大的相同数据,例如查询抢红包Top100就可以用Nginx的代理缓存中proxy_cache_lock 来实现。

后台代码移除分布式锁:

此时只有第1次从后台获取数据,当然这里会请求3次后才会从缓存拿数据,因为有个属性 proxy_cache_min_use 3属性。

3 缓存雪崩解决方案

缓存雪崩介绍

缓存雪崩是指,由于缓存层承载着大量请求,有效的保护了存储层,但是如果缓存层由于某些原因整体不能提供服务,于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。

解决方案

1)缓存高可用

即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务,比如 Redis Sentinel 和 Redis Cluster 都实现了高可用。

2)限流

微服务网关或者Nginx做好限流操作,防止大量请求直接进入后端,使后端载荷过重最后宕机。

3)数据预热

预先去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀,不要同时失效。

4)队列术限流

使用Nginx队列或者MQ队列,缓存用户的请求,让所有相同操作只有1次查询数据库,并将查询的数据加入到缓存中,下次查询直接从缓存中获取数据。

5)加锁

数据操作,如果是带有缓存查询的,均使用分布式锁,防止大量请求直接操作数据库。

6)多级缓存(推荐)

采用多级缓存,Nginx+Redis+MyBatis二级缓存,当Nginx缓存失效时,查找Redis缓存,Redis缓存失效查找MyBatis二级缓存。

4 缓存一致性

用户每次抢完红包,要查看自己抢红包记录,此时需要查询数据库表 money_log ,如果每次都查询 money_log 就会占用大量数据库资源。此时我们应该将数据存储到缓存中,每次查询直接从缓存获取即可。

但现在面临的问题是如果用户抢到了不同的红包,缓存没法及时更新,因此我们需要实现抢红包数据库数据和Redis缓存中的数据同步。

4.1 缓存一致性解决方案

用户每次操作数据库的时候,使用Canal监听数据库指定表的增量变化,在Java程序中消费Canal监听到的增量变化,并在Java程序中实现对Redis缓存或者Nginx缓存的更新。

用户查询的时候,先通过Lua查询Nginx的缓存,如果Nginx缓存没有数据,则查询Redis缓存,Redis缓存如果也没有数据,可以去数据库查询.

4.2 Canal介绍

Canal主要用途是基于 MySQL 数据库增量日志解析,并能提供增量数据订阅和消费,应用场景十分丰富。

github地址:

版本下载地址:

文档地址:

Canal应用场景

1.电商场景下商品实时更新同步到至Elasticsearch、solr等搜索引擎; 2.价格、库存发生变更实时同步到redis; 3.数据库异地备份、数据同步; 4.代替使用轮询数据库方式来监控数据库变更,有效改善轮询耗费数据库资源。

MySQL主从复制原理

MySQL master 将数据变更写入二进制日志( binary log , 其中记录叫做二进制日志事件 binary log events ,可以通过 show binlog events 进行查看)MySQL slave 将 master 的 binary log events 拷贝到它的中继日志( relay log ) 3. MySQL slave 重放 relay log 中事件,将数据变更反映它自己的数据

Canal工作原理

canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal ) 3.canal 解析 binary log 对象(原始为 byte流)4.3 Canal配置

1)开启MySQL的bin-log

开启MySQL的binlog日志功能:

2)Canal安装

这里采用容器安装

配置CanalServer

修改 /home/admin/canal-server/conf/canal.properties ,将它的id属性修改成和mysql数据库中server-id不同的值,如下图:

修改 /home/admin/canal-server/conf/example/instance.properties ,配置要监听的数据库服务地址和监听数据变化的数据库以及表,修改如下:

指定监听数据库表的配置如下 canal.instance.filter.regex :

重启canal:

不要忘了MySQL创建账号并授权:

4.4 同步更新Redis缓存

修改 com.itheima.service.impl.MoneyLogServiceImpl ,添加方法 list(String username) ,代码如下:

创建类 com.itheima.canal.MoneyLogSync ,实现消费Canal中监听到的增量数据,代码如下:

application.yml中配置Canal的地址:

关于微服务中如何消费Canal监听到的数据,参考参考地址:

我们可以测试实现抢单,抢单后,数据会自动同步到Redis缓存中。

4.5 清理Nginx缓存

清理Nginx缓存,可以利用 purge 来清理,请求地址: <; 。

我们现在要用程序访问,这里需要做相关操作,在java代码中访问上面地址即可,在这里大家可以把前面所学的基于Jwt令牌身份安全校验弄进去,实现安全清理缓存,也可以基于Nginx安全配置实现缓存清理操作。

5 RESTful站点安全终极解决方案

当前主流架构都属于微服务架构,主流的开发模式是前后端分离,前后端分离有较强的数据规则,前面针对缓存击穿和穿透解决方案适用于普通项目,不约束项目数据规则,但针对规则如此强的前后端分离开发模式和RESTful的微服务架构,应该是有更优秀的解决方案,不仅限于缓存穿透、缓存击穿问题解决。

解决基于RESTful开发的站点的安全风控解决方案。【缓存穿透】、【缓存击穿】、【缓存雪崩】、【黑白名单】、【定向日志收集】、【防止攻击】、【限流】、【熔断】

5.1 RESTful特性分析

刚才我们做了一个分析,所有前后端分离项目数据规则极强,我们设计了一套如下安全控制解决方案。

我们如果能在Nginx端使用Lua脚本取代Nginx自发执行操作,可以实现很多高级功能。按照上面架构图规则可以过滤很多不安全因素,实现一些高级功能,例如:限流、故障自动切换、缓存击穿、缓存穿透、用户行为收集等,所有能做的事几乎都能做,我们这里拿恶意请求为例,可以制作出如下高质量的功能。

5.2 功能实现分析

1)无效路径收集

如上图,如果我们能在Nginx执行Http请求,并获取Http请求后的结果,如果请求后的结果是404,证明地址无效,我们可以把该请求地址存储到缓存中,标记为无效标识,下次有相同地址请求,直接跳转到404界面,而不需要请求后端微服务。

2)缓存击穿

定期检测缓存过期时间,一旦要过期,立即将请求做队列限流控制。

3)黑白名单过滤

导入黑白名单IP,每次获取用户IP,检测用户IP是否为黑名单IP,一旦为黑名单IP,则直接拒绝访问。

4)异常熔断降级

收集指定路径返回数据,返回code=500的链接指定时间内频率超过N,则将该路径链接存入缓存,一段时间内禁止用户访问,防止大量错误连接导致服务器宕机。

5.3 Lua执行Http请求5.3.1 响应接口设计

1)规范接口设计

响应数据必须是规范的数据,我们可以为后端设计一个响应数据封装对象,代码如下:

2)请求测试

创建测试 RestUserController ,代码如下:

测试: <;

5.3.2 Lua执行Http请求实现

Lua执行Http请求,需要依赖http模块,Http模块下载地址 ,下载该模块后,解压到 /usr/local/openresty/lua-resty-http-master 下,并且在nginx的nginx.conf中指定依赖库地址 lua_package_path "/usr/local/openresty/nginx/lua/?.lua;/usr/local/openresty/lua-resty-http-master/lib/?.lua;/usr/local/openresty/lua-resty-jwt-master/lib/resty?.lua;;";

编写lua脚本用于处理用户请求, resthttp.lua 脚本如下:

修改nginx.conf,添加如下配置:

访问 <; 效果如下:

此时如果我们在 resthttp.lua 织入缓存脚本,可以实现各种条件判断,前面已经实现过相关操作,这里我们就不做演示了。

如果想把地址换成动态的,可以获取用户访问地址,这里提供了一些关于Http参数处理的操作,大家可以参考实现想要的功能。

标签: #nginx缓存怎么清