pigeon服务限流
pigeon支持4个纬度的限流策略,当客户端请求达到服务端统计的限流阈值时,会抛出RejectedException。具体限流策略包括:
- 应用限流:限制某个客户端应用的最大QPS
- 应用方法限流:限制某个客户端应用访问某个服务方法的最大QPS
- 单机总限流:限制服务端单机的最大QPS
- 方法线程数限流:限制单个方法最大访问线程数,避免因为单个方法异常流量过大阻塞线程池,导致其他方法没有线程去执行导致饥饿。
流量统计原理
限流实现源码定义在GatewayProcessFilter,是pigeon服务端处理客户端RPC请求拦截器链中的一员。在拦截器方法invoke的入口和出口,有专门的计数器会对请求处理的流入流出进行统计,相关代码行实现为:
try {
// 流量流入计数
ProviderStatisticsHolder.flowIn(request);
if (Constants.MESSAGE_TYPE_SERVICE == request.getMessageType()) {
// 省略具体的限流检测逻辑,在下面展开分析
// ……代码省略
// 交由下一个拦截器处理
response = handler.handle(invocationContext);
return response;
}
} finally {
// 请求方法处理计数减一
if (Constants.MESSAGE_TYPE_SERVICE == request.getMessageType() && enableMethodThreadsLimit) {
decrementRequest(requestMethod);
}
// 流量流出,异步和手动响应需要在别的地方处理
if (!(Constants.REPLY_MANUAL || invocationContext.isAsync())) {
ProviderStatisticsHolder.flowOut(request);
}
}
流量的流入流出逻辑主要通过ProviderStatisticsHolder的flowIn和flowOut方法实现:
public static void flowIn(InvocationRequest request) {
// messageType=MESSAGE_TYPE_SERVICE & statEnable配置允许
if (checkRequestNeedStat(request)) {
// app level,根据app name从appCapacityBuckets中获取,如果没有则初始化一个
ProviderCapacityBucket barrel = getCapacityBucket(request);
if (barrel != null) {
barrel.flowIn(request);
}
// method level,根据requestMethod从methodCapacityBuckets中获取,如果没有则初始化一个
final String requestMethod = request.getServiceName() + "#" + request.getMethodName();
ProviderCapacityBucket methodBarrel = getCapacityBucket(requestMethod);
if (methodBarrel != null) {
methodBarrel.flowIn(request);
}
// method app level,基于特定方法从methodAppCapacityBuckets拿到指定桶Map,再根据请求app拿到具体桶,中间如果没有都会初始化一个
ProviderCapacityBucket methodAppBarrel = getMethodAppCapacityBucket(request);
if (methodAppBarrel != null) {
methodAppBarrel.flowIn(request);
}
// global level 全局流量桶
globalCapacityBucket.flowIn(request);
}
}
public static void flowOut(InvocationRequest request) {
// messageType=MESSAGE_TYPE_SERVICE & statEnable配置允许
if (checkRequestNeedStat(request)) {
// app level
ProviderCapacityBucket barrel = getCapacityBucket(request);
if (barrel != null) {
barrel.flowOut(request);
}
// method level
final String requestMethod = request.getServiceName() + "#" + request.getMethodName();
ProviderCapacityBucket methodBarrel = getCapacityBucket(requestMethod);
if (methodBarrel != null) {
methodBarrel.flowOut(request);
}
// method app level
ProviderCapacityBucket methodAppBarrel = getMethodAppCapacityBucket(request);
if (methodAppBarrel != null) {
methodAppBarrel.flowOut(request);
}
// global level
globalCapacityBucket.flowOut(request);
}
}
从上面的流量统计中,可以看到流量桶都是基于ProviderCapacityBucket实现的,并且对应有4种类型流量桶变量:
- appCapacityBuckets:统计指定应用流量
- methodCapacityBuckets: 统计指定服务方法流量,服务方法通过
serviceName + "#" + methodName
定义,不在当前限流逻辑中使用。 - methodAppCapacityBuckets:统计指定服务方法下,特定应用请求的流量
- globalCapacityBucket: 全局流量桶
流量桶定义
流量桶的实现类为ProviderCapacityBucket,具体以三个纬度的成员变量来存储统计流量:
// 流经流量桶的总请求量
private AtomicInteger requests = new AtomicInteger();
// 秒级请求量,容量固定为60,对应一分钟每秒
private Map<Integer, AtomicInteger> totalRequestsInSecond = new ConcurrentHashMap<Integer, AtomicInteger>();
// 分钟级请求量,容量固定为60,对应一小时每分钟
private Map<Integer, AtomicInteger> totalRequestsInMinute = new ConcurrentHashMap<Integer, AtomicInteger>()