dubbo限流

既然我们已经使用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之前我们使用这些办法控制我们方法的调用频率【这个都比较基础 如果需要深度定制自然需要再次加入业务 比如某个客户调用多少次 不同的客户次数不同等等逻辑还是需要额外开发!】

猜你喜欢

转载自blog.csdn.net/qq_29234631/article/details/85244083