Ribbon2核心設計分析

luyiisme發表於2017-06-05

Ribbon 提供基於軟體的負載均衡方式與目標叢集中的機器進行通訊。這裡忽略狀態統計部分,健康檢查的邏輯部分。

下面是個典型的使用方式開始,先指定一些目標伺服器地址,

//sample-client.ribbon.listOfServers=www.taobao.com:80,www.baidu.com:80,www.sina.com:80

然後藉助ribbon 封裝的 httpClient 來傳送請求(訪問站點首頁),迴圈20次是希望列印出負載均衡的效果。

...
RestClient client = (RestClient) ClientFactory.getNamedClient("sample-client");  
HttpRequest request = HttpRequest.newBuilder().setUri(new URI("/")).build();
for (int i = 0; i < 20; i++)  {
  HttpResponse response = client.executeWithLoadBalancer(request);
  System.out.println("Status code for " + response.getRequestedURI() + "  :" + response.getStatus());
}

下面按 Ribbon 的主要模組進行設計分析,並在最後部分分析上述程式碼的效果。

Ribbon-core 模組

這一模組中,主要定義通用的呼叫抽象。ribbon 的 client 處理請求並返回響應。

public interface IClient<S extends ClientRequest, T extends IResponse> {
    public T execute(S request, IClientConfig requestConfig) throws Exception;
}

ClientRequest, 是獨立於所以具體實現的通訊協議的抽象。它主要包括:uri(遠端資源的定位符),loadBalancerKey(Object型別,這是決定請求呼叫到目標伺服器的關鍵資訊),isRetriable; 當然它的具體實現類,可能還有請求headers,body等資訊。

IResponse, 是服務端響應的抽象,主要包括: body(payload),isSuccess(響應狀態的簡化),響應headers,請求url;

VipAddress 術語,是一個地址的邏輯名,該邏輯名代表一系列目標伺服器。比如“apple.bar:80”。

RetryHandler,用來決定哪些異常(比如:ConnectException,SocketTimeoutException)發生時要做重試,哪些異常(SocketException,SocketTimeoutException)發生時表示應該熔斷掉(對應伺服器)的呼叫,預設的是不重試。

Ribbon-loadbalancer 模組

負載均衡,巨集觀上效果是希望將請求的流量均衡(不是簡單意義上的平均)的負載到提供服務的伺服器之上。而對於每次請求而言,是通過計算拿到一個目標伺服器地址的。

  • ILoadBalancer

ILoadBalancer, 負載均衡器的介面。它的核心方法是 public Server chooseServer(Object key); 每次請求發生時根據引數傳入的 loadBalancerKey物件,決定出一個目標的伺服器地址。Server 表示一個伺服器(包括:主機地址,埠號以及一些後設資料標識資訊)。 那麼服務均衡器應該需要有一批可供選擇的目標服務集合吧,所以它還有個重要方法 addServers(List<Server> newServers),一般在啟動階段就要完成初始化地呼叫。

  • BaseLoadBalancer

BaseLoadBalancer(實現 ILoadBalancer),這裡宣告瞭一個基礎lb,應該關聯個 IRule(負載均衡的規則)預設是輪詢規則:RoundRobinRule)。 它還預設支援一個Ping檢查功能(可以幫忙我們定時檢查目標伺服器是否可通訊)的定時任務,開發者可以在構造例項時明確指定 Iping(判斷一個服務是否還是活的),IPingStrateg 預設是序列地檢查策略,但如果目標服務地址過多,或者IPing執行過慢就不太合適。

IRule 路由規則,Rule 和 LoadBalancer 是一對一的相互關聯關係,Rule 是具體的負責均衡策略。常見的規則包括: 輪詢,隨機,基於響應的延遲等; public Server choose(Object key)核心方法的輸入輸出基本一致,區別是 Rule 拿取目標 server list 是通過藉助對應依賴的 LoadBalancer 拿到的。

  • DynamicServerListLoadBalancer

DynamicServerListLoadBalancer(繼承 BaseLoadBalancer),事實上從啟動後一直不變的 ServerList 場景一般不太多,尤其在微服務場景:服務掛了,擴容,縮容等都會需要對服務消費方的客戶端的服務列表做出實時調整(通常藉助服務發現產品:eureka,consul,zk…),DynamicServerListLoadBalancer 顧名思義就是針對該類場景的。要做到執行時實時更新,既要保證更新的實時可見,也要保證更新操作本身的同步。

ServerListUpdater,是動態服務列表的更新器。現實場景中一般有個專門提供釋出和查詢/訂閱服務列表服務的角色,這裡暫時簡稱為”遠端登錄檔服務”。更新 ServerList 常見的有兩種實現模式:push 或者 pull。 push 機制就是客戶端保持對“遠端登錄檔服務”觀察就行,伺服器發生變化時會,客戶端就會及時得到通知。 還有一種 pull 機制,一般是頻繁的發請求進行詢問“遠端登錄檔服務”是否有變化發生。這個一般建議基於常見的服務發現產品進行實現。

ServerListFilter,是DynamicServerListLoadBalancer的可選構造引數之一。作用是在發生 ServerList 更新時篩選過濾出符合條件的一個子集。 因為 ServerList 可能比較大,包含成百上千臺機器地址,如果都嘗試去呼叫,那麼客戶端的連線數就會非常多,這也會造成必要的消耗。

Ribbon-httpclient 模組

ribbon-loadbalancer 模組中有個 ClientFactory 靜態工具類,可以生成和管理多個名字唯一的 IClient 具體例項。ClientFactory.getNamedClient("sample-client"); ,這裡因為未特殊配置(指定具體的 IClient 的實現類),所以選擇的是預設的Client實現類 com.netflix.niws.client.http.RestClient。

  • RestClient

RestClient,是 ribbon-httpclient 模組中針對 IClient 的具體實現,通過使用 Jesery Client (實際依賴 apacheHttpClient4 傳送 http),同樣 HttpRequest 實現 ClientRequest,而HttpResponse 實現了 ClientResponse。

client.executeWithLoadBalancer(request),是client的父類 AbstractLoadBalancerAwareClient 中定義的方法,最終是呼叫 LoadBalancerContext#getServerFromLoadBalancer(URI,loadBalancerKey)方法,該方法主要LB邏輯如下:

Server svc = lb.chooseServer(loadBalancerKey);
if (svc == null){
    throw new ClientException(ClientException.ErrorType.GENERAL,
            "Load balancer does not have available server for client: "
                    + clientName);
}

//... check host not null

logger.debug("{} using LB returned Server: {} for request {}", new Object[]{clientName, svc, original});
return svc;


相關文章