03.02 Spring Cloud Ribbon 源碼解析

依賴關係


<code>+------------+              +------------+
| | | |
| | | |
| | | |
| | | |
| consumer +------------> | provider |
| | RestTemplate | |
| | | |
| | | |
| | | |
+------------+ +------------+/<code>


pom 依賴

加入nacos 服務發現即可,內部引用了 spring-cloud-ribbon 相關依賴

<code><dependency>
<groupid>com.alibaba.cloud/<groupid>
<artifactid>spring-cloud-starter-alibaba-nacos-discovery/<artifactid>
/<dependency>/<code>


調用客戶端

我們這裡以最簡單的 RestTemplate 調用開始使用Ribbon

<code>@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}

// Controller 使用restTemplate 調用服務提供方接口
ResponseEntity<string> forEntity = restTemplate.getForEntity("http://provider/req", String.class);
/<string>/<code>


源碼解析


創建調用攔截器


1. 獲取全部 @LoadBalanced標記的RestTemplate


<code>public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<resttemplate> restTemplates = Collections.emptyList();
}/<resttemplate>/<code>


2. 增加 LoadBalancerInterceptor 處理邏輯

Spring Cloud Ribbon 源碼解析

  • 沒有引入 spring-retry使用的是
<code>@Bean
public LoadBalancerInterceptor ribbonInterceptor() {
return new LoadBalancerInterceptor();
}/<code>


  • 引入 spring-retry 使用的是
<code>@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor ribbonInterceptor() {
return new RetryLoadBalancerInterceptor();
}/<code>


  • LoadBalancerInterceptor 業務邏輯
<code>public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept() {
final URI originalUri = request.getURI();
// http://demo-provider/req 截取 demo-provider 服務名稱
String serviceName = originalUri.getHost();

// 默認注入的 RibbonAutoConfiguration.RibbonLoadBalancerClient
return this.loadBalancer.execute(serviceName,
// 創建請求對象
this.requestFactory.createRequest(request, body, execution));
}
}/<code>


執行攔截器


3. RibbonLoadBalancerClient執行


<code>//RibbonAutoConfiguration默認注入的RibbonLoadBalancerClient
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}/<code>


4.execute執行


<code>public class RibbonLoadBalancerClient implements LoadBalancerClient {
public T execute(){
//獲取具體的ILoadBalancer實現
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);

// 調用ILoadBalancer 實現獲取Server
Server server = getServer(loadBalancer, hint);
RibbonServer ribbonServer = new RibbonServer(serviceId, server,
isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));

//獲取狀態記錄器,保存此次選取的server
RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId);
RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
T returnVal = request.apply(serviceInstance);
statsRecorder.recordStats(returnVal);
return returnVal;
}

}
/<code>


獲取ILoadBalancer


5 SpringClientFactory


<code>// bean 工廠生成LoadBalancer 的實現
protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.springClientFactory.getLoadBalancer(serviceId);
}

// 具體生成邏輯看 RibbonClientConfiguration,這個Bean 只有工廠調用的時候才會創建
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<server> serverList, ServerListFilter<server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
return new ZoneAwareLoadBalancer<>();
}/<server>/<server>/<code>


6.創建LoadBalancer 的依賴要素


名稱默認實現作用IClientConfigDefaultClientConfigImplribbon 客戶端配置參數,例如: 超時設置、壓縮設置等ServerListNacosServerList目標服務的實例實例表,具體服務發現客戶端實現ServerListFilterZonePreferenceServerListFilter針對ServerList 實例列表的過濾邏輯處理IRuleZoneAvoidanceRule負載均衡選擇Server 的規則IPingDummyPing檢驗服務是否可用的方法實現ServerListUpdaterPollingServerListUpdater針對ServerList 更新的操作實現

以上默認實現參考 RibbonClientConfiguration. ZoneAwareLoadBalancer

獲取服務實例


<code>//Server server = getServer(loadBalancer, hint);  4. excute 方法
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
return loadBalancer.chooseServer(hint != null ? hint : "default");
}/<code>
Spring Cloud Ribbon 源碼解析

7. ZoneAwareLoadBalancer


<code>public class ZoneAwareLoadBalancer{
public ZoneAwareLoadBalancer() {
// 調用父類初始化方法。 這裡會開啟實例維護的定時任務等 (具體解析參考 擴展部分)
super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
}
@Override
public Server chooseServer(Object key) {
// 若是使用的 Nacos 服務發現,則沒有 Zone 的概念,直接調用父類的實現
if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
return super.chooseServer(key);
}
// 以下為有 Zone 的概念 例如 Eureka (具體)
...
return server;
}
}/<code>


  • 父類調用IRule實現選擇Server
<code>public Server chooseServer(Object key) {
return rule.choose(key);
}/<code>
Spring Cloud Ribbon 源碼解析

8.PredicateBasedRule 選擇規則


<code>public abstract class PredicateBasedRule {
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
// 獲取斷言配置
Optional<server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
}/<server>/<code>


9. ZoneAvoidancePredicate服務列表斷言


<code>public class ZoneAvoidancePredicate {
@Override
public boolean apply(@Nullable PredicateKey input) {
if (!ENABLED.get()) {
return true;
}
// 還是獲取區域配置,如是使用的 Nacos 直接返回true
String serverZone = input.getServer().getZone();
if (serverZone == null) {
// there is no zone information from the server, we do not want to filter
// out this server
return true;
}
// 區域高可用判斷
...
}
}/<code>




擴展: ServerList 維護


初始化ServerList

Spring Cloud Ribbon 源碼解析

在上文 6.創建LoadBalancer 的依賴要素

,中 ServerList 目標服務的實例實例表,具體服務發現客戶端實現。我們來看下 Nacos 的實現

<code>public class NacosServerList extends AbstractServerList<nacosserver> {
@Override
public List<nacosserver> getInitialListOfServers() {
return getServers();
}

@Override
public List<nacosserver> getUpdatedListOfServers() {
return getServers();
}

private List<nacosserver> getServers() {
String group = discoveryProperties.getGroup();
//調用nacos-sdk 查詢實例列表
List<instance> instances = discoveryProperties.namingServiceInstance()
.selectInstances(serviceId, group, true);
// 類型轉換
return instancesToServerList(instances);

}
}/<instance>/<nacosserver>/<nacosserver>/<nacosserver>/<nacosserver>/<code>


更新ServerListUpdater

Spring Cloud Ribbon 源碼解析

  • ServerList 初始化後更新操作通過 PollingServerListUpdater
<code>public class PollingServerListUpdater implements ServerListUpdater {
@Override
public synchronized void start(final UpdateAction updateAction) {
// 更新任務 交給updateAction 具體實現
final Runnable wrapperRunnable = () -> {
updateAction.doUpdate();
lastUpdated = System.currentTimeMillis();
};

// 開啟後臺線程定時執行 updateAction
scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
wrapperRunnable,
initialDelayMs,
refreshIntervalMs,
TimeUnit.MILLISECONDS
);
}
}/<code>


  • updateAction 實現
<code>public void doUpdate() {
DynamicServerListLoadBalancer.this.updateListOfServers();
}/<code>


<code>public class PollingServerListUpdater implements ServerListUpdater {
public void updateListOfServers() {
List servers = new ArrayList();

// 調用NacosServiceList 獲取全部服務列表
servers = this.serverListImpl.getUpdatedListOfServers();


// 如果配置實例過濾器在執行過濾
if (this.filter != null) {
servers = this.filter.getFilteredListOfServers((List)servers);
}

// 更新LoadBalancer 服務列表
this.updateAllServerList((List)servers);
}
}
/<code>


擴展: Server 狀態維護

Spring Cloud Ribbon 源碼解析

  • LoadBalancer 初始構造時會觸發 setupPingTask()
<code>public BaseLoadBalancer() {
this.name = DEFAULT_NAME;
this.ping = null;
setRule(DEFAULT_RULE);
// 開啟ping 檢查任務
setupPingTask();
lbStats = new LoadBalancerStats(DEFAULT_NAME);
}/<code>


  • setupPingTask
<code>void setupPingTask() {
// 是否可以ping, 默認的DummyPing 直接 跳過不執行
if (canSkipPing()) {
return;
}
// 執行PingTask
lbTimer.schedule(new BaseLoadBalancer.PingTask(), 0, pingIntervalSeconds * 1000);
// 開啟任務
new BaseLoadBalancer.Pinger(pingStrategy).runPinger();
}/<code>


  • SerialPingStrategy 串行執行邏輯
<code>// 串行調度執行 Iping 邏輯
private static class SerialPingStrategy implements IPingStrategy {
@Override
public boolean[] pingServers(IPing ping, Server[] servers) {
int numCandidates = servers.length;
boolean[] results = new boolean[numCandidates];

for (int i = 0; i < numCandidates; i++) {
results[i] = false; /* Default answer is DEAD. */

if (ping != null) {
results[i] = ping.isAlive(servers[i]);
}

}
return results;
}
}/<code>


  • 調用url 判斷可用性
<code>public class PingUrl implements IPing {
public boolean isAlive(Server server) {
urlStr = urlStr + server.getId();
urlStr = urlStr + this.getPingAppendString();
boolean isAlive = false;
HttpClient httpClient = new DefaultHttpClient();
HttpUriRequest getRequest = new HttpGet(urlStr);
String content = null;

HttpResponse response = httpClient.execute(getRequest);
content = EntityUtils.toString(response.getEntity());
isAlive = response.getStatusLine().getStatusCode() == 200;
return isAlive;
}
}/<code>


擴展: RibbonClient 懶加載處理

由上文可知,默認情況下 Ribbon 在第一次請求才會去創建 LoadBalancer ,這種懶加載機制會導致服務啟動後,第一次調用服務延遲問題,甚至在整合 斷路器(hystrix)等出現超時熔斷 。

為了解決這個問題,我們會配置 Ribbon 的飢餓加載

<code>ribbon:
eager-load:
clients:
- provider/<code>


  • RibbonApplicationContextInitializer 服務啟動後自動調用 工廠提前創建需要的ribbon clients
<code>public class RibbonApplicationContextInitializer
implements ApplicationListener<applicationreadyevent> {
private final List<string> clientNames;

protected void initialize() {
if (clientNames != null) {
for (String clientName : clientNames) {
this.springClientFactory.getContext(clientName);
}
}
}
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
initialize();
}

}/<string>/<applicationreadyevent>/<code>


分享到:


相關文章: