最近的工作中需要对我们提供的一个API进行限流来保证服务的稳定行。
参考网络,提出了两个简单的方案:
1, Ngnix限流
Nginx在架构中起到请求转发与负载均衡器的作用。外部req首先到Nginx监听的80端口,然后Nginx将req交给到监听8080端口的APP服务器处理。处理结果再经由Nginx返回给调用方。
Nginx限流的配置:(/usr/local/etc/nginx/nginx.conf)
#user nobody;
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
# Rate limitation conf
limit_req_zone $binary_remote_addr zone=mylimit:1m rate=1r/s;
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# rate limitation and redirection to APP
location ~* /(inventories|prices) {
limit_req zone=mylimit;
proxy_pass http://localhost:8080;
}
}
include servers/*;
}
其中,
limit_req_zone $binary_remote_addr zone=mylimit:1m rate=1r/s;
将流量限制为 1QPS,如调用方超过该限制则返回 503
重启nginx: sudo nginx -s reload
参考:https://www.nginx.com/blog/rate-limiting-nginx/
2, Redis 计数器
原理: 每个req都对redis中生命周期为一个时间单位的计数器(key:callLimit)加1,如果单位时间内的访问次数超过限制,则拒绝❌所有后来的请求直到下一个单位时间开始。
public Mono<ServerResponse> req_handle(ServerRequest request) {
if (callLimitCheck()){
return getResp(request){
… business logics to handle the req …
};
} else {
return ServerResponse.ok().body(Mono.just(“Over call limit!”), String.class);
}
}
private boolean callLimitCheck() {
String callLimit = this.template.opsForValue().get("callLimit");
if(callLimit != null && Integer.valueOf(callLimit)>30) {
return false;
}
DefaultRedisScript<String> script = new DefaultRedisScript<>(
"local current current = redis.call('incr',KEYS[1]) if tonumber(current) == 1 then redis.call('expire', KEYS[1], 60) end");
script.setResultType(String.class);
List<String> keys = new ArrayList<>();
keys.add("callLimit");
this.template.execute(script, keys);
return true;
}
上面的代码将有效的QPS限制为 0.5/s ( 30/m).
其中为了避免race condition,将 incr 与 expire两个操作写到一个Lua脚本中实现原子性。
"local current current = redis.call('incr',KEYS[1]) if tonumber(current) == 1 then redis.call('expire', KEYS[1], 60) end"
参考:https://redis.io/commands/incrby