[享學Netflix] 三十八、Ribbon核心API源碼解析:ribbon-core(一)

軟件設計有兩種方式:一種方式是,使軟件過於簡單,明顯沒有缺陷;另一種方式是,使軟件過於複雜,沒有明顯的缺陷

–> 返回專欄總目錄 <–
代碼下載地址:https://github.com/f641385712/netflix-learning

前言

上篇文章整體上對Ribbon做了介紹,可能有小夥伴的有和我一樣的感覺:知道Ribbon它是做什麼大,僅只是略懂略懂狀態,一種不踏實之感。Java庫的好處是它開源,大大降低了學習的難度(不用純憑記憶,可以從設計脈絡上整體把握)。

從本文起將對Ribbon從API源碼出發,附以示例講解,逐個擊破的方式,一步步對Ribbon進行全面剖析。因Ribbon一時半會還找不到替代的技術,並且國內學習它的資料比較少,希望此係列文章幫助到你。


正文

ribbon-core它是Ribbon的核心包,其它任何包均依賴於此。該Jar包內只定義了公共API,自己並不能單獨work,所以你可以理解爲它就是一公共抽象即可。

<dependency>
    <groupId>com.netflix.ribbon</groupId>
    <artifactId>ribbon-core</artifactId>
    <!-- 版本號爲上文約定的2.3.0版本,保持和Spring Cloud的依賴一致 -->
    <version>${ribbon.version}</version>
</dependency>

在這裏插入圖片描述
從圖中可以看出:core核心包裏並沒有任何loadbalance負載均衡的概念,並且也沒有任何http的概念在裏面,所以說core是一個高度抽象的包:和lb無關,和協議亦無關。

說明:Ribbon的源碼對類的命名有個規範 -> 接口一定是以I開頭的,如IClient、以及後面的IRule、IPing等


IClient

Ribbon要想負載均衡,那必然需要有發送請求的能力,而該接口就是它最最最最爲核心接口嘍,其它的一切組件均圍繞它來設計和打造,包括LB

該接口表示可以執行單個請求的客戶端:發送請求Request,獲得響應Response,注意並沒有綁定任何協議哦(http、tcp、udp、文件協議、本地調用都是闊儀的)。

public interface IClient<S extends ClientRequest, T extends IResponse> {
	// 執行請求並返回響應
    public T execute(S request, IClientConfig requestConfig) throws Exception; 
}

core包裏沒提供此接口的任何實現,在Spring Cloud下有如下實現:

在這裏插入圖片描述
在這裏插入圖片描述


ClientRequest

表示適用於所有通信協議的通用客戶端請求對象。該對象是immutable不可變的。

public class ClientRequest implements Cloneable {

	// 請求的URI
    protected URI uri;
    protected Object loadBalancerKey = null;
    // 是否是可重試。true:該請求可重試   false:該請求不可重試
    protected Boolean isRetriable = null;
    // 外部傳進來的配置,可以覆蓋內置的IClientConfig配置哦
    protected IClientConfig overrideConfig;
    
	... // 省略各種構造器
	... // 生路各種get方法。注意:沒有set方法,因爲該實例不可變

	// 判斷該請求是否可以重試(重要)
    public boolean isRetriable() {
        return (Boolean.TRUE.equals(isRetriable));
    }
    
    ...
	
	// 使用新的URI創建一個**新的**ClientRequest
	// 它會先用clone方法去克隆一個,若拋錯那就new ClientRequest(this) new一個實例
	// 推薦子類複寫此方法,提供更多、更有效的實施。
    public ClientRequest replaceUri(URI newURI) {
        ClientRequest req;
        try {
            req = (ClientRequest) this.clone();
        } catch (CloneNotSupportedException e) {
            req = new ClientRequest(this);
        }
        req.uri = newURI;
        return req;
    }
}

core包裏無子類。Spring Cloud下繼承圖譜如下:

在這裏插入圖片描述
在這裏插入圖片描述


IResponse

客戶端框架的響應接口,請注意它是一個接口,而Request請求是類哦。

public interface IResponse extends Closeable {

	// 從響應中獲得實體。若是Http協議,那就是Body體
	// 因爲和協議無關,所以這裏只能取名叫Payload
	public Object getPayload() throws ClientException;
	public boolean hasPayload();
	
	// 如果認爲響應成功,則爲真,例如,http協議的200個響應代碼。
	public boolean isSuccess();
	public URI getRequestedURI();
	// 響應頭們
	public Map<String, ?> getHeaders();  
}

小細節:該接口並沒有形如getStatus獲取響應狀態碼的方法,是因爲它和協議無關,而響應狀態碼是Http的專屬。

core包內並無此接口的實現,Spring Cloud下的情況如下:

在這裏插入圖片描述
在這裏插入圖片描述


本地測試環境搭建

本着main方法是一切程序的入口,任何組件均可本地測試的原則,再加上Ribbon核心的設計本就是和協議無關的,所以關於Ribbon核心的講解內容使用單元測試(非集成測試)的方式來完成,相信會更加有助於你對負載均衡的學習。

說明:本環境搭建聚焦於內核部分的測試,也就是ribbon-coreribbon-loadbalancer,因爲他倆均爲協議無慣性、網絡無慣性設計,因此均可通過本地方式達到測試目的,讓一切均可測試。

  • 自定義MyClient實現
/**
 * 不具有負載均衡功能的一個Client
 *
 * @author yourbatman
 * @date 2020/3/14 22:09
 */
public class MyClient implements IClient<ClientRequest, MyResponse> {

    @Override
    public MyResponse execute(ClientRequest request, IClientConfig requestConfig) throws Exception {
        MyResponse response = new MyResponse();
        response.setRequestUri(request.getUri());
        return response;
    }
}
  • 自定義MyResponse實現
public class MyResponse implements IResponse {

    private URI requestUri;
    public void setRequestUri(URI requestUri) {
        this.requestUri = requestUri;
    }

    @Override
    public Object getPayload() throws ClientException {
        return "ResponseBody";
    }

    @Override
    public boolean hasPayload() {
        return true;
    }

    // 永遠成功
    @Override
    public boolean isSuccess() {
        return true;
    }

    @Override
    public URI getRequestedURI() {
        return requestUri;
    }

    @Override
    public Map<String, ?> getHeaders() {
        return null;
    }

    @Override
    public void close() throws IOException {

    }
}
  • 最基礎測試用例展示:
@Test
public void fun1() throws Exception {
    // client配置
    IClientConfig clientConfig = DefaultClientConfigImpl.getClientConfigWithDefaultValues("YourBatman");

    // Client客戶端,用於發送請求(使用ClientConfig配置)
    // 因爲木有LB功能,所以要不要IClientConfig沒什麼關係
    MyClient client = new MyClient();

    // 執行請求,獲得響應
    MyResponse response = client.execute(createClientRequest(), null);
    System.out.println(response.isSuccess());
}

控制檯輸出:true。這邊是一個最簡的本地測試環境,後面會基於此展開更多測試case


配置key管理

作爲一個開源的庫,需要使用配置來提高其彈性,因此Ribbon也有一套屬於自己的配置管理,core包裏記錄着它的核心API。它需要管理大量的可識別的配置key,這就是將要介紹的Ribbon對key的管理方式。


IClientConfigKey

用於定義用於IClientConfig使用的key,注意它僅是key,而非k-v。

// 注意:泛型接口
public interface IClientConfigKey<T> {
	// 用於做Hash的字符串表現形式
	public String key();
	// key的類型,比如Integer.class
	// 若不指定,會根據泛型類型自動判斷出來
	public Class<T> type();
}

對該接口簡單粗暴的理解:含義同普通的Object key。它有一個子類:CommonClientConfigKey,記錄着通用的一些key們(太多了,40+個)。


CommonClientConfigKey

它是一個抽象類,所以IClientConfigKey均以它的匿名子類的形式出現。

public abstract class CommonClientConfigKey<T> implements IClientConfigKey<T> {

	public static final IClientConfigKey<String> AppName = new CommonClientConfigKey<>("AppName"){};
	public static final IClientConfigKey<String> Version = new CommonClientConfigKey<>("Version"){};
	public static final IClientConfigKey<Integer> Port = new CommonClientConfigKey<>("Port"){};
	... // 因爲實在太多了,略
	public static final IClientConfigKey<Integer> ConnectTimeout = new CommonClientConfigKey<Integer>("ConnectTimeout"){};
	public static final IClientConfigKey<Integer> ReadTimeout = new CommonClientConfigKey<Integer>("ReadTimeout"){};
	... // 因爲實在太多了,略
	// 此key是使用得最最最多的:通過配置的形式指定Server地址(可指定多個)
	public static final IClientConfigKey<String> ListOfServers = new CommonClientConfigKey<String>("listOfServers") {};
}

說明:配置的含義是任何程序都不可忽視的一部分,所以關於每個key到底什麼意思?默認值是什麼?有何作用?這在後面配置章節專題裏專門講解

本類內部維護着非常多個public static的常量,這樣外面便可直接使用。但是再怎麼多,也不可能涵蓋所有,所以本類也提供了自定義構造key的方法:

CommonClientConfigKey:

	// 根據字符串name,創建出一個IClientConfigKey實例
	public static IClientConfigKey valueOf(final String name) {
		// 先從keys緩存裏看看是否已經有了,若已經存在就直接返回
        for (IClientConfigKey key: keys()) {
            if (key.key().equals(name)) {
                return key;
            }
        }
	
		// 若沒有現成的,那就創建一個新的返回
        return new IClientConfigKey() {
            @Override
            public String key() {
                return name;
            }

            @Override
            public Class type() {
                return String.class;
            }
        };

	}

支出這裏的一個小"Bug":新建出來的key並沒有放進緩存裏,其實放進去是否會更好呢???


示例
@Test
public void fun1() {
    IClientConfigKey key1 = CommonClientConfigKey.valueOf("YourBatman");
    IClientConfigKey key2 = CommonClientConfigKey.valueOf("YourBatman");
    System.out.println(key1.key());
    System.out.println(key1 == key2);
}

控制檯輸出:

YourBatman
false

可見,同一個String類型的key構建兩次,得到的是兩個不同的IClientConfigKey實例,這麼處理大多數時候對內存是不夠友好的,這就是爲何上面我認爲它這是個小“bug”的原因。


總結

本文牛刀小試,介紹了ribbon-core核心包裏面的最核心API:IClient,以及搭建好了本地測試環境,這對後續加入負載均衡邏輯提供基礎支持。另外我們知道Ribbon它對key的管理使用的是IClientConfigKey接口抽象,並且使用CommonClientConfigKey管理着內部可識別的40+個常用key供以方便使用。

關於ribbon-core的核心API第一部分就先介紹到這,下文繼續。。。
分隔線

聲明

原創不易,碼字不易,多謝你的點贊、收藏、關注。把本文分享到你的朋友圈是被允許的,但拒絕抄襲。你也可【左邊掃碼/或加wx:fsx641385712】邀請你加入我的 Java高工、架構師 系列羣大家庭學習和交流。
往期精選

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章