有哪些实现限流的技术方案?
1.计数器(固定窗口限流+滑动窗口限流)
固定窗口限流:
固定窗口算法指每个单位时间相对隔离,一个单位区间的请求量统计跟其他单位区间的请求量统计完全独立。当一个单位时间过期,自动进入下一个时间阶段重新进行计数,固定窗口计数器算法逻辑图如下,固定窗口计数器算法相对简单,但会存在临界问题(用户流量并不会像我们所期望的匀速请求,而是可能在某个时间点集中爆发,在一个窗口快结束的时候来了大量的请求,然后消耗完这个窗口的次数,然后在下个窗口刚开始又来了大量的请求,消耗完这个窗口的次数,这样在这个很短的时间间隔内,处理的请求数会超过>单个窗口的次数限制)
2.滑动窗口限流:
为了解决固定窗口算法的临界问题,有了滑动窗口限流,这种算法每次统计当前时间往前推一个单位的时间,统计这个单位时间内的请求量,是否超出阀值。(可以使用Redis中的有序集合sorted set来实现,就是每个member存储请求的信息,member的score就是请求发生的时间戳,每次接受请求时,先去查询有序集合的大小,如果数量超出,就删除时间戳过期的(也就是超出当前时间窗口的请求),删除后还是超出,就限流,否则就不限流,并且有一个定时任务定时删除时间戳过期的。)
但是由于每次都需要统计单位时间的请求量,开销远大于固定窗口算法,所以在真实的业务环境中需要慎重使用滑动窗口算法。
2.令牌桶算法:
令牌以固定速率产生,并缓存到令牌桶中,令牌桶放满时,多余的令牌被丢弃。请求要获取令牌才能被处理,获取不到令牌时,请求被缓存。(Guava框架中的RateLimiter类就是令牌桶算法的解决方案)
3.漏桶算法:
可以认为这种算法有一定固定长度的队列,来缓存请求,消费端每次以固定的速率从队列取出请求进行处理,当队列满了时,请求就只能丢弃。
区别:
漏桶算法和令牌桶算法都是限制数据的平均传输速率,区别在于,令牌桶算法能够在限制数据的平均传输速率的同时还允许某种程度的突发传输。
就是假设漏桶每秒钟处理10个请求,使用漏桶法,我们设置桶的大小为10,流出的速率为0.1s流出一个请求,也就是每0.1s能处理一个请求,在第一个0.1s内只处理了1个请求,后面的9个请求都在桶内等待,每过0.1s,处理一个。
而使用令牌桶的话,我们会设置每1s向桶中放入10个令牌,假设之前没有请求,桶内有10个令牌,那么来10个请求都可以获得令牌,然后直接进行处理,来第11个请求,如果桶内没有令牌才需要等待。
Nginx官方版本限制IP的连接和并发分别有两个模块:
1.限制请求数
limit_req_zone
用来限制单位时间内的请求数,即速率限制,默认采用的漏桶算法 ,也就是超出限制的请求会丢弃,如果设置了burst就会变成令牌桶算法,使用一个长度为burst的队列来存储这些超出限制的请求。(nginx使用的漏桶算法)例子:限制访问速度
http { limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; server { location /search/ { limit_req zone=one burst=5 nodelay; } } 点击复制代码复制出错复制成功
- 这个例子中主要是限制每个IP请求的次数,limit_req_zone中的参数
- 第一个参数:$binary_remote_addr 表示通过用户请求的ip地址这个标识来做限制,使用binary_remote_addr 而不是remote_addr目的是使用二进制格式的ip地址,可以缩小内存占用量。
- 第二个参数:zone=one:10m表示生成一个大小为10M,名字为one的内存区域,用来存储访问的频次信息。
- 第三个参数:rate=1r/s表示允许相同标识的客户端的访问频次,这里限制的是每秒1次只允许请求一次,还可以有比如30r/m的。
- limit_req_zone中的参数
- 第二个参数:burst=5,burst是爆发的意思,这个配置的意思是设置一个大小为5的缓冲区当有大量请求(爆发)过来时,超过了访问频次限制的请求可以先放到这个缓冲区内先进行处理,处理完之后后面的请求还是需要等待生成令牌的速度跟上了之后才能处理。
- 第三个参数:nodelay,如果没有设置,那么超出缓存区的所有请求会进行排队等待等待,如果设置nodelay,超过访问频次而且缓冲区也满了的时候就会直接返回503。
默认情况下,当某个客户端超过它的限流,NGINX用503(Service Temporarily Unavailable状态码来响应。使用limit_req_status\指令设置一个不同的状态码,例如limit_req_status 444;
实战案例
limit_req_zone $binary_remote_addr zone=mylimiter:10M rate=2r/s; server { location /search { limit_req_zone=mylimiter; } }点击复制代码复制出错复制成功
这个例子是限制同一ip地址请求/search接口时,限制每秒转发2个请求,也就是500ms转发一个。如果瞬间同一ip来了6个请求,只有第一个请求会成功,后面5个请求会被拒绝,因为nginx的限流统计是基于毫秒的,设置的速度是2r/s,转换一下就是500ms内单个IP只允许通过1个请求,从501ms开始才允许通过第二个请求。(此时也是默认的漏桶的算法)
limit_req_zone $binary_remote_addr zone=mylimiter:10M rate:2r/s; server { location /search { limit_req_zone=mylimiter burst=4; } }点击复制代码复制出错复制成功
多了一个burst参数,可以认为burst是一个缓冲队列,可以将多余的请求缓存请求,这些请求也不会立即处理,只能等着慢慢被处理。假设同一ip来了6个请求,第一个请求会被立即处理,然后会有4个请求被缓存在队列中,然后每0.5s会从队列中取出一个进行处理。而最后一个请求,由于队列满了,是会被拒绝,返回503。
limit_req_zone $binary_remote_addr zone=mylimiter:10M rate:2r/s; server { location /search { limit_req_zone=mylimiter burst=4 nodelay; } }点击复制代码复制出错复制成功
多了一个nodelay参数,就是快速转发的意思,不会增加每秒能处理的请求数,但是可以让处于burst队列中请求,就会立即被后台worker处理。假设同一ip来了6个请求,那么第一个请求会被立即处理,然后会有4个请求被缓存在队列中,缓存队列中的请求也是立即被转发处理,第六个请求则是由于队列满了,被拒绝,返回503。