[享學Ribbon] 二十五、Ribbon和Eureka的整合(一):ribbon-eureka工程詳解

一個好的程序員是那種過單行線馬路都要往兩邊看的人。

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

前言

Ribbon作爲客戶端負載均衡器,有一個必要的基礎條件就獲取到ServerList服務器列表,以及後續的動態更新服務列表。通過前面學習知道,服務列表它可以來自任何地方,比如默認實現ConfigurationBasedServerList它表示服務列表可以來自於配置(文件)。實際生產中,我們不可能把ServerList地址寫死在配置裏,實際的方式是把Ribbon同註冊中心整合從而從註冊中心裏獲取到列表,並且動態的去sync服務列表,本文“A哥”就帶你領略一番。

服務註冊中心有多種,本文將講述它和自家產品Eureka做整合,以Eureka爲代表進行說明即可,其它的舉一反三。另需要說明的是:雖說eureka1.x目前也已經處在停更維護狀態,但在Spring Cloud體系註冊中心方面它依舊堅挺。爲了便於整合,Ribbon官方提供了專門的整合工程:ribbon-eureka(基於Eureka 1.x)。

在閱讀本文之前,建議/要求你已對Eureka有一定的認識了。關於Netflix各組件的學習,A哥非常用心的專門彙總了一篇文章,供以參考:Netflix OSS套件一站式學習驛站


正文

該工程是Ribbon旗下的一個子模塊,所以GAV和Ribbon保持一致:

<dependency>
    <groupId>com.netflix.ribbon</groupId>
    <artifactId>ribbon-eureka</artifactId>
    <version>2.3.0</version>
</dependency>

它的間接依賴截圖如下:

在這裏插入圖片描述
說明:關於版本的使用上,請參照本專欄第一篇文章的版本聲明部分,詳細介紹了爲何本系列依舊使用2.3.0版本。Eureka版本我們是可以單獨升級的,本處約定使用其1.9.13版本(保持和Spring Cloud Hoxton.SR1版本內置的Eureka版本一致)。

因爲ribbon-eureka僅依賴Eureka的核心API,因此只要大版本號不變,核心API必定是向下兼容的


爲何Ribbon需要Eureka?

Ribbon 維護了一個服務器列表,如果服務器有宕機現象,Ribbon 能夠自行將其剔除(內部有探活機制),沒毛病;但如果該服務器故障排除,重新啓動,或者增加新的負載節點,那麼我們需要手工調用 Ribbon 的接口將其動態添加/移除進Ribbon 的服務器列表才能正常work,這樣明顯不夠盡如人意。

我們想,如何能夠在服務節點啓動時,自行動態的添加/減少服務列表呢?答案那就是註冊中心,也就是本文要說的Eureka。Eureka 提供了 Application Service 客戶端的自行註冊的功能。此外,Eureka 的緩存機制能夠防止大規模宕機帶來的災難性後果。因此Ribbon和它整合,便可以讓本地服務列表實現動態化。

Eureka和Ribbon因爲都是Netflix自家產品,所以整合起來是比較方便的。若你想整合其它註冊中心,可以使用其它相關整合包


ribbon-eureka工程詳解

該整合工程由Netflix官方提供,是Ribbon主動去整合Eureka的(從命名上你也能看得到主次)。ribbon-eureka這個工程的內容並不多,截圖如下:

在這裏插入圖片描述
針對這個工程的詳解,主要會分兩大部分展開:

  1. 工程內各類的源碼解釋
  2. 手工代碼示例,領略Ribbon整合Eureka後是如何工作的

DiscoveryEnabledServer:擴展Server實現

它擴展自Server,代表該實例來自於註冊中心(服務發現),所以它額外擴展了包含InstanceInfo形式的元數據。

說明:InstanceInfo是eureka裏面的一個實例info信息,包含如:instanceId、appName、appGroupName、ipAddr、port、securePort、homePageUrl、healthCheckUrl...非常非常多的屬性

public class DiscoveryEnabledServer extends Server{

	private final InstanceInfo instanceInfo;
	// com.netflix.loadbalancer.Server.MetaInfo服務元信息,基礎數據均來自於InstanceInfo 
	private final MetaInfo serviceInfo;

	// 構造器:通過InstanceInfo構造出一個DiscoveryEnabledServer實例
    public DiscoveryEnabledServer(final InstanceInfo instanceInfo, boolean useSecurePort) {
        this(instanceInfo, useSecurePort, false);
    }
    // useIpAddr:是否使用IP地址
    public DiscoveryEnabledServer(final InstanceInfo instanceInfo, boolean useSecurePort, boolean useIpAddr) {
    	// 如果允許使用ip地址,並且判斷該註冊中心實例的port的是允許的,那就設置上
    	if(useSecurePort && instanceInfo.isPortEnabled(PortType.SECURE)) {
    		super.setPort(instanceInfo.getSecurePort());
    	}
		
		this.instanceInfo = instanceInfo;

		// MetaInfo的實現,全部委託給instanceInfo實例信息
		this.serviceInfo = new MetaInfo() {
			...
            @Override
            public String getAppName() {
                return instanceInfo.getAppName();
            }
            @Override
            public String getServiceIdForDiscovery() {
                return instanceInfo.getVIPAddress();
            }
            ...
		};
    }
    
    // 屬性get方法
    public InstanceInfo getInstanceInfo() {
        return instanceInfo;
    }
    // 這可是複寫了父類的方法哦
    // 父類的MetaInfo實現幾乎爲空實現:大都返回null
    @Override
    public MetaInfo getMetaInfo() {
        return serviceInfo;
    }
}

該Server實例的ip、端口等其它信息均來自於註冊中心的實例info:InstanceInfo,因此對Eureka中的InstanceInfo的理解就顯得很有必要,還好我有準備,請參考:Eureka的最核心概念:InstanceInfo實例信息


LegacyEurekaClientProvider

Legacy:遺贈,遺產。

一個通過靜態方法,單例模式提供EurekaClient的遺留類,不建議再使用

class LegacyEurekaClientProvider implements Provider<EurekaClient> {

    private volatile EurekaClient eurekaClient;

    @Override
    public synchronized EurekaClient get() {
        if (eurekaClient == null) {
            eurekaClient = DiscoveryManager.getInstance().getDiscoveryClient();
        }

        return eurekaClient;
    }
}

因爲DiscoveryManager已經被標記爲@Deprecated,自然而然的本(工具)類也就不再推使用了(容易出錯)。


NIWSDiscoveryPing:通過實例狀態探活

對於IPing接口,Ribbon內置的幾個實現如NoOpPing、DummyPing等其實均沒有實際意義,空實現而已(永遠返回true)。而對於本處集成了Eureka註冊中心的話,定時ping這個動作顯得就非常的有意義了。

本處給的實現,就是一個結合註冊中心實例狀態來實現探活的:

public class NIWSDiscoveryPing extends AbstractLoadBalancerPing {

	@Override
	public boolean isAlive(Server server) {
	    boolean isAlive = true;
	    if (server!=null && server instanceof DiscoveryEnabledServer){
	           DiscoveryEnabledServer dServer = (DiscoveryEnabledServer)server;	            
	           InstanceInfo instanceInfo = dServer.getInstanceInfo();
	           if (instanceInfo!=null){	                
	               InstanceStatus status = instanceInfo.getStatus();
	               if (status!=null){
	                   isAlive = status.equals(InstanceStatus.UP);
	               }
	           }
	       }
	    return isAlive;
	}
	...
    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

它並不會進行遠成訪問。Server的活與否,完全由註冊中心(本地)實例InstanceInfo.status屬性來決定:

  • InstanceStatus.UP狀態表示Server是活着的(isAlive=true)
  • 其它狀態如InstanceStatus.DOWN/STARTING/OUT_OF_SERVICE/UNKNOWN都會認爲Server已死(暫時不可用),從而最終就會被T出去

還記得IPing多久執行一次嗎?

突然被靈魂拷問的感覺有木有,這在前面講IPing這個組件的時候可沒少囉嗦,這裏只是“複習”一下。答案是:30s(默認值),詳見BaseLoadBalancer#PingTask

當然嘍:此值必須是可配置的啊。通過ribbon.NFLoadBalancerPingInterval = xxx來指定,單位秒。


DefaultNIWSServerListFilter:具有區域意識的服務過濾器

默認的NIWS篩選器——處理基於zone區域關聯性和其他相關屬性的篩選服務器。

public class DefaultNIWSServerListFilter<T extends Server> extends ZoneAffinityServerListFilter<T> {
}

直接繼承自ZoneAffinityServerListFilter的“空”實現,關於它的講解前面A哥也沒少囉嗦。


總結

關於Ribbon和Eureka的整合第一部分就先講解到這,你會發現ribbon-eureka工程還有兩個API沒有涉及到,因爲那哥倆纔是重頭戲,爲了分離關注,A哥把它放在了下篇文章單獨、重點敘述,請您移步。
分隔線

聲明

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