前言
上一篇我們知道了feign調用實現負載均衡是通過集成ribbon實現的。也較為詳細的了解到了集成的過程。現在我們看一下ribbo是如何實現負載均衡的。寫到這里我尚未去閱讀源代碼,我在這里盲猜一下: 他肯定是有一個從注冊中心拉取配置的模塊,一個選擇調用服務的模塊。然后我們就帶着這樣的指導思想去看源碼。
一、ribbo是何時從eurake加載的服務列表?
從上一篇文章我們知道,feign調用實際上調用的是AbstractLoadBalancerAwareClient.executeWithLoadBalancer(...)方法,我們看一下該方法做了那些事情:
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
// 省略
}
}
可以看到上述代碼主要是創建了一個負載均衡執行命令類,然后執行請求。我們看看提交請求后執行的具體邏輯:
public Observable<T> submit(final ServerOperation<T> operation) {
final ExecutionInfoContext context = new ExecutionInfoContext();
// Use the load balancer
Observable<T> o =
(server == null ? selectServer() : Observable.just(server))
.concatMap(...);
// 省略部分代碼
}
這里可以看到它使用了RXjava,rxjava主要是利用觀察者思想實現的鏈式調用,其源碼的實現稍顯復雜,自己使用需要謹慎,借用Uncle Ben的一句話 ”with great power comes great responsibility “。我們關心一下上面的selectServers()方法,從方法名我們可以大致猜出,它是在選擇調用的服務。我們看一下此方法的實現:
/**
* Return an Observable that either emits only the single requested server
* or queries the load balancer for the next server on each subscription
*/
private Observable<Server> selectServer() {
return Observable.create(new OnSubscribe<Server>() {
@Override
public void call(Subscriber<? super Server> next) {
try {
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
next.onNext(server);
next.onCompleted();
} catch (Exception e) {
next.onError(e);
}
}
});
}
顯然核心邏輯在loadBalancerContext.getServerFromLoadBalancer(...)里面,我們繼續向下看:
public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
// 省略部分代碼
ILoadBalancer lb = getLoadBalancer();
if (host == null) {
// Partial URI or no URI Case
// well we have to just get the right instances from lb - or we fall back
if (lb != null){
Server svc = lb.chooseServer(loadBalancerKey);
}
}
// 省略部分代碼
return new Server(host, port);
}
非常戲劇性的是,我們看到他選擇一個服務是通過ILoadBalancer進行的,那我們的看一下ILoadBalancer是怎么創建的。首先ILoadBalancer是屬於Ribbon提供的類,其創建我們先看Ribbon自身的ILoadbalance創建過程,發現其是通過RibbonClientConfiguration的下述方法創建:
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
可以看到上述方法先判斷name對應的ILoadBalancer是否存在,不存在就創建了一個。我們看一下這個name代表的是什么:
@RibbonClientName
private String name = "client";
可以看到它取得的是客戶端名稱,其實就是@FeignClient的name屬性的值,這里可以直接告訴你怎么實現動態給這里的name賦值的:在生成feign的代理對象過程的實現,會將feign通過@FeingClient的contentId屬性進行Context隔離,在加載配置的時候會將@RibbonClientName屬性的@Value屬性的環境變量的值設置成當前@FeignClient的name值,具體可以直看NamedContextFactory.createContext()方法。言歸正轉,ZoneAwareLoadBalancer的構建顯然是持有了Config,Rule,Ping,ServerList,ServerListFilter,ServerListUpdater。
現在我們看一下這四個類的分工與職責。
| 類 | 職責 |
|---|---|
| Config | config 主要是配置了服務名稱,服務讀取的一些配置比如刷新服務間隔等 |
| Rule | 選用服務的規則,他決定了選用哪個服務 |
| Ping | 主要用於探測服務列表中的服務是否可用 |
| ServerList | 它主要是提供最新服務列表:包括上線、下線的服務(默認實現DiscoveryEnabledNIWSServerList) |
| ServerListFilter | 用於過濾服務,do what you want to do. |
| ServerListUpdater | 用於更新服務列表(當前的默認實現是啟動定時器,然后調用ServerList獲取服務列表,將當前負載均衡可用服務列表全覆蓋) |
知道了上面的職責划分,我們看到ZoneAwareLoadBalancer最終初始化服務列表的方法:
public void updateListOfServers() {
List<T> servers = new ArrayList<T>();
if (serverListImpl != null) {
servers = serverListImpl.getUpdatedListOfServers();
LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
if (filter != null) {
servers = filter.getFilteredListOfServers(servers);
LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
}
}
updateAllServerList(servers);
}
基於上述我們可以清晰的了解到,獲取服務列表發生在ServerList的實現中(serverListImpl.getUpdatedListOfServers())。
二、ribbo配合Eurake的ServerList實現
上面serverListImpl 實現類就是DiscoveryEnabledNIWSServerList,當調用getUpdatedListOfServers()最終調用到了如下所述方法,我們看一下他干了些什么:
// 省略了部分代碼
private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
List<DiscoveryEnabledServer> serverList = new ArrayList<DiscoveryEnabledServer>();
EurekaClient eurekaClient = eurekaClientProvider.get();
if (vipAddresses!=null){
for (String vipAddress : vipAddresses.split(",")) {
List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);
for (InstanceInfo ii : listOfInstanceInfo) {
if (ii.getStatus().equals(InstanceStatus.UP)) {
DiscoveryEnabledServer des = createServer(ii, isSecure, shouldUseIpAddr);
serverList.add(des);
}
}
}
}
return serverList;
}
上述代碼主要是通過EurakeClient 獲取對應服務實例,然后將服務實例向DiscoveryEnabledServer進行映射。至此從服務列表的獲取過程就完成了。
三、 Rule 服務的選擇
知道了服務列表的來源,接下來就是最關鍵的一步了,選擇一個服務調用。我們先看一下默認情況使用的是什么規則(RibbonClientConfiguration):
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
if (this.propertiesFactory.isSet(IRule.class, name)) {
return this.propertiesFactory.get(IRule.class, config, name);
}
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}
顯然默認使用的是ZoneAvoidanceRule。 這里我們看一下IRule都有哪些實現,然后闡述一下各個算法實現(這里沒有再看源碼的必要了,算法就那些):
| 類 | 描述 |
|---|---|
| ZoneAvoidanceRule | 基於可用性與區域的復合預測規則 |
| RoundRobinRule | 輪詢 |
| WeightedResponseTimeRule | 根據響應時間選擇 |
| RandomRule | 隨機規則 |
小結
總結起來,ribbon的核心組成就是ZoneAwareLoadBalancer的組成。其分工明確,服務列表的維護、服務的可用性、服務的選擇都有專門的類去負責。下一篇將會着重講一下Feign、Ribbon如何做到基於服務的配置隔離的,會適當配合實戰,比如怎么自定義一個全局生效的路由規則,自定義一個基於服務名的生效的路由規則等等。
