分布式限流
分布式限流
为什么需要限流
在互联网应用中,我们的系统会面临一个重大的挑战,那就是大流量高并发访问,比如∶天猫双十一、京东618、秒杀、抢购促销等,这些都是典型的大流量高并发场景。
短时间内巨大的访问流量,我们如何让系统在处理高并发的同时还能保证自身系统的稳定?有人说,增加机器即可,但是如果加机器也不够怎么办?而且我们也不能无限制地添加机器,我们的服务资源总是有限的,在有限的资源下,我们要应对这种高并发大流量访问,就不得不采取一些其他措施来保护我们的后端服务系统,比如缓存、异步,降级、限流、静态化等;
我们今天要给大家介绍的是面对高并发系统,我们经常会采用的一个策略:限流;
什么是限流
限流在我们的系统中指的是∶在高并发场景下对高并发访问或请求进行限速来保护我们的系统,一旦达到我们限制的速度则可以∶
- 拒绝服务(提示友好的信息或者跳转到错误提示页);
- 排队或等待(比如秒杀)
- 降级(返回默认数据)。
所以限流也就是对请求进行限速,比如10r/s,即每秒只允许10个请求,这样就限制了请求的速度﹔限流,归根结底就是在一定频率上进行量的限制。
我们今天主要介绍限流技术;
1、对稀缺资源的秒杀、抢购;
2、对数据库的高并发读写操作,比如下单、瞬间往数据库插入大量的数据等;
限流可以说是处理高并发问题的利器,有了限流就不用担心瞬间高峰流量压垮系统服务或服务雪崩,[最终做到有损服务而不是不服务﹔
当然限流要评估好、测试好,否则会导致正常流量访问也被限流了,这样会有损用户体验,引起用户抱怨甚至投诉﹔
实现限流的方案
使用nginx接入层限流
Nginx中使用ngx_http_limit_req_module
模块来限制的访问频率,在nginx.conf配置文件中可以使用limit_req_zone命令及limit_req命令限制请求速率;
API网关限流
j继承ZuulFilter写一个filter,并使用google的guava库的RateLimiter实现令牌桶限流。
private static final Ratelimiter rateLimiter = RateLimiter.create(1):
//尝试获取合牌
boolean token = ratelimiter.tryAcquire();
if (!token) {
//没有拿到合牌,那么就不能访问
ctx.setsendzuulResponse(false);
ctx.setResponsestatuscode(401);
ctx.addzuulResponseHeader( " content-type" , "text/html;charset=utf-8");
ctx.setResponseBody("非法访问");
}
也可以使用spring-cloud-zuul-ratelimite
https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit
使用Redis+Lua脚本限流
分布式限流最关键的是要将限流服务做成全局的,可以采用redis+lua 技术进往实现,通过这种技术可以实现高并发和高性能的限流。Lua是一种轻量小巧的脚本编程语言,用标准C语言编写并以源代码形式开放,其设计目的是为了嵌入到应用程序中,为应用程序提供灵活的扩展和定制功能。
local key = KEYS[1] --限流KEY
local limit = tonumber(ARGV[1]) --限流大小10
local current = tonumber(redis.call( 'get', key) or "o")if current + 1 > limit then --如果超出限流大小
return 0 --被限流了
else--请求数+1,并设置2秒过期
redis.call( "INCRBY" , key,"1")
redis.call( "expire" , key ,"2")
return 1 --没有被限流了,可以正常访问
end
redisscript = new DefaultRedisscript<List>();
redisscript.setResultType(List.class) ;
redisscript.setscriptsource(new ResourceScriptsource(new ClassPathResource("limit.lua")));
string key = "ip:" + system.currentTimelillis()/1000;
List<string> keyList = Lists.newArrayList(key);
//调用脚本并执行
List result = stringRedisTemplate.execute(redisscript,keyList,string.valueof(value));
system.out.println("1ua脚本执行结果:" + result);
使用Nginx+lua(OpenResty)
常见的限流算法
计数器法
它是限流算法中最简单粗暴,也最容易的一种算法,比如我们要求某一个接口1分钟内的请求不能超过60次,我们可以在开始时设置一个计数器,每次请求,该计数器+1如果该计数器的值大于60并且与第一次请求的时间间隔在1分钟内,那么说明请求过多,如果该请求与第一次请求的时间间隔大于1分钟,并且该计数器的值还在限流范围内,那么重置该计数器;
漏桶算法
维基百科:https://en.wikipedia.org/wiki/Leaky_,bucket
漏桶算法的思路,水(请求)先进入到漏桶里,漏桶以恒定的速度流出,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。
令牌桶算法
维基百科:https://en.wikipedia.org/wiki/Token_bucket
令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。
Guava框架提供了令牌桶算法实现( Google ) ,可直接拿来使用,使用Guava框架的RateLimiter类即可创建一个令牌桶限流器,比如设置每秒放置令牌数为5个,那么RateLimiter 对象就可以保证1秒内不会放入超过5个令牌,并且以固定速率进行放置令牌,达到平滑输出的效果。