两个简单的API限流实现方案

最近的工作中需要对我们提供的一个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"

posted @ 2020-12-14 20:12  姚春辉  阅读(497)  评论(0编辑  收藏  举报