nginx 、springMvc(非分布式)相应的限流、消峰
互联网服务赖以生存的根本是流量, 产品和运营会经常通过各种方式来为应用倒流,比如淘宝的双十一等,如何让系统在处理高并发的同时还是保证自身系统的稳定,
通常在最短时间内提高并发的做法就是加机器, 但是如果机器不够怎么办? 那就需要做业务降级或系统限流。
流量控制中用的比较多的三个算法就是令牌桶、漏桶、计数器。
一、令牌桶限流(TokenBucket)
令牌桶算法的基本过程如下:
- 每秒会有 r 个令牌放入桶中,或者说,每过 1/r 秒桶中增加一个令牌。
- 桶中最多存放 b 个令牌,如果桶满了,新放入的令牌会被丢弃。
- 当一个 n 字节的数据包到达时,消耗 n 个令牌,然后发送该数据包。
- 如果桶中可用令牌小于 n,则该数据包将被缓存或丢弃。
二、漏桶限流(LeakBucket)
漏桶算法强制一个常量的输出速率而不管输入数据流的突发性
(1)当输入空闲时,该算法不执行任何动作.就像用一个底部开了个洞的漏桶接水一样, 水进入到漏桶里, 桶里的水通过下面的孔以固定的速率流出
(2)当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率.如下图所示:
漏桶和令牌桶比较
“漏桶算法”能够强行限制数据的传输速率,而“令牌桶算法”在能够限制数据的平均传输数据外,还允许某种程度的突发传输。
在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的上限,因此它适合于具有突发特性的流量。
三、计数器限流
有时我们还会使用计数器来进行限流,主要用来限制一定时间内的总并发数。计数器限流方法可以通过缓存实现计数器,假如以秒为单位进行限流,
过期时间为1秒,每次请求计数加1,超过每秒允许的最大请求数请求数阀值将被丢弃。
四、nginx 实现漏桶
#以用户二进制IP地址,定义三个漏桶,滴落速率1-3req/sec,桶空间1m,1M能保持大约16000个(IP)状态
limit_req_zone $binary_remote_addr zone=qps1:1m rate=1r/s;
limit_req_zone $binary_remote_addr zone=qps2:1m rate=2r/s;
limit_req_zone $binary_remote_addr zone=qps3:1m rate=3r/s; server { #速率qps=1,峰值burst=5,延迟请求
#严格按照漏桶速率qps=1处理每秒请求
#在峰值burst=5以内的并发请求,会被挂起,延迟处理
#超出请求数限制则直接返回503
#客户端只要控制并发在峰值[burst]内,就不会触发limit_req_error_log
# 例1:发起一个并发请求=6,拒绝1个,处理1个,进入延迟队列4个:
#time request refuse sucess delay
#00:01 6 1 1 4
#00:02 0 0 1 3
#00:03 0 0 1 2
#00:04 0 0 1 1
#00:05 0 0 1 0
location /delay {
limit_req zone=qps1 burst=5;
} #速率qps=1,峰值burst=5,不延迟请求
#加了nodelay之后,漏桶控制一段时长内的平均qps = 漏桶速率,允许瞬时的峰值qps > 漏桶qps
#所以峰值时的最高qps=(brust+qps-1)=5
#请求不会被delay,要么处理,要么直接返回503
#客户端需要控制qps每秒请求数,才不会触发limit_req_error_log
# 例2:每隔5秒发起一次达到峰值的并发请求,由于时间段内平均qps=1 所以仍然符合漏桶速率:
#time request refuse sucess
#00:01 5 0 5
#00:05 5 0 5
#00:10 5 0 5
# 例3:连续每秒发起并发请求=5,由于时间段内平均qps>>1,超出的请求被拒绝:
#time request refuse sucess
#00:01 5 0 5
#00:02 5 4 1
#00:03 5 4 1 location /nodelay {
limit_req zone=qps1 burst=5 nodelay;
} }
五、redis
local key = KEYS[] --限流KEY(一秒一个)
local limit = tonumber(ARGV[]) --限流大小
local current = tonumber(redis.call('get', key) or "")
if current + > limit then --如果超出限流大小
redis.call("INCRBY", key,"") -- 如果不需要统计真是访问量可以不加这行
return
else --请求数+1,并设置2秒过期
redis.call("INCRBY", key,"")
if tonumber(ARGV[]) > - then
redis.call("expire", key,tonumber(ARGV[])) --时间窗口最大时间后销毁键
end
return
end
lua脚本返回值比较奇怪,用java客户端接受返回值,只能使用Long,没有去深究。这个脚本只需要传入key(url+时间戳/预设时间窗口大小),便可以实现限流。
java调用lua脚本
/**
* Created by xujingfeng on 2017/3/13.
* <p>
* 基于redis lua脚本的线程安全的计数器限流方案
* </p>
*/
public class RedisRateLimiter { /**
* 限流访问的url
*/
private String url; /**
* 单位时间的大小,最大值为 Long.MAX_VALUE - 1,以秒为单位
*/
final Long timeUnit; /**
* 单位时间窗口内允许的访问次数
*/
final Integer limit; /**
* 需要传入一个lua script,莫名其妙redisTemplate返回值永远是个Long
*/
private RedisScript<Long> redisScript; private RedisTemplate redisTemplate; /**
* 配置键是否会过期,
* true:可以用来做接口流量统计,用定时器去删除
* false:过期自动删除,时间窗口过小的话会导致键过多
*/
private boolean isDurable = false; public void setRedisScript(RedisScript<Long> redisScript) {
this.redisScript = redisScript;
} public void setRedisTemplate(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
} public String getUrl() {
return url;
} public void setUrl(String url) {
this.url = url;
} public boolean isDurable() {
return isDurable;
} public void setDurable(boolean durable) {
isDurable = durable;
} public RedisRateLimiter(Integer limit, Long timeUnit) {
this.timeUnit = timeUnit;
Assert.isTrue(timeUnit < Long.MAX_VALUE - 1);
this.limit = limit;
} public RedisRateLimiter(Integer limit, Long timeUnit, boolean isDurable) {
this(limit, timeUnit);
this.isDurable = isDurable;
} public boolean acquire() {
return this.acquire(this.url);
} public boolean acquire(String url) {
StringBuffer key = new StringBuffer();
key.append("rateLimiter").append(":")
.append(url).append(":")
.append(System.currentTimeMillis() / 1000 / timeUnit);
Integer expire = limit + 1;
String convertExpire = isDurable ? "-1" : expire.toString();
return redisTemplate.execute(redisScript, Arrays.asList(key.toString()), limit.toString(), convertExpire).equals(1l);
} }
5.spring mvc
RateLimiter
我们可以使用 Guava 的 RateLimiter 来实现基于令牌桶的流量控制。RateLimiter 令牌桶算法的单桶实现,RateLimiter 对简单的令牌桶算法做了一
些工程上的优化,具体的实现是 SmoothBursty。需要注意的是,RateLimiter 的另一个实现 SmoothWarmingUp,就不是令牌桶了,而是漏桶算法。
SmoothBursty 有一个可以放 N 个时间窗口产生的令牌的桶,系统空闲的时候令牌就一直攒着,最好情况下可以扛 N 倍于限流值的高峰而不影响
后续请求,就像三峡大坝一样能扛千年一遇的洪水.



作者:Lewe
链接:http://www.jianshu.com/p/7170edcd9239
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
nginx 、springMvc(非分布式)相应的限流、消峰的更多相关文章
- 基于令牌桶算法实现的SpringBoot分布式无锁限流插件
本文档不会是最新的,最新的请看Github! 1.简介 基于令牌桶算法和漏桶算法实现的纳秒级分布式无锁限流插件,完美嵌入SpringBoot.SpringCloud应用,支持接口限流.方法限流.系统限 ...
- 【Nginx】实现负载均衡、限流、缓存、黑白名单和灰度发布,这是最全的一篇了!
写在前面 在<[高并发]面试官问我如何使用Nginx实现限流,我如此回答轻松拿到了Offer!>一文中,我们主要介绍了如何使用Nginx进行限流,以避免系统被大流量压垮.除此之外,Ngin ...
- Nginx配置之负载均衡、限流、缓存、黑名单和灰度发布
一.Nginx安装(基于CentOS 6.5) 1.yum命令安装 yum install nginx –y(若不能安装,执行命令yum install epel-release) 2. 启动.停止和 ...
- spring boot:用redis+lua实现基于ip地址的分布式流量限制(限流/简单计数器算法)(spring boot 2.2.0)
一,限流有哪些环节? 1,为什么要限流? 目的:通过对并发请求进行限速或者一个时间单位内的的请求进行限速,目的是保护系统可正常提供服务,避免被压力太大无法响应服务. 如果达到限制速率则可以采取预定的处 ...
- Go 分布式令牌桶限流 + 兜底策略
上篇文章提到固定时间窗口限流无法处理突然请求洪峰情况,本文讲述的令牌桶线路算法则可以比较好的处理此场景. 工作原理 单位时间按照一定速率匀速的生产 token 放入桶内,直到达到桶容量上限. 处理请求 ...
- SpringCloud系列——限流、熔断、降级
前言 分布式环境下,服务直接相互调用,一个复杂的业务可能要调用多个服务,例如A -> B -> C -> D,当某个服务出现异常(调用超时.调用失败等)将导致整个流程阻塞崩溃,严重的 ...
- 常用限流算法与Guava RateLimiter源码解析
在分布式系统中,应对高并发访问时,缓存.限流.降级是保护系统正常运行的常用方法.当请求量突发暴涨时,如果不加以限制访问,则可能导致整个系统崩溃,服务不可用.同时有一些业务场景,比如短信验证码,或者其它 ...
- 限流&熔断的考量
限流的原则,是尽量在流量源头限,并且是需要依据现有团队所掌握的技能来. 如上最左侧便是主要流量的来源入口,首先就要限制的地方就是slb节点的income流量 slb节点的流量特点是啥?加限流怎么加?限 ...
- 用nginx实现分布式限流
1.前言 一般对外暴露的系统,在促销或者黑客攻击时会涌来大量的请求,为了保护系统不被瞬间到来的高并发流量给打垮, 就需要限流 . 本文主要阐述如何用nginx 来实现限流. 听说 Hystrix 也可 ...
随机推荐
- Socket 相关资料(随笔)
由于项目上的 http 请求量较大,项目上性能跟不上.于是考虑把 短连接的 http 换成 长连接的tcp 形式 试试效果. 先 研究了一下 长连接方式.就是要用到 socket 方面的知识. pac ...
- PI SQL 语句
insert [piarchive]..[picomp2](tag,time,value) values('ppnie_test','t',100) INSERT into pipoint..clas ...
- Java8学习笔记(六)--Optional
前言 身为一名Java程序员,大家可能都有这样的经历:调用一个方法得到了返回值却不能直接将返回值作为参数去调用别的方法.我们首先要判断这个返回值是否为null,只有在非空的前提下才能将其作为其他方法的 ...
- 设置Linux打开文件句柄/proc/sys/fs/file-max和ulimit -n的区别
max-file 表示系统级别的能够打开的文件句柄的数量.是对整个系统的限制,并不是针对用户的. ulimit -n 控制进程级别能够打开的文件句柄的数量.提供对shell及其启动的进程的可用文件句柄 ...
- Linux Platform驱动模型(三) _platform+cdev
平台总线是一种实现设备信息与驱动方法相分离的方法,利用这种方法,我们可以写出一个更像样一点的字符设备驱动,即使用cdev作为接口,平台总线作为分离方式: xjkeydrv_init():模块加载函数 ...
- Spring.NET依赖注入框架学习--简单对象注入
Spring.NET依赖注入框架学习--简单对象注入 在前面的俩篇中讲解了依赖注入的概念以及Spring.NET框架的核心模块介绍,今天就要看看怎么来使用Spring.NET实现一个简单的对象注入 常 ...
- 【Vue】---编写Vue插件流程---【巷子】
一.在Vue中编写插件流程 1.创建组件 components/message.vue <template> <div class="message" v-if= ...
- Linux 创建文件系统及挂载文件系统流程详解(转)
作者:北南南北 来自: LinuxSir.Org 摘要:本文对新增硬盘,切割硬盘,创建硬盘分区,为硬盘分区创建文件系统,以及加载文件系统的流程做总结性论述:主要是为初学者弄清楚这一操作过程:本文涉及f ...
- ML.NET 0.8特性简介
本周.NET生态圈内的更新源源不断,除了.NET Core 2.2,ASP.NET Core 2.2和Entity Framework Core 2.2之外,ML.NET 0.8也一并登上舞台. 新的 ...
- Codeforces 977F - Consecutive Subsequence - [map优化DP]
题目链接:http://codeforces.com/problemset/problem/977/F 题意: 给定一个长度为 $n$ 的整数序列 $a[1 \sim n]$,要求你找到一个它最长的一 ...