關鍵資源 關鍵資源總是有限的,也就意味著處理能力也有限,所以當面對大量業務時,為了保障自己能夠有序的提供服務最經濟的做法就是限制同一時間處理的事務數。比如銀行的工作人員,一個工作人員同時只能為一個客戶服務,來多了根本處理不了,不光是一種浪費而且有可以造成混亂的局面導致工作人員無法工作。 網路請求漏斗 ...
關鍵資源
關鍵資源總是有限的,也就意味著處理能力也有限,所以當面對大量業務時,為了保障自己能夠有序的提供服務最經濟的做法就是限制同一時間處理的事務數。比如銀行的工作人員,一個工作人員同時只能為一個客戶服務,來多了根本處理不了,不光是一種浪費而且有可以造成混亂的局面導致工作人員無法工作。
網路請求漏斗
越上層的伺服器處理的事務越輕,應付請求的能力也越強,也就意味著同一請求越上層處理時間越短。為了有效的保護下層伺服器,就需要對發送給下層的請求量做限流,在下層伺服器可接受的範圍內。否則就可能會出現下層伺服器資源耗盡而無法正常提供服務的情況。
限流場景
服務端限流
如果在服務端做限流,無論有多少個客戶端,總的提供能力是固定的(感謝@ xuanbg提出的評論,指出服務端也可以對客戶端做精準的判斷,後續我再想想實現方案),所以不會因為客戶端數量過多而導致資源不足,因為處理不過來的請求會被阻塞等待獲取資源。
缺點
缺點也比較明顯,由於服務提供者整體設置了最大限流數,此時所有的客戶端共用同一份限流數據,那麼有可能導致有的服務能分配到資源有些服務請求分配不到資源導致無法請求的情況。
客戶端限流
客戶端限流解決上服務端限流提到的問題,它能保證每個客戶端都能得到響應。但是從其它方面考慮,必須針對不同的客戶端做不同的限流策略:
- 請求量大,但時效性不高,此時將限流數控制小一些會比較合適
- 請求量大,但時效性高,此時將限流數適當調高
- 響應時間長,即慢介面,適當降低
- 主流業務,核心業務,適當調高
- 非主流業務,適當降低
- ......
缺點
-
如果客戶端的數量不固定,那麼有可能導致客戶端數量過多造成大量請求打到服務端導致處理不了的結果,所以需要嚴格監控客戶端的調用情況。
-
配置複雜,需要針對每個客戶端做相對精準的判斷
RPC實現
限流
這裡指的限流是指每秒從客戶端提交到服務端的請求數量。
過濾器機制可參考:簡易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()); } ... }
RpcInvocation增加限流參數
public interface RpcInvocation { ... int getMaxExecutesCount(); }
AbstractInvoker修改buildRpcInvocation方法
從request對象中獲取限流參數,傳遞給RpcInvocation對象。
public RpcInvocation buildRpcInvocation(RpcRequest request){ RpcInvocation rpcInvocation=new RpcInvocation() { ... @Override public int getMaxExecutesCount() { return request.getMaxExecutesCount(); } }; return rpcInvocation; }
AccessLimitFilter
- 修改令牌管理器
按介面分配令牌管理器,令牌管理器存儲在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(); } } }
- 修改invoke方法
將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
文中代碼是依賴上述項目的,如果有不明白的可下載源碼