調試一個初次見到的代碼比重寫代碼要困難兩倍。是以,按照定義,如果你寫代碼非常巧妙,那麼沒有人足夠聰明來調試它。
代碼下載下傳位址: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
環境下多了個實作):

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
裡這兩個方法也是一樣的)。