dubbo限流

4 篇文章 0 订阅

欢迎来到梁钟霖个人博客网站。本个人博客网站提供最新的站长新闻,各种互联网资讯。 还提供个人博客模板,最新最全的java教程,java面试题。在此我将尽我最大所能将此个人博客网站做的最好! 谢谢大家,愿大家一起进步! 
 

背景

既然我们已经使用springboot+dubbo 那么也不放继续深入下去了【springcloud确实各种特性很垂涎哈!】

对于大量的请求我们存在一些限流需求【比如发送短信接口==》目前使用http请求】后续存在改造需求

因此来看一下我们如何实现限流吧

http限流

没有上springcloud的zuul之前我们使用nginx/openresty作为反向代理

那么做一些业务无关的限制请求自然在nginx这一层完成

其实存在一些第三方比较好用的网关过滤等【包括限流 验签等等】典型的包括kong orange等

我们直接使用nginx的limitzone来完成。

 
limit_req_zone  $binary_remote_addr  zone=req_one:10m rate=1r/s;

The ngx_http_limit_req_module module (0.7.21) is used to limit the request processing rate per a defined key, in particular, the processing rate of requests coming from a single IP address. The limitation is done using the “leaky bucket” method.

很明显这个表示使用ip进行限流 zone名称为req_one 分配了10m 空间使用漏桶算法 每秒钟允许1个请求

当我们使用的时候只需要使用刚才的名称即可

limit_req zone=req_three burst=20;

这边burst表示可以瞬间超过20个请求 由于没有noDelay参数因此需要排队 如果超过这20个那么直接返回503

dubbo

dubbo提供了多个和请求相关的filter 我们可以看到

ActiveLimitFilter ExecuteLimitFilter TPSLimiterFilter

三个侧重点各有不同

ActiveLimitFilter

@Activate(group = Constants.CONSUMER, value = Constants.ACTIVES_KEY)

很明显这是作用在客户端的 

主要作用如下

控制客户端同样的方法可同时运行的次数【即该方法的并发度】

public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    URL url = invoker.getUrl();
    String methodName = invocation.getMethodName();
    int max = invoker.getUrl().getMethodParameter(methodName, Constants.ACTIVES_KEY, 0);
    RpcStatus count = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());
    if (max > 0) {
        long timeout = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.TIMEOUT_KEY, 0);
        long start = System.currentTimeMillis();
        long remain = timeout;
        int active = count.getActive();
        if (active >= max) {
            synchronized (count) {
                while ((active = count.getActive()) >= max) {
                    try {
                        count.wait(remain);
                    } catch (InterruptedException e) {
                    }
                    long elapsed = System.currentTimeMillis() - start;
                    remain = timeout - elapsed;
                    if (remain <= 0) {
                        throw new RpcException("Waiting concurrent invoke timeout in client-side for service:  "
                                               + invoker.getInterface().getName() + ", method: "
                                               + invocation.getMethodName() + ", elapsed: " + elapsed
                                               + ", timeout: " + timeout + ". concurrent invokes: " + active
                                               + ". max concurrent invoke limit: " + max);
                    }
                }
            }
        }
    }
    try {
        long begin = System.currentTimeMillis();
        RpcStatus.beginCount(url, methodName);
        try {
            Result result = invoker.invoke(invocation);
            RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, true);
            return result;
        } catch (RuntimeException t) {
            RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, false);
            throw t;
        }
    } finally {
        if(max>0){
            synchronized (count) {
                count.notify();
            }
        }
    }
}

很明显当超过了指定的active值之后该请求将等待前面的请求完成【何时结束呢?依赖于该方法的timeout 如果没有设置timeout的话可能就是多个请求一直被阻塞然后等待随机唤醒吧……】

因此要搭配timeout一起使用噢!

ExecuteLimitFilter

@Activate(group = Constants.PROVIDER, value = Constants.EXECUTES_KEY)

很明显这是指在服务端的限制了

这个就简单了

public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    URL url = invoker.getUrl();
    String methodName = invocation.getMethodName();
    int max = url.getMethodParameter(methodName, Constants.EXECUTES_KEY, 0);
    if (max > 0) {
        RpcStatus count = RpcStatus.getStatus(url, invocation.getMethodName());
        if (count.getActive() >= max) {
            throw new RpcException("Failed to invoke method " + invocation.getMethodName() + " in provider " + url + ", cause: The service using threads greater than <dubbo:service executes=\"" + max + "\" /> limited.");
        }
    }
    long begin = System.currentTimeMillis();
    boolean isException = false;
    RpcStatus.beginCount(url, methodName);
    try {
        Result result = invoker.invoke(invocation);
        return result;
    } catch (Throwable t) {
        isException = true;
        if(t instanceof RuntimeException) {
            throw (RuntimeException) t;
        }
        else {
            throw new RpcException("unexpected exception when ExecuteLimitFilter", t);
        }
    }
    finally {
        RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, isException);
    }
}

一旦超出指定的数目直接报错 其实是指在服务端的并行度【需要注意这些都是指的是在单台服务上而不是整个服务集群】

TpsLimitFilter

@Activate(group = Constants.PROVIDER, value = Constants.TPS_LIMIT_RATE_KEY)

同样作用在服务端 目的在于控制一段时间类中的请求数

public class DefaultTPSLimiter implements TPSLimiter {
 
    private final ConcurrentMap<String, StatItem> stats
        = new ConcurrentHashMap<String, StatItem>();
     
    public boolean isAllowable(URL url, Invocation invocation) {
        int rate = url.getParameter(Constants.TPS_LIMIT_RATE_KEY, -1);
        long interval = url.getParameter(Constants.TPS_LIMIT_INTERVAL_KEY,
                                         Constants.DEFAULT_TPS_LIMIT_INTERVAL);
        String serviceKey = url.getServiceKey();
        if (rate > 0) {
            StatItem statItem = stats.get(serviceKey);
            if (statItem == null) {
                stats.putIfAbsent(serviceKey,
                                  new StatItem(serviceKey, rate, interval));
                statItem = stats.get(serviceKey);
            }
            return statItem.isAllowable(url, invocation);
        } else {
            StatItem statItem = stats.get(serviceKey);
            if (statItem != null) {
                stats.remove(serviceKey);
            }
        }
 
        return true;
    }
 
}

默认情况下取得tps.interval字段表示请求间隔 如果无法找到则使用60s 根据tps字段表示允许调用次数

class StatItem {
 
    private String name;
 
    private long lastResetTime;
 
    private long interval;
 
    private AtomicInteger token;
 
    private int rate;
 
    StatItem(String name, int rate, long interval) {
        this.name = name;
        this.rate = rate;
        this.interval = interval;
        this.lastResetTime = System.currentTimeMillis();
        this.token = new AtomicInteger(rate);
    }
 
    public boolean isAllowable(URL url, Invocation invocation) {
        long now = System.currentTimeMillis();
        if (now > lastResetTime + interval) {
            token.set(rate);
            lastResetTime = now;
        }
 
        int value = token.get();
        boolean flag = false;
        while (value > 0 && !flag) {
            flag = token.compareAndSet(value, value - 1);
            value = token.get();
        }
 
        return flag;
    }
 
    long getLastResetTime() {
        return lastResetTime;
    }
     
    int getToken() {
        return token.get();
    }
     
    public String toString() {
        return new StringBuilder(32).append("StatItem ")
            .append("[name=").append(name).append(", ")
            .append("rate = ").append(rate).append(", ")
            .append("interval = ").append(interval).append("]")
            .toString();
    }

使用AtomicInteger表示允许调用的次数 每次调用减少1次当结果小于0之后返回不允许调用

总结

在没有sc之前我们使用这些办法控制我们方法的调用频率【这个都比较基础 如果需要深度定制自然需要再次加入业务 比如某个客户调用多少次 不同的客户次数不同等等逻辑还是需要额外开发!】

 

https://liangzl.com/get-article-detail-1489.html

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值