两个简单的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

posted on 2018-08-03 15:02  小夏coding  阅读(188)  评论(0编辑  收藏  举报

导航