關鍵資源老是有限的,也就意味着處理能力也有限,因此當面對大量業務時,爲了保障本身可以有序的提供服務最經濟的作法就是限制同一時間處理的事務數。好比銀行的工做人員,一個工做人員同時只能爲一個客戶服務,來多了根本處理不了,不光是一種浪費並且有能夠形成混亂的局面致使工做人員沒法工做。html
越上層的服務器處理的事務越輕,應付請求的能力也越強,也就意味着同一請求越上層處理時間越短。爲了有效的保護下層服務器,就須要對發送給下層的請求量作限流,在下層服務器可接受的範圍內。不然就可能會出現下層服務器資源耗盡而沒法正常提供服務的狀況。java
若是在服務端作限流,不管有多少個客戶端,總的提供能力是固定的(感謝@ xuanbg提出的評論,指出服務端也能夠對客戶端作精準的判斷,後續我再想一想實現方案),因此不會由於客戶端數量過多而致使資源不足,由於處理不過來的請求會被阻塞等待獲取資源。git
缺點github
缺點也比較明顯,因爲服務提供者總體設置了最大限流數,此時全部的客戶端共享同一份限流數據,那麼有可能致使有的服務能分配到資源有些服務請求分配不到資源致使沒法請求的狀況。web
客戶端限流解決上服務端限流提到的問題,它能保證每一個客戶端都能獲得響應。可是從其它方面考慮,必須針對不一樣的客戶端作不一樣的限流策略:canvas
缺點ruby
若是客戶端的數量不固定,那麼有可能致使客戶端數量過多形成大量請求打到服務端致使處理不了的結果,因此須要嚴格監控客戶端的調用狀況。服務器
配置複雜,須要針對每一個客戶端作相對精準的判斷markdown
限流網絡
這裏指的限流是指每秒從客戶端提交到服務端的請求數量。
過濾器機制可參考:簡易RPC框架-過濾器機制
public @interface RpcReference {
boolean isSync() default true;
/**
* 客戶端最大併發數
* @return
*/
int maxExecutesCount() default 10;
}
須要修改RpcProxy類,構造函數中增長服務引用註解參數,而後在invoke方法中從服務引用註解中獲取限流參數傳遞給request對象。
public RpcProxy(Class<T> clazz,ReferenceConfig referenceConfig,RpcReference reference) {
this.clazz = clazz;
this.referenceConfig=referenceConfig;
this.reference=reference;
this.isSync=reference.isSync();
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
...
if (this.reference != null) {
request.setMaxExecutesCount(this.reference.maxExecutesCount());
}
...
}
public interface RpcInvocation {
...
int getMaxExecutesCount();
}
從request對象中獲取限流參數,傳遞給RpcInvocation對象。
public RpcInvocation buildRpcInvocation(RpcRequest request){
RpcInvocation rpcInvocation=new RpcInvocation() {
...
@Override
public int getMaxExecutesCount() {
return request.getMaxExecutesCount();
}
};
return rpcInvocation;
}
按接口分配令牌管理器,令牌管理器存儲在map中共享。若是未初始化則進行令牌管理器的初始化,若是已經初始化則直接申請令牌。
static class AccessLimitManager{
private final static Object lock=new Object();
private final static Map<String,RateLimiter> rateLimiterMap= Maps.newHashMap();
public static void acquire(RpcInvocation invocation){
if(!rateLimiterMap.containsKey(invocation.getClassName())) {
synchronized (lock) {
if(!rateLimiterMap.containsKey(invocation.getClassName())) {
final RateLimiter rateLimiter = RateLimiter.create(invocation.getMaxExecutesCount());
rateLimiterMap.put(invocation.getClassName(), rateLimiter);
}
}
}
else {
RateLimiter rateLimiter=rateLimiterMap.get(invocation.getClassName());
rateLimiter.acquire();
}
}
}
將invocation參數傳遞給acquire方法。
public Object invoke(RpcInvoker invoker, RpcInvocation invocation) {
logger.info("before acquire,"+new Date());
AccessLimitManager.acquire(invocation);
Object rpcResponse=invoker.invoke(invocation);
logger.info("after acquire,"+new Date());
return rpcResponse;
}
這裏配置每秒一個請求
@RpcReference(maxExecutesCount = 1)
private ProductService productService;
以下圖所示,每次請求相隔了一秒,達到了限流請求的目的。
以上只支持客戶端接口級別的限流配置,能夠再單首創建一個方法級的註解來配置相關參數。
服務端限流盡管有它的缺點,但爲了更好的保護服務提供者,須要結合多種業務場景來配合客戶端限流一塊兒完善,取長補短共同發揮做用。
https://github.com/jiangmin168168/jim-framework
文中代碼是依賴上述項目的,若是有不明白的可下載源碼