第1章 多级缓存架构


目录:
  • 目标1:掌握缓存的分类、缓存的淘汰算法、缓存的应用场景、缓存在微服务架构中的使用
  • 目标2:理解双十一架构痛点
  • 目标3:理解多级缓存架构流程
  • 目标4:能实现Redis缓存的扩容、收容
  • 目标5:能实现Redis集群哨兵策略
  • 目标6:能在项目中操作Redis集群
  • 目标7:实现基于Nginx配置浏览器缓存
  • 目标8:实现Nginx代理缓存配置



1 缓存的应用


女朋友问我啥是多级缓存?为啥我会懵!!!
不是因为,我太菜,而是因为你一个姑娘都能问出这个问题,着实不简单啊!!
有一个程序员女朋友,你得多学多少技术啊
果然,爱情的力量是伟大的
以前只是到缓存,没想到还有多级缓存,看来确实需要研究一下了,要不以后在女朋友面前怎么抬的起头来
当然了,如果你没有女朋友,那可以把手机扔了~~~
缓存(cache),原始意义是指访问速度比一般随机存储器(RAM)快的一种高速存储器,设置缓存是为了更好的发挥计算机系统的高性能。
1.1 缓存分类
1)应用级缓存
应用级缓存也就是我们平时写的应用程序中所使用的缓存。在平时程序中一般是按照如下操作流程来实现缓存的操作,首先张三用户读取数据库,并将读取的数据存入到缓存中,其他用户读取的时候,直接从缓存中读取,而不用查询数据库,从而提高程序的执行速度和效率。
2)系统级别缓存
系统级别缓存是抛开我们应用程序之外硬件的缓存操作,例如某些CPU的缓存操作和如下图多级缓存流程类似,CPU在操作数据的时候,先读取1级缓存,1级缓存如果没有数据则读取2级缓存,2级缓存没有数据则读取3级缓存,3级缓存如果没有数据就直接从主存储器(存储指令和数据)读取数据。
1.2 缓存淘汰算法
数据缓存之后,为了避免缓存无限变大,我们需要对缓存进行管理,将一些不用的或者很少用的或者很久没更新的缓存淘汰掉,这里就需要用到一系列淘汰算法了,淘汰算法主要有如下几种:
1)FIFO(先进先出)
数据最先存入缓存,那么也将最先备淘汰掉。
2)LRU(最不常用数据)
判断缓存最近被使用的时间,时间最远被使用的缓存优先淘汰。
3)LFU(最少使用)
一段时间内,被使用的次数最少的数据,优先淘汰掉。
1.3 缓存应用场景
1)频繁查询数据缓存
有一些数据经常被访问,而且变更频率较低,实时性要求不高的数据,可以把它存储到缓存中,每次读取数据直接读缓存即可,从而提升数据的加载速度和系统的性能。
2)列表排序分页数据
一些变更频率较低查询频次较高的列表、分页、排序数据,可以存入到Redis缓存,每次查询分页或者排序的时候,直接从Redis缓存中获取。
3)计数器
网站中用于统计访问频次、在线人数、商品抢购次数等,也可以使用缓存来实现。
4)详情内容
站点中,资讯内容、商品详情等较大变更频率又低的内容,可以采用缓存来加速数据的读取,降低IO操作。
5)分布式Session
实现会话共享的时候,可以使用Session来存储需要共享的会话,从而节省内存空间。
6)热点排名
我们可以使用ZSet来存储热数据,并实现热点数据的排名。
7)发布订阅
用Redis也可以实现发布与订阅,但不推荐,推荐用MQ。
8)分布式锁
可以使用Redisson结合Redis实现分布式锁,Redis实现的分布式锁效率极高,得到了市场的广泛使用。
1.4 微服务架构缓存的使用
在微服务架构中,会大量使用到缓存,如上图:
1.客户端缓存(手机、PC)
2.Nginx缓存
3.微服务网关限流令牌缓存
4.Nacos缓存服务列表、配置文件
5.各大微服务自身也具有缓存
6.数据库查询Query Cache
7.Redis集群缓存
8.Kafka也属于缓存

2 双十一缓存架构设计


一谈到缓存架构,很多人想到的是Redis,但其实整套体系的缓存架构并非只有Redis,而应该是多个层面多个软件结合形成一套非常良性的缓存体系。比如咱们的缓存架构设计就涉及到了多个层面的缓存软件。
2.1 缓存架构设计
(缓存架构设计)
上图是黑马程序员专为双十一大促设计的缓存架构体系,该缓存架构体系借鉴了一线大厂京东缓存架构设计经验,并在京东设计方案上进行了优化,让它更符合一线厂家的需要。上述架构图综合了多种缓存和多层面的缓存设计,从前端页面缓存到代理服务器lvs和nginx缓存,以及后端服务redis缓存,包括缓存数据同步等。
对上述架构,我们来个宏观解说:
1、HTML页面做缓存,浏览器端可以缓存HTML页面和其他静态资源,防止用户频繁刷新对后端造成巨大压力
2、Lvs实现记录不同协议以及不同用户请求链路缓存
3、Nginx这里会做HTML页面缓存配置以及Nginx自身缓存配置
4、数据查找这里用Lua取代了其他语言查找,提高了处理的性能效率,并发处理能力将大大提升
5、数据缓存采用了Redis集群+主从架构,并实现缓存读写分离操作
6、集成Canal实现数据库数据增量实时同步Redis
这里安装6个redis,配置如下:
Redis节点
IP
端口

redis-7001
172.18.0.2
7001

redis-7002
172.18.0.3
7002

redis-7003
172.18.0.4
7003

redis-7004
172.18.0.5
7004

redis-7005
172.18.0.6
7005

redis-7006
172.18.0.7
7006
小知识点(Redis6.0.5):
如果需要安装单机版,需要安装指定的插件
yum -y install gcc gcc-c++ libstdc++-devel

升级gcc
yum -y install centos-release-scl devtoolset-7
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils

切换gcc7
scl enable devtoolset-7 bash

单机版安装
解压redis-6.0.5.tar.gz
tar -xf redis-6.0.5.tar.gz

进入到redis-6.0.5目录
cd redis-6.0.5

编译安装
make
make install PREFIX=/usr/local/server/redis-cluster/redis

redis.conf拷贝到安装bin目录下
cp /usr/local/server/redis-cluster/redis-6.0.5/redis.conf /usr/local/server/redis-cluster/redis/bin/

修改redis.conf更改redis配置:
设置daemonize为yes  让redis支持后台启动
将protected-mode 改为no,远程连接就不需要账号密码了
2.2 集群扩容收容
上面虽然创建了主从复制,但如果手动给节点添加一个从节点,有可能添加从节点,也有可能添加从节点,这是我们想要干的。接下来我们给集群节点添加指定的从节点。
我们安装7007、7008、7009几个Redis节点,然后将7007和7008作为主节点,添加到集群中,7009作为从节点添加到集群中。
Redis节点
IP
端口

redis-7007
172.18.0.8
7007

redis-7008
172.18.0.9
7008

redis-7009
172.18.0.10
7009
基于Docker安装Redis这里编写了一个脚本,安装脚本redis-port.sh如下:
#!/bin/bash
#在/usr/local/server/redis-cluster下生成conf和data目标,并生成配置信息
#换行
echo -e "\n"
#输入信息
read -p "请输入容器端口:" DOCKER_PORT

#输入端口赋值
port=$DOCKER_PORT;

echo -e "$port"

#创建配置文件
mkdir -p ./${port}/conf && PORT=${port} envsubst < ./redis-cluster.tmpl > ./${port}/conf/redis.conf && mkdir -p ./${port}/data;

#创建redis容器
docker run -d -it -p ${port}:${port} -p 1${port}:1${port} -v /usr/local/server/redis-cluster/${port}/conf/redis.conf:/usr/local/etc/redis/redis.conf -v /usr/local/server/redis-cluster/${port}/data:/data --privileged=true --restart always --name redis-${port} --net redis-net --sysctl net.core.somaxconn=1024 redis redis-server /usr/local/etc/redis/redis.conf;
#查找ip
echo  -n "启动$(docker inspect --format '{{ (index .NetworkSettings.Networks "redis-net").IPAddress }}' "redis-${port}")":${port}" 成功!";
echo -e "\n"
我们执行redis-port.sh脚本,实现7007,7008,7009节点安装。
查看集群状态
主节点查看:
./redis-cli -p 7001 cluster nodes|grep master
主节点状态信息如下:
从节点查看:
./redis-cli -p 7001 cluster nodes|grep slave
从上面信息我们可以看出集群关系:
Master:7001        Slave:7005
Master:7002        Slave:7006
Master:7003        Slave:7004
2.2.1 动态添加集群主节点
我们需要给集群节点添加一个主节点,我们需要将192.168.211.141:7007节点添加到192.168.211.141:7001节点所在的集群中,并且添加后作为主节点,添加命令行如下:
./redis-cli --cluster add-node 192.168.211.141:7007 192.168.211.141:7001
命令说明:
将192.168.211.141:7007节点添加到192.168.211.141:7001节点所在的集群中
执行命令后,效果如下:
此时我们可以进入到集群节点之一查看此时的集群状态,我们进入到7002节点中输入cluster node查询,操作方法如下:
进入容器
docker exec -it redis-7002 /bin/bash

进入到redis-cli脚本目录
cd /usr/local/bin

登录7002节点
./redis-cli -p 7002 -c

查询集群状态
cluster nodes
效果如下:
2.2.2 哈希槽分配
从上面的集群节点信息我们可以看出一个问题,其他主节点都有一串数字范围,而刚才添加的7007节点没有这段数字范围,这和Redis集群原理有关系,我们来讲解一下集群的原理,然后实现新增节点哈希槽(这段数字范围)的分配。
(Redis集群原理图)
Redis原理:
(1)Redis Cluster 特性之一是引入了槽的概念。一个redis集群包含 16384 个哈希槽。
(2)集群时,会将16384个哈希槽分别分配给每个Master节点,每个Master节点占16384个哈希槽中的一部分。
(3)执行GET/SET/DEL时,都会根据key进行操作,Redis通过CRC16算法对key进行计算得到该key所属Redis节点。
(4)根据key去指定Redis节点操作数据。
从原理上分析,因为之前7001,7002,7003已经瓜分了16384个哈希槽,所以再增加一个新节点是没有剩余哈希槽分配的,所以新增的7007节点没有分配到哈希槽。我们只能重新分配哈希槽,才能让新增节点分配到一定的哈希槽,重新分配哈希槽后,我们还要考虑之前其他Redis节点中的数据迁移。
重新分配Hash槽
我们将7001,7002,7003中的100个哈希槽挪给7007,命令如下:
./redis-cli --cluster reshard 192.168.211.141:7001 --cluster-from c9687b2ebec8b99ee14fcbb885b5c3439c58827f,80a69bb8af3737bce2913b2952b4456430a89eb3,612e4af8eae48426938ce65d12a7d7376b0b37e3 --cluster-to 443096af2ff8c1e89f1160faed4f6a02235822a7 --cluster-slots 100
命令说明:
将节点
    c9687b2ebec8b99ee14fcbb885b5c3439c58827f
    80a69bb8af3737bce2913b2952b4456430a89eb3
    612e4af8eae48426938ce65d12a7d7376b0b37e3
中的100个哈希槽移动到
    443096af2ff8c1e89f1160faed4f6a02235822a7

参数说明:
--cluster-from:表示slot目前所在的节点的node ID,多个ID用逗号分隔
--cluster-to:表示需要新分配节点的node ID
--cluster-slots:分配的slot数量
将100个哈希槽挪给7007后,我们查询下节点信息:
./redis-cli -p 7001 cluster nodes|grep master
效果如下:
2.2.3 动态添加集群从节点
我们需要往集群中给7007节点添加一个从节点7008,添加从节点的主要目的是提高高可用,防止主节点宕机后该节点无法提供服务。添加从节点命令如下:
./redis-cli --cluster add-node 192.168.211.141:7008 192.168.211.141:7007 --cluster-slave --cluster-master-id 443096af2ff8c1e89f1160faed4f6a02235822a7
命令说明:
将192.168.211.141:7008节点添加到192.168.211.141:7007对应的集群中,并且加入的节点为从节点,对应的主节点id是443096af2ff8c1e89f1160faed4f6a02235822a7
参数说明:
add-node: 后面的分别跟着新加入的slave和slave对应的master
cluster-slave:表示加入的是slave节点
--cluster-master-id:表示slave对应的master的node ID
执行命令后,效果如下:
集群信息查看:
2.2.4 缩容
1)数据迁移
2)哈希槽迁移
3)从节点删除
4)主节点删除
在真实生产环境中,我们也会跟着我们的业务和环境执行缩容处理,比如双十一过后,流量没有那么大了,我们往往会缩容处理,服务器开销。
Redis实现缩容,需要哈希槽重新分配,将需要移除的节点所分配的所有哈希槽值分配给其他需要运行工作的节点,还需要移除该节点的从节点,然后再删除该节点。
移除从节点
移除7007的从节点7008,命令如下:
./redis-cli --cluster del-node 192.168.211.141:7008 98be71d8d6eff6bd40947fa0441e5a821dce20ae
参数说明:
del-node:删除节点,后面跟着slave节点的 ip:portnode ID
删除后,我们再来查看集群节点,此时再无7008节点。
迁移Master的Slot
我们需要将7007节点的哈希槽迁移到7001,7002,7003节点上,仍然用上面用过的redis-cli --cluster reshard语法,命令如下:
第1次迁移:
./redis-cli --cluster reshard 192.168.211.141:7007 --cluster-from 443096af2ff8c1e89f1160faed4f6a02235822a7 --cluster-to 80a69bb8af3737bce2913b2952b4456430a89eb3 --cluster-slots 33 --cluster-yes
命令说明:
将192.168.211.141:7007节点所在集群中443096af2ff8c1e89f1160faed4f6a02235822a7节点的33个哈希槽迁移给80a69bb8af3737bce2913b2952b4456430a89eb3节点,不回显需要迁移的slot,直接迁移。
效果如下:
查看集群节点:
我们再次迁移其他哈希槽到其他节点,将剩余的哈希槽迁移到7002和7003去。
第2次迁移:
./redis-cli --cluster reshard 192.168.211.141:7007 --cluster-from 443096af2ff8c1e89f1160faed4f6a02235822a7 --cluster-to c9687b2ebec8b99ee14fcbb885b5c3439c58827f --cluster-slots 34 --cluster-yes
第3次迁移:
./redis-cli --cluster reshard 192.168.211.141:7007 --cluster-from 443096af2ff8c1e89f1160faed4f6a02235822a7 --cluster-to 612e4af8eae48426938ce65d12a7d7376b0b37e3 --cluster-slots 33 --cluster-yes
集群状态查询:
删除7007主节点
删除节点命令如下:
./redis-cli --cluster del-node 192.168.211.141:7007 443096af2ff8c1e89f1160faed4f6a02235822a7
效果如下:
集群节点查看:



3 Nginx热点缓存管理


为了提升网站的整体性能,我们一般会采用缓存,从宏观层面来说,会采用浏览器缓存和后端缓存,Nginx处于Web网站的服务最外层,而且支持浏览器缓存配置和后端数据缓存,用它来做部分数据缓存,效率更高。
Web缓存是可以自动保存常见文档副本的HTTP 设备。当Web请求抵达缓存时,如果本地有“已缓存的”副本,就可以从本地设备而不是服务器中提取这个文 档。
OpenRestry安装
OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。OpenResty 通过lua脚本扩展nginx功能,可提供负载均衡、请求路由、安全认证、服务鉴权、流量控制与日志监控等服务。
OpenResty® 通过汇聚各种设计精良的 Nginx 模块(主要由 OpenResty 团队自主开发),从而将 Nginx 有效地变成一个强大的通用 Web 应用平台。这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。
关于OpenRestry的学习,大家可以参考:http://openresty.org/cn/
安装依赖库
yum install wget libtermcap-devel ncurses-devel libevent-devel readline-devel pcre-devel gcc openssl openssl-devel per
下载安装包
wget https://openresty.org/download/openresty-1.11.2.5.tar.gz
解压安装
tar -xf openresty-1.11.2.5.tar.gz

cd openresty-1.11.2.5

./configure --prefix=/usr/local/openresty --with-luajit --without-http_redis2_module --with-http_stub_status_module --with-http_v2_module --with-http_gzip_static_module --with-http_sub_module --add-module=/usr/local/server/ngx_cache_purge-2.3/

make && make install
安装完成后,在/usr/local/openrestry/nginx目录下是安装好的nginx。
3.1 浏览器缓存
客户端侧缓存一般指的是浏览器缓存、app缓存等等,目的就是加速各种静态资源的访问,降低服务器压力。
我们通过配置Nginx设置网页缓存信息,从而降低用户对服务器频繁访问造成的巨大压力。我们先配置一个案例,再基于案例去讲解Nginx缓存。
3.1.1 Nginx Web缓存配置
nginx 提供了 expires、etag、if-modified-since 指令来进行浏览器缓存控制。我们使用expires来配置Nginx对网页的缓存。
语法: expires [modified] time;
默认值: expires off;
上下文: http, server, location, if in location
1)上传html
将1.html上传到服务器的/usr/local/server/html目录下。
2)配置nginx
修改/usr/local/openrestry/nginx/conf/nginx.conf文件,配置如下:
server {
    listen       80;
    server_name  localhost;

    location / {
        #静态文件路径
        root   /usr/local/server/html;
        #缓存10秒
        expires 10s;
    }
}
过期时间配置说明
expires 30s;    #30秒
expires 30m;    #30分钟
expires 2h;        #2个小时
expires 30d;    #30天
3.1.2 Http缓存控制头
参数说明:
HTTP 中最基本的缓存机制,涉及到的 HTTP 头字段,包括 Cache-Control, Last-Modified, If-Modified-Since, Etag,If-None-Match 等。

Last-Modified/If-Modified-Since

Etag是服务端的一个资源的标识,在 HTTP 响应头中将其传送到客户端。所谓的服务端资源可以是一个Web页面,也可以是JSON或XML等。服务器单独负责判断记号是什么及其含义,并在HTTP响应头中将其传送到客户端。比如,浏览器第一次请求一个资源的时候,服务端给予返回,并且返回了ETag: "50b1c1d4f775c61:df3" 这样的字样给浏览器,当浏览器再次请求这个资源的时候,浏览器会将If-None-Match: W/"50b1c1d4f775c61:df3" 传输给服务端,服务端拿到该ETAG,对比资源是否发生变化,如果资源未发生改变,则返回304HTTP状态码,不返回具体的资源。

Last-Modified :标示这个响应资源的最后修改时间。web服务器在响应请求时,告诉浏览器资源的最后修改时间。

If-Modified-Since :当资源过期时(使用Cache-Control标识的max-age),发现资源具有 Last-Modified 声明,则再次向web服务器请求时带上头。

If-Modified-Since ,表示请求时间。web服务器收到请求后发现有头 If-Modified-Since 则与被请求资源的最后修改时间进行比对。若最后修改时间较新,说明资源有被改动过,则响应整片资源内容(写在响应消息包体内),HTTP 200;若最后修改时间较旧,说明资源无新修改,则响应 HTTP 304 (无需包体,节省浏览),告知浏览器继续使用所保存的 cache 。

Pragma行是为了兼容 HTTP1.0 ,作用与 Cache-Control: no-cache 是一样的

Etag/If-None-Match
Etag :web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定),如果给定URL中的资源修改,则一定要生成新的Etag值。

If-None-Match :当资源过期时(使用Cache-Control标识的max-age),发现资源具有Etage声明,则再次向web服务器请求时带上头 If-None-Match (Etag的值)。web服务器收到请求后发现有头 If-None-Match 则与被请求资源的相应校验串进行比对,决定返回200304

Etag:
Last-Modified 标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间,如果某些文件会被定期生成,当有时内容并没有任何变化,但 Last-Modified 却改变了,导致文件没法使用缓存有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形 Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-Modified 与 ETag 是可以一起使用的,服务器会优先验证 ETag ,一致的情况下,才会继续比对 Last-Modified ,最后才决定是否返回304。
3.2 代理缓存
用户如果请求获取的数据不是需要后端服务器处理返回,如果我们需要对数据做缓存来提高服务器的处理能力,我们可以按照如下步骤实现:
1、请求Nginx,Nginx将请求路由给后端服务
2、后端服务查询Redis或者MySQL,再将返回结果给Nginx
3、Nginx将结果存入到Nginx缓存,并将结果返回给用户
4、用户下次执行同样请求,直接在Nginx中获取缓存数据
3.2.1 proxy_cache
proxy_cache 是用于 proxy 模式(一般也可称为反代)的缓存功能,proxy_cache 在 Nginx 配置的 http 段、server 段(location 段)中分别写入不同的配置。http 段中的配置用于定义 proxy_cache 空间,server 段中的配置用于调用 http 段中的定义,启用对 server 的缓存功能。
属性使用说明
proxy_cache_path:
Example
    proxy_cache_path /usr/local/openresty/nginx/cache levels=1:2 keys_zone=openresty_cache:10m max_size=10g inactive=60m use_temp_path=off;

【作用】指定缓存存储的路径,缓存存储在/usr/local/openresty/nginx/cache目录

【levels=1:2】设置一个两级目录层次结构存储缓存,在单个目录中包含大量文件会降低文件访问速度,因此我们建议对大多数部署使用两级目录层次结构。如果 levels 未包含该参数,Nginx 会将所有文件放在同一目录中。

【keys_zone=openresty_cache:10m】设置共享内存区域,用于存储缓存键和元数据,例如使用计时器。拥有内存中的密钥副本,Nginx 可以快速确定请求是否是一个 HIT 或 MISS 不必转到磁盘,从而大大加快了检查速度。1 MB 区域可以存储大约 8,000 个密钥的数据,因此示例中配置的 10 MB 区域可以存储大约 80,000 个密钥的数据。

【max_size=10g】设置缓存大小的上限。它是可选的; 不指定值允许缓存增长以使用所有可用磁盘空间。当缓存大小达到限制时,一个称为缓存管理器的进程将删除最近最少使用的缓存,将大小恢复到限制之下的文件。

【inactive=60m】指定项目在未被访问的情况下可以保留在缓存中的时间长度。在此示例中,缓存管理器进程会自动从缓存中删除 60 分钟未请求的文件,无论其是否已过期。默认值为 10 分钟(10m)。非活动内容与过期内容不同。Nginx 不会自动删除缓存 header 定义为已过期内容(例如 Cache-Control:max-age=120)。过期(陈旧)内容仅在指定时间内未被访问时被删除。访问过期内容时,Nginx 会从原始服务器刷新它并重置 inactive 计时器。

【use_temp_path=off】表示NGINX会将临时文件保存在缓存数据的同一目录中。这是为了避免在更新缓存时,磁盘之间互相复制响应数据,我们一般关闭该功能。
proxy_cache:
设置是否开启对后端响应的缓存,如果开启的话,参数值就是zone的名称,比如:proxy_cache openresty_cache;
proxy_cache_valid:
针对不同的response code设定不同的缓存时间,如果不设置code,默认为200,301,302,也可以用any指定所有code

Example:
    【proxy_cache_valid 200 304 10s;】所有200/304响应的数据都缓存10秒。
    【proxy_cache_valid any 1m;】所有请求响应的值都缓存1分钟。
proxy_cache_min_uses:
指定在多少次请求之后才缓存响应内容,这里表示将缓存内容写入到磁盘。

Example:
    【proxy_cache_min_uses 3;】同一个请求达到了3次,才将缓存写入磁盘。
proxy_cache_lock:
默认不开启,开启的话则每次只能有一个请求更新相同的缓存,其他请求要么等待缓存有数据要么限时等待锁释放;nginx 1.1.12才开始有。
proxy_cache_key:
缓存文件的唯一key,可以根据它实现对缓存文件的清理操作
3.2.2 缓存操作
我们在nginx.conf中添加如下配置:
#缓存配置
proxy_cache_path /usr/local/openresty/nginx/cache levels=1:2 keys_zone=openresty_cache:10m max_size=10g inactive=60m use_temp_path=off;

server {
    listen       80;
    server_name  localhost;

    #html配置
    location  ~ \.html {
        #静态文件路径
        root   /usr/local/server/html;
        #缓存10秒
        expires 10s;
    }

    #非html配置
    location / {
        #启用缓存openresty_cache
        proxy_cache openresty_cache;
        #针对指定请求缓存
        #proxy_cache_methods GET;
        #设置指定请求会缓存
        proxy_cache_valid 200 304 10s;
        #最少请求1次才会缓存
        proxy_cache_min_uses 3;
        #如果并发请求,只有第1个请求会去服务器获取数据
        #proxy_cache_lock on;
        #唯一的key
        proxy_cache_key $host$uri$is_args$args;
        proxy_pass http://myip:18081;
    }
}
此时/usr/local/openresty/nginx/cache目录下只有1个temp文件夹。
我们执行3次请求<http://192.168.211.141/user/wangwu>,可以发现此时多了一些其他目录,这些目录就是存储每个请求对应的缓存。
3.3 Nginx缓存清理
很多时候我们如果不想等待缓存的过期,想要主动清除缓存,可以采用第三方的缓存清除模块清除缓存 nginx_ngx_cache_purge。安装nginx的时候,需要添加purge模块,purge模块我们已经下载了,在/usr/local/server目录下,添加该模块--add-module=/usr/local/server/ngx_cache_purge-2.3/,这一个步骤我们在安装OpenRestry的时候已经实现了。
安装好了后,我们配置一个清理缓存的地址:
#清理缓存
location ~ /purge(/.*) {
    #清理缓存
    proxy_cache_purge openresty_cache $host$1$is_args$args;
}
每次请求$host$key就可以删除指定缓存,我们可以先查看缓存文件的可以:
此时再查看缓存文件,已经删除了。

4 Nginx限流


一般情况下,双十一很多查询的并发量是比较大的,即使 有了多级缓存,当用户不停的刷新页面的时候,也是没有必要的,另外如果有恶意的请求 大量达到,也会对系统造成影响。
而限流就是保护措施之一。
4.1 限流理解
限流和生活中很多现实场景类似,如下场景:
•水坝泄洪,通过闸口限制洪水流量(控制流量速度)。•办理银行业务:所有人先领号,各窗口叫号处理。每个窗口处理速度根据客户具体业务而定,所有人排队等待叫号即可。若快下班时,告知客户明日再来(拒绝流量)•火车站排队买票安检,通过排队 的方式依次放入。(缓存带处理任务)
而程序限流,主要是控制应对高并发和恶意操作的一种技术手段。
4.2 Nginx限流实战
nginx提供两种限流的方式:
•一是控制速率•二是控制并发连接数
4.2.1 速率限流
控制速率的方式之一就是采用漏桶算法。
(1)漏桶算法实现控制速率限流
漏桶(Leaky Bucket)算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率.示意图如下:
(2)nginx的配置
配置示如下:
创建限流缓存空间:
#限流设置
limit_req_zone $binary_remote_addr zone=contentRateLimit:10m rate=2r/s;
配置限流:
#使用限流配置
limit_req zone=contentRateLimit;
参数说明:
binary_remote_addr 是一种key,表示基于 remote_addr(客户端IP) 来做限流,binary_ 的目的是压缩内存占用量。
zone:定义共享内存区来存储访问信息, contentRateLimit:10m 表示一个大小为10M,名字为contentRateLimit的内存区域。1M能存储16000 IP地址的访问信息,10M可以存储16W IP地址访问信息。
rate 用于设置最大访问速率,rate=10r/s 表示每秒最多处理10个请求。Nginx 实际上以毫秒为粒度来跟踪请求信息,因此 10r/s 实际上是限制:每100毫秒处理一个请求。这意味着,自上一个请求处理完后,若后续100毫秒内又有请求到达,将拒绝处理该请求.我们这里设置成2 方便测试。
如果恶意一直刷新会出现如下现象:
(3)处理突发流量
上面例子限制 2r/s,如果有时正常流量突然增大,超出的请求将被拒绝,无法处理突发流量,可以结合 burst 参数使用来解决该问题。
例如,如下配置表示:
limit_req zone=contentRateLimit burst=4;
burst 译为突发、爆发,表示在超过设定的处理速率后能额外处理的请求数,当 rate=10r/s 时,将1s拆成10份,即每100ms可处理1个请求。
此处,**burst=4 **,若同时有4个请求到达,Nginx 会处理第一个请求,剩余3个请求将放入队列,然后每隔500ms从队列中获取一个请求进行处理。若请求数大于4,将拒绝处理多余的请求,直接返回503.
不过,单独使用 burst 参数并不实用。假设 burst=50 ,rate依然为10r/s,排队中的50个请求虽然每100ms会处理一个,但第50个请求却需要等待 50 * 100ms即 5s,这么长的处理时间自然难以接受。
因此,burst 往往结合 nodelay 一起使用。
例如:如下配置:
limit_req zone=contentRateLimit burst=4 nodelay;
如上表示:
平均每秒允许不超过2个请求,突发不超过4个请求,并且处理突发4个请求的时候,没有延迟,等到完成之后,按照正常的速率处理。
完整配置如下:
4.2.2 控制并发量(连接数)
ngx_http_limit_conn_module 提供了限制连接数的能力。主要是利用limit_conn_zone和limit_conn两个指令。
利用连接数限制 某一个用户的ip连接的数量来控制流量。
注意:并非所有连接都被计算在内 只有当服务器正在处理请求并且已经读取了整个请求头时,才会计算有效连接。此处忽略测试。
配置语法:
Syntax:    limit_conn zone number;
Default: —;
Context: http, server, location;
(1)配置限制固定连接数
如下,配置如下:
配置限流缓存空间:
#根据IP地址来限制,存储内存大小10M
limit_conn_zone $binary_remote_addr zone=addr:1m;
location配置:
limit_conn addr 2;
参数说明:
limit_conn_zone $binary_remote_addr zone=addr:10m;  表示限制根据用户的IP地址来显示,设置存储地址为的内存大小10M

limit_conn addr 2;   表示 同一个地址只允许连接2次。
(2)限制每个客户端IP与服务器的连接数,同时限制与虚拟服务器的连接总数。
限流缓存空间配置:
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
location配置
limit_conn perip 10;#单个客户端ip与服务器的连接数.
limit_conn perserver 100; #限制与服务器的总连接数
-END-
文末福利
  • 2021年3月5日前端基础班、2021年2月28日Java火热报名中
  • 约起小伙伴,扫码领福利哦~


   ▲加小姐姐订阅最新免费学习资源


您需要登录后才可以回帖 登录 | 加入黑马