天天看點

[享學Netflix] 四十三、Ribbon的LoadBalancer五大元件之:ServerList服務清單

調試一個初次見到的代碼比重寫代碼要困難兩倍。是以,按照定義,如果你寫代碼非常巧妙,那麼沒有人足夠聰明來調試它。

代碼下載下傳位址:https://github.com/f641385712/netflix-learning

前言

繼上文講述了LoadBalancer五大元件之一的IPIng後,本文繼續講解它的

ServerList

元件。

正文

ServerList

該接口定義了擷取伺服器清單的方法。

// T并不規定必須是Server,也可以是它的子類
// 但實際情況是:一般并不會自己去繼承Server來玩,因為Server已經夠抽象了
public interface ServerList<T extends Server> {
	// 傳回初始狀态的伺服器清單。比如初試配置了10台那它永遠是10個Server
	public List<T> getInitialListOfServers();
	// 傳回更新後的服務清單。它會周期ping過後,傳回活着的
	public List<T> getUpdatedListOfServers();
}           

複制

它的繼承圖譜如下(

Spring Cloud

環境下多了個實作):

[享學Netflix] 四十三、Ribbon的LoadBalancer五大元件之:ServerList服務清單
[享學Netflix] 四十三、Ribbon的LoadBalancer五大元件之:ServerList服務清單

AbstractServerList

該抽象實作有且提供一個public方法:提供loadBalancer使用的過濾器

AbstractServerListFilter

此處疑問:擷取

ServerListFilter

的方法為何寫在

ServerList

裡呢?八竿子打不着好嗎?不知道作者咋想的呢?是以本處先一筆略過

public abstract class AbstractServerList<T extends Server> implements ServerList<T>, IClientConfigAware {

	public AbstractServerListFilter<T> getFilterImpl(IClientConfig niwsClientConfig) throws ClientException{
		...
	}
}           

複制

還好它有個實作類:

ConfigurationBasedServerList

,基于配置的ServerList。

ConfigurationBasedServerList

它可以從

Configuration

配置中加載伺服器清單的實用工具實作類。屬性名的定義格式如下:

<clientName>.<nameSpace>.listOfServers=<comma delimited hostname:port strings>           

複制

依賴的

IClientConfig

配置管理去擷取配置值,是以沒有ClientName擷取的就是全局的,但是那又有什麼意義呢???
// 這裡挺有意思,到這裡卻确定了泛型類型是Server,搞怪~
public class ConfigurationBasedServerList extends AbstractServerList<Server>  {
	private IClientConfig clientConfig;
	
	// 在此處兩個方法的實作效果是一模一樣的。
	@Override
	public List<Server> getInitialListOfServers() {
	    return getUpdatedListOfServers();
	}
	@Override
	public List<Server> getUpdatedListOfServers() {
		// key是:listOfServers(請注意:首字母l是小寫)
        String listOfServers = clientConfig.get(CommonClientConfigKey.ListOfServers);
        // derive->逗号分隔 -> new Server(s.trim())
        // 也就是說s是個id
        return derive(listOfServers);
	}
	
	protected List<Server> derive(String value) { ... }
}           

複制

該實作類需要引起重視,因為在

Spring Cloud

中,脫離eureka使用ribbon的經典配置:

# 禁用ribbon在eureka裡使用
ribbon.eureka.enabled=false
# 配置服務提供者的位址
account.ribbon.listOfServers=localhost:8080,localhost:8081           

複制

這樣配置後,它就是使用

ConfigurationBasedServerList

來加載伺服器清單的。

代碼示例
  • API方式配置:
@Test
public void fun2(){
    // 準備配置
    IClientConfig config = new DefaultClientConfigImpl();
    // config.set(CommonClientConfigKey.valueOf("listOfServers"), "www.baidu.com,http://yourbatman.com:8080");
    config.set(CommonClientConfigKey.ListOfServers, "    www.baidu.com,http://yourbatman.com:8080    ");

    ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
    serverList.initWithNiwsConfig(config);

    serverList.getInitialListOfServers().forEach(server -> {
        System.out.println(server.getId());
        System.out.println(server.getHost());
        System.out.println(server.getPort());
        System.out.println(server.getHostPort());
        System.out.println(server.getScheme());
        System.out.println("-----------------------------");
    });
}           

複制

運作程式,控制台輸出:

www.baidu.com:80
www.baidu.com
80
www.baidu.com:80
null
-----------------------------
yourbatman.com:8080
yourbatman.com
8080
yourbatman.com:8080
http
-----------------------------           

複制

  • 配置檔案方式:

config.properties

配置内容為:

account.ribbon.listOfServers=    www.baidu.com,http://yourbatman.com:8080               

複制

編寫測試代碼:

@Test
public void fun100(){
    DefaultClientConfigImpl config = DefaultClientConfigImpl.getClientConfigWithDefaultValues("account");

    ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
    serverList.initWithNiwsConfig(config);

    serverList.getInitialListOfServers().forEach(server -> {
        System.out.println(server.getId());
        System.out.println(server.getHost());
        System.out.println(server.getPort());
        System.out.println(server.getHostPort());
        System.out.println(server.getScheme());
        System.out.println("-----------------------------");
    });
}           

複制

運作程式,結果完全同上。

小bug

有小夥伴跟我反應了一種case,在此處順便我給補上。它是這麼做的(僅僅改了Config而已):

@Test
public void fun101(){
    DefaultClientConfigImpl config = DefaultClientConfigImpl.getEmptyConfig();
    config.loadDefaultValues(); // 注意:這句必須顯示調用,否則配置裡無值

    ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
    serverList.initWithNiwsConfig(config);

    serverList.getInitialListOfServers().forEach(server -> {
        System.out.println(server.getId());
        System.out.println(server.getHost());
        System.out.println(server.getPort());
        System.out.println(server.getHostPort());
        System.out.println(server.getScheme());
        System.out.println("-----------------------------");
    });
}           

複制

config.properties内容(使用的全局配置):

ribbon.listOfServers=www.baidu.com,http://yourbatman.com:8080           

複制

運作程式輸出:

www.baidu.com:80
www.baidu.com
80
www.baidu.com:80
null
-----------------------------           

複制

what?隻輸出一個???為何隻會列印一個呢?

在了解具體原因之前,確定你已經對

IClientConfig

的原理非常了解了(不了解可以點這裡)。

ConfigurationBasedServerList

使用的是

clientConfig.get(CommonClientConfigKey.ListOfServers)

去擷取屬性值,而get()方法依賴的是

DefaultClientConfigImpl#getProperty(String key)

DefaultClientConfigImpl:

    protected Object getProperty(String key) {
        if (enableDynamicProperties) {
        	// 它裡面會先找指定clientName的屬性,再找全局
        	// 秒就秒在這裡,它使用的是DynamicProperty.getInstance(..).getString()
        	// 這個getString()方法為**原樣輸出**,這點特别重要
        	...
        }
        return properties.get(key);
	}           

複制

例子中隻調用了加載預設值方法,是以

enableDynamicProperties

仍舊為false,是以此處會走

properties.get(key)

方法,該方法就是一Map的get方法沒啥可說的,重點是它的值是如何放進去的?

DefaultClientConfigImpl:

	public void loadDefaultValues() {
		...
		// 回去配置檔案加載預設值放進去
		putDefaultStringProperty(CommonClientConfigKey.ListOfServers, "");
	}
	protected void putDefaultStringProperty(IClientConfigKey propName, String defaultValue) {
		String value = ConfigurationManager.getConfigInstance().getString(getDefaultPropName(propName), defaultValue);
		setPropertyInternal(propName, value);
	}           

複制

這個

AbstractConfiguration#getString()

方法就是罪魁禍首:當key對應的value值是集合時,隻會傳回第一個值

collection.iterator().next()

。是以放進

properties

時隻有一個值,

properties.get(key)

取出來肯定就隻有一個喽。

解決問題非常的簡單,不要用

config.loadDefaultValues()

方法即可,而是這麼做,問題就得到解決。

DefaultClientConfigImpl config = DefaultClientConfigImpl.getClientConfigWithDefaultValues("noClient")           

複制

這個“bug”本身并不重要,因為說過

listOfServers

禁止使用全局配置。但是該結果反映出來的原理還是可以深究的。實際上

Ribbon

裡的這種小bug不止一處,後續文章還會繼續提到。

說明:這種小問題我個人覺得是Ribbon的考慮不夠全面導緻的,當然喽絕大多數情況下都無傷大雅,可以從“操作”上規避它~

StaticServerList

它是

Spring Cloud

ServerList

接口的實作,因為比較簡單,是以在此處一并帶過。顧名思義它表示靜态的List,這種實作太簡單了。

public class StaticServerList<T extends Server> implements ServerList<T> {

	private final List<T> servers;
	public StaticServerList(T... servers) {
		this.servers = Arrays.asList(servers);
	}
	
	@Override
	public List<T> getInitialListOfServers() {
		return servers;
	}
	@Override
	public List<T> getUpdatedListOfServers() {
		return servers;
	}
}           

複制

該實作類沒有任何地方被使用到過,

Spring Cloud

提供出來是若你有寫死Server位址在代碼裡的時候,可以使用它,隻是我們需要這麼做的機率極小極小而已。

哪裡調用?

ServerList#getInitialListOfServers

方法無任何地方調用,

ServerList#getUpdatedListOfServers

方法的唯一調用處是:

DynamicServerListLoadBalancer#updateListOfServers

處:

DynamicServerListLoadBalancer:

	// 更新服務清單。
	public void updateListOfServers() {
		// 拿到清單
		servers = serverListImpl.getUpdatedListOfServers();
		// 把清單過濾一把
		servers = filter.getFilteredListOfServers(servers);
	
		// 設定最新清單
		pdateAllServerList(servers);
	}           

複制

總結

關于Ribbon的LoadBalancer五大元件之:ServerList就介紹到這了,本篇内容依舊比較簡單。該API用于擷取Server清單,它可以來自于寫死的List、配置檔案、甚至是遠端網絡(如eureka配置中心)。另外從實作中你可以看到:

getInitialListOfServers和getUpdatedListOfServers

兩方法是完全對等的(哪怕和eureka內建的實作

com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList

裡這兩個方法也是一樣的)。