两个简单的API限流实现方案
1, Ngnix限流
Nginx在架构中起到请求转发与负载均衡器的作用。外部req首先到Nginx监听的80端口,然后Nginx将req交给到监听8080端口的APP服务器处理。处理结果再经由Nginx返回给调用方。
Nginx限流的配置:(/usr/local/etc/nginx/nginx.conf)
1 #user nobody; 2 worker_processes 1; 3 4 events { 5 worker_connections 1024; 6 } 7 8 http { 9 include mime.types; 10 default_type application/octet-stream; 11 12 sendfile on; 13 keepalive_timeout 65; 14 15 # Rate limitation conf 16 limit_req_zone $binary_remote_addr zone=mylimit:1m rate=1r/s; 17 server { 18 listen 80; 19 server_name localhost; 20 21 location / { 22 root html; 23 index index.html index.htm; 24 } 25 26 error_page 500 502 503 504 /50x.html; 27 location = /50x.html { 28 root html; 29 } 30 31 # rate limitation and redirection to APP 32 location ~* /(inventories|prices) { 33 limit_req zone=mylimit; 34 proxy_pass http://localhost:8080; 35 } 36 } 37 include servers/*; 38 }
其中, limit_req_zone $binary_remote_addr zone=mylimit:1m rate=1r/s;
将流量限制为 1QPS,如调用方超过该限制则返回 503
重启nginx: sudo nginx -s reload
2, Redis 计数器
原理: 每个req都对redis中生命周期为一个时间单位的计数器(key:callLimit)加1,如果单位时间内的访问次数超过限制,则拒绝所有后来的请求直到下一个单位时间开始。
1 public Mono<ServerResponse> req_handle(ServerRequest request) { 2 if (callLimitCheck()){ 3 return getResp(request){ 4 … business logics to handle the req … 5 }; 6 } else { 7 return ServerResponse.ok().body(Mono.just(“Over call limit!”), String.class); 8 } 9 } 10 11 private boolean callLimitCheck() { 12 String callLimit = this.template.opsForValue().get("callLimit"); 13 if(callLimit != null && Integer.valueOf(callLimit)>30) { 14 return false; 15 } 16 DefaultRedisScript<String> script = new DefaultRedisScript<>( 17 "local current current = redis.call('incr',KEYS[1]) if tonumber(current) == 1 then redis.call('expire', KEYS[1], 60) end"); 18 script.setResultType(String.class); 19 List<String> keys = new ArrayList<>(); 20 keys.add("callLimit"); 21 this.template.execute(script, keys); 22 return true; 23 }
上面的代码将有效的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://blog.csdn.net/qijin2016/article/details/79284553