学网站建设 去哪里,工程承包合同协议书,收录优美图片官网,深圳网站seo设计本文已收录至Github#xff0c;推荐阅读 #x1f449; Java随想录 文章目录限流算法计数器算法滑动窗口漏桶算法令牌桶算法限流算法实现Guava RateLimiter实现限流令牌预分配预热限流Nginx 限流limit_connlimit_req黑白名单限流这篇文章来讲讲限流#xff0c;在高并发系统中…本文已收录至Github推荐阅读 Java随想录 文章目录限流算法计数器算法滑动窗口漏桶算法令牌桶算法限流算法实现Guava RateLimiter实现限流令牌预分配预热限流Nginx 限流limit_connlimit_req黑白名单限流这篇文章来讲讲限流在高并发系统中限流是必不可少的限流可以保证一部分的请求得到正常的响应是一种自我保护的措施。限流可以保证使用有限的资源提供最大化的服务能力按照预期流量提供服务超过的部分将会拒绝服务、排队或等待、降级等处理。首先先来了解下几种限流算法。
限流算法
计数器算法
计数器算法是限流算法里最简单也是最容易实现的一种算法。
举个例子我们规定接口A在1分钟内访问次数不能超过1000个。我们可以设置一个计数器对固定时间窗口1分钟进行计数每有一个请求计数器就1如果请求数超过了阈值则舍弃该请求当时间窗口结束时重置计数器为0。 计数器算法虽然简单但是有一个十分致命的问题那就是临界问题。
假设有一个用户他在0:59时瞬间发送了1000个请求并且1:01又发送了1000个请求那么其实用户在 2秒里面瞬间发送了2000个请求。用户通过在时间窗口的重置节点处突发请求 可以瞬间超过我们的速率限制。用户有可能利用这个漏洞卡Bug瞬间压垮我们的应用。
缺点没有办法防止时间范围临界点突发大流量很可能在时间范围交界处被大量请求直接打到降级影响后续服务。
滑动窗口
滑动窗口算法解决了上诉计数器算法的缺点。计数器的时间窗口是固定的而滑动窗口的时间窗口是动态的。 整个红色的矩形框表示一个时间窗口在我们的例子中一个时间窗口就是一分钟。然后我们将时间窗口进行划分比如图中我们就将滑动窗口划成了6格所以每格代表的是10秒钟。每过10秒钟我们的时间窗口就会往右滑动一格。每一个格子都有自己独立的计数器比如当一个请求在0:35秒的时候到达那么0:30~0:39对应的计数器就会加1。
那么滑动窗口怎么解决刚才的临界问题的呢我们可以看上图0:59到达的1000个请求会落在灰色的格子中而1:01到达的请求会落在橘黄色的格子中。当时间到达1:00时我们的窗口会往右移动一格那么此时时间窗口内的总请求数量一共是2000个超过了限定的1000个所以此时能够检测出来触发限流。
当滑动窗口的格子划分的越多那么滑动窗口的滚动就越平滑限流的统计就会越精确。
缺点滑动窗口无法平滑控制请求流量仅能控制时间段内请求总量宏观来看时间轴上的请求数量波形可能出现较大的波动。
漏桶算法
说到漏桶算法的时候我们脑中先构思出一幅图一个水桶桶底下有一个小孔水以固定的频率流出水龙头以任意速率流入水当水超过桶则”溢出“。 漏桶算法的话保证了固定的流出速率这是漏桶算法的优点也可以说是缺点。始终恒定的处理速率有时候并不一定是好事情对于突发的请求洪峰在保证服务安全的前提下应该尽最大努力去响应这个时候漏桶算法显得有些呆滞最终可能导致水位”溢出“请求被丢弃。
缺点无法应对突发流量由于处理速度恒定当大量请求到来时用户等待时间长用户体验差。
令牌桶算法
对于很多应用场景来说除了要求能够限制数据的平均传输速率外还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了令牌桶算法更为适合。
令牌桶算法的原理是系统以恒定的速率产生令牌然后把令牌放到令牌桶中令牌桶有一个容量当令牌桶满了的时候再向其中放令牌那么多余的令牌会被丢弃当想要处理一个请求的时候需要从令牌桶中取出一个令牌如果此时令牌桶中没有令牌那么则拒绝该请求。 缺点令牌桶的数量生成的速度需要根据以往的系统性能以及用户习惯等经验的累积来判断实际限流数难以预知。
限流算法实现
Guava RateLimiter实现限流
引入依赖
dependencygroupIdcom.google.guava/groupIdartifactIdguava/artifactIdversion31.1-jre/version
/dependency下面是一个使用的简单例子
import com.google.common.util.concurrent.RateLimiter;public class RateLimiterTest {public static void main(String[] args) {RateLimiter rateLimiter RateLimiter.create(1); //创建一个每秒产生一个令牌的令牌桶for (int i 1; i 5; i) {double waitTime rateLimiter.acquire(i); //一次获取i个令牌System.out.println(acquire: i waitTime: waitTime);}}
}结果acquire:1 waitTime:0.0
acquire:2 waitTime:0.995081
acquire:3 waitTime:1.998054
acquire:4 waitTime:2.999351
acquire:5 waitTime:3.999224
可以发现等待时间差不多间隔都是1秒。 RateLimiter是个抽象类子类SmoothRateLimiter又做了层抽象SmoothRateLimiter有两个子类SmoothBursty和SmoothWarmingUp。
SmoothBursty 令牌的生成速度恒定。使用 RateLimiter.create(double permitsPerSecond) 创建的是 SmoothBursty 实例。SmoothWarmingUp令牌的生成速度持续提升直到达到一个稳定的值。WarmingUp顾名思义就是有一个热身的过程。使用 RateLimiter.create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) 时创建就是 SmoothWarmingUp 实例其中 warmupPeriod 就是热身达到稳定速度的时间。
SmoothWarmingUp可以理解为是进阶版的SmoothBursty。
令牌预分配
RateLimiter 使用令牌桶算法会进行令牌的累积令牌会被预先分配。
public class RateLimiterTest {public static void main(String[] args) {RateLimiter r RateLimiter.create(5);while (true) {System.out.println(get 5 tokens: r.acquire(5) s);System.out.println(get 1 tokens: r.acquire(1) s);System.out.println(get 1 tokens: r.acquire(1) s);System.out.println(get 1 tokens: r.acquire(1) s);System.out.println(end);/*** output:* get 5 tokens: 0.0s* get 1 tokens: 0.996766s 滞后效应需要替前一个请求进行等待* get 1 tokens: 0.194007s* get 1 tokens: 0.196267s* end* get 5 tokens: 0.195756s* get 1 tokens: 0.995625s 滞后效应需要替前一个请求进行等待* get 1 tokens: 0.194603s* get 1 tokens: 0.196866s*/}}
}RateLimiter 由于会累积令牌所以可以应对突发流量。有一个请求会直接请求5个令牌但是由于此时令牌桶中有累积的令牌足以快速响应。 RateLimiter 在没有足够令牌发放时采用滞后处理的方式也就是前一个请求获取令牌所需等待的时间由下一次请求来承受也就是代替前一个请求进行等待。
预热限流
RateLimiter 的 SmoothWarmingUp 是带有预热期的平滑限流它启动后会有一段预热期逐步将分发频率提升到配置的速率。
public class RateLimiterTest {public static void main(String[] args) {RateLimiter r RateLimiter.create(2, 3, TimeUnit.SECONDS);while (true) {System.out.println(get 1 tokens: r.acquire(1) s);System.out.println(get 1 tokens: r.acquire(1) s);System.out.println(get 1 tokens: r.acquire(1) s);System.out.println(get 1 tokens: r.acquire(1) s);System.out.println(end);/*** output:* get 1 tokens: 0.0s* get 1 tokens: 1.329289s* get 1 tokens: 0.994375s* get 1 tokens: 0.662888s 上边三次获取的时间相加正好为3秒* end* get 1 tokens: 0.49764s 正常速率0.5秒一个令牌* get 1 tokens: 0.497828s* get 1 tokens: 0.49449s* get 1 tokens: 0.497522s*/}}
}创建一个平均分发令牌速率为2预热期为3秒。令牌桶一开始并不会0.5秒发一个令牌而是频率越来越高在3秒钟之内达到原本设置的频率以后就以固定的频率输出。
介绍几个重要的参数
abstract class SmoothRateLimiter extends RateLimiter {//当前存储令牌数double storedPermits;//最大存储令牌数double maxPermits;//添加令牌时间间隔double stableIntervalMicros;private long nextFreeTicketMicros;
}通过Debug我们可以看到SmoothBursty方法的最大令牌数被设置成了maxBurstSeconds * permitsPerSecond而maxBurstSeconds默认是1。 而 SmoothWarmingUp最大令牌数的计算方法要复杂的多。 Nginx 限流
对于Nginx接入层限流可以使用 Nginx自带的两个模块连接数限流模块ngx_http _limit_conn_module和漏桶算法实现的请求限流模块ngx_http_limit_req_module。
limit_conn 用来对某个key对应的总的网络连接数进行限流可以按照如IP、域名维度进行限流。limit_req用来对某个key对应的请求的平均速率进行限流有两种用法平滑模式delay和允许突发模式nodelay。
limit_conn
limit_conn是对某个key对应的总的网络连接数进行限流。可以按照IP来限制IP维度的总连接数或者按照服务域名来限制某个域名的总连接数。但是记住不是每个请求连接都会被计数器统计只有那些被Nginx处理的且已经读取了整个请求头的请求连接才会被计数器统计。
http {limit_conn_zone $binary_remote_addr zoneaddr:10m;limit_conn_log_level error;limit_conn_status 503 ;server {location /limit {limit_conn addr l;}}
}limit_conn要配置存放key和计数器的共享内存区域和指定key的最大连接数。此处指定的最大连接数是1表示Nginx最多同时并发处理1个连接addr就是限流key对应上文 zoneaddr。limit_conn_zone用来配置限流key及存放key对应信息的共享内存区域大小。此处的key是**binaryremoteaddr∗∗表示IP地址也可以使用∗∗binary_remote_addr**表示IP地址也可以使用**binaryremoteaddr∗∗表示IP地址也可以使用∗∗server_name**作为key来限制域名级别的最大连接数。limit_conn_status配置被限流后返回的状态码默认返回503。limit_conn_log_level配置记录被限流后的日志级别默认error级别。
limit_req
limit_req 是漏桶算法实现用于对指定key 对应的请求进行限流比如按照 IP维度限制请求速率。配置示例如下
http {limit_req_zone $binary_remote_addr zoneone:10m rate1r/s;limit_conn_log_level error;limit_conn_status 503;server {location /limit {limit_req zoneone burst20 nodelay;}}
}limit_req 和 limit_conn 的配置类似。
limit_req配置限流区域上面的参数会让nginx 每个IP一秒钟只处理一个请求。burst burst 参数定义了超出 zone 指定速率的情况下客户端还能发起多少请求超出速率的请求将会被放入队列我们将队列大小设置为20。这意味着如果从一个给定 IP 地址发送 21 个请求Nginx 会立即将第一个请求发送到上游服务器群然后将余下 20 个请求放在队列中。然后每1秒转发一个排队的请求只有当传入请求使队列中排队的请求数超过 20 时Nginx 才会向客户端返回 503。nodelay配置 burst 参数将会使通讯更流畅但是可能会不太实用因为该配置会使站点看起来很慢。在上面的示例中队列中的第 20 个包需要等待 20 秒才能被转发此时返回给客户端的响应可能不再有用。要解决这个情况可以在 burst 参数后添加 nodelay 参数。使用 nodelay 参数当一个请求到达“太早”时只要在队列中能分配位置Nginx 将立即转发这个请求。将队列中的该位置标为”taken”(占据)并且不会被释放以供另一个请求使用直到一段时间后才会被释放。假设如前所述队列中有 20 个空位从给定的 IP 地址发出的 21 个请求同时到达。Ngin x会立即转发这个 21 个请求并且标记队列中占据的 20 个位置然后每 1秒释放一个位置。如果是25个请求同时到达Nginx 将会立即转发其中的 21 个请求标记队列中占据的 20 个位置并且返回 503 状态码来拒绝剩下的 4 个请求。如果希望不限制两个请求间允许间隔的情况下实施“流量限制”nodelay 参数是很实用的。limit_req_zone配置限流key、存放key对应信息的共享内存区域大小、固定请求速率。此处指定的key是“$binary_remote_addr”表示IP地址。10m表示共享内存的大小16000 个 IP 地址的状态信息大约需要 1MB所以示例中区域可以存储 160000 个 IP 地址。limit_conn_status配置被限流后返回的状态码默认返回503。limit_conn_log_level配置记录被限流后的日志级别默认级别为error。
黑白名单限流
geo $limit {default 1;10.0.0.0/8 0;192.168.0.0/64 0;
}
map $limit $limit_key {0 ;1 $binary_remote_addr;
}
limit_req_zone $limit_key zonereq_zone:10m rate5r/s;
server {location / {limit_req zonereq_zone burst10 nodelay;}
}geo 指令将给在白名单中的 IP 地址对应的 $limit 变量分配一个值 0给其它不在白名单中的分配一个值 1。然后我们使用一个映射将这些值转为 key。
白名单内 IP 地址的**$limit_key变量被赋值为空字符串不在白名单内的被赋值为客户端的 IP 地址。当limit_req_zone**后的第一个参数是空字符串时不会应用“流量限制”所以白名单内的 IP 地址不会被限制。其它所有 IP 地址都会被限制到每秒 5 个请求。
而要做出网站黑名单就有可能要屏蔽一堆ip但是如果将其放在nginx.conf文件夹下既不美观也不利于管理因此需要单独写出一个conf文件然后在nginx.conf中使用 include标签引用它。
如果我们不是要限流而是要直接实现黑名单禁止访问网站的话。可以使用allow和deny标签。
server{listen: 80;server_name www.baidu.com;allow all; #允许访问所有的ipdeny 172.0.0.1; #禁止 172.0.0.1 访问
}
可以配合shell脚本然后把脚本加入crontab定时任务就可以实现动态添加黑名单。
#!/bin/bash
#取最近5w条数据
tail -n50000 /usr/local/nginx/logs/access.log \
#过滤需要的信息行ip等
|awk {print $1,$12} \
#过滤爬虫
|grep -i -v -E google|yahoo|baidu|msnbot|FeedSky|sogou|360|bing|soso|403|admin \
#统计
|awk {print $1}|sort|uniq -c|sort -rn \
#超过1000加入黑名单
|awk {if($11000)print deny $2;} /usr/local/nginx/conf/blockip.conf
#重启nginx生效
/usr/local/nginx/sbin/nginx -s reload本篇文章就到这里感谢阅读如果本篇博客有任何错误和建议欢迎给我留言指正。