Spring Cloud Ribbon 源碼解析

簡介

這篇文章是關(guān)于Spring Cloud Ribbon源碼的解析的文章,在開始前大家必須搞清楚一件事,那就是Spring Cloud Ribbon和Netflix Ribbon,這個很關(guān)鍵,因我剛開始就弄混了,以為Spring Cloud Ribbon就是Netflix的Ribbon,這對查資料會有很大的誤區(qū)。

Spring Cloud Ribbon 和 Netflix Ribbon
  1. Spring Cloud Ribbon是在Netflix Ribbon的基礎(chǔ)上做了進一步的封裝,使它更加適合與微服。
  2. 在用法上Spring Cloud Ribbon的路由的服務清單是根據(jù)"注冊中心"微服列表來的會實時更新,Netflix Ribbon需要手動設置。
  3. Spring Cloud Ribbon的均衡器使用的是Netflix Ribbon的ZoneAwareLoadBalancer,如下圖所示。
ZoneAwareLoadBalancer.png

Spring Cloud Ribbon文檔的地址:http://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.0.0.M5/single/spring-cloud-netflix.html#spring-cloud-ribbon
Netflix Ribbon 文檔地址:https://github.com/Netflix/ribbon/wiki

所以如果我們想理解Spring Cloud Ribbon首先應該理解NetFlix的工作原理

Netflix Ribbon如何實現(xiàn)均衡器功能

先看一段Netflix Ribbon實現(xiàn)簡單路由的demo,代碼如下:

public static void main(String[] args) throws Exception {
  ConfigurationManager.loadPropertiesFromResources("sample-client.properties");  // 1
  System.out.println(ConfigurationManager.getConfigInstance().getProperty("sample-client.ribbon.listOfServers"));
  RestClient client = (RestClient) ClientFactory.getNamedClient("sample-client");  // 2
  HttpClientRequest request = HttpClientRequest.newBuilder().setUri(new URI("/")).build(); // 3
  for (int i = 0; i < 20; i++)  {
    HttpClientResponse response = client.executeWithLoadBalancer(request); // 4
    System.out.println("Status code for " + response.getRequestedURI() + "  :" + response.getStatus());
  }
  ZoneAwareLoadBalancer lb = (ZoneAwareLoadBalancer) client.getLoadBalancer();
  System.out.println(lb.getLoadBalancerStats());
  ConfigurationManager.getConfigInstance().setProperty(
        "sample-client.ribbon.listOfServers", "www.linkedin.com:80,www.google.com:80"); // 5
  System.out.println("changing servers ...");
  Thread.sleep(3000); // 6
  for (int i = 0; i < 20; i++)  {
    HttpClientResponse response = client.executeWithLoadBalancer(request);
    System.out.println("Status code for " + response.getRequestedURI() + "  : " + response.getStatus());
    response.releaseResources();
  }
  System.out.println(lb.getLoadBalancerStats()); // 7
}

配置文件如下:

 sample-client.ribbon.listOfServers=www.microsoft.com:80,www.yahoo.com:80,www.google.com:80

上面代碼的步驟如下:

  1. 相關(guān)數(shù)據(jù)配置在config文件中,通過Archaius ConfigurationManager 加載配置數(shù)據(jù)。
  2. 通過ClientFactory創(chuàng)建RestClient 和 ZoneAwareLoadBalancer(負載均衡器)。
  3. 使用構(gòu)建器構(gòu)建http請求。請注意,我們只提供URI的路徑部分(“/”)。一旦服務器被ZoneAwareLoadBalancer(負載均衡器)選中,完整的請求鏈接將由RestClient計算。
  4. 發(fā)送請求是通過RestClient 的executeWithLoadBalancer()方法觸發(fā)的。
  5. 可以通過修改配置文件來動態(tài)的修改可用的服務的列表。

通過上面的步驟我們可以知道,網(wǎng)絡請求的動作有RestClient實現(xiàn),負載均衡的服務清單的維護和負載均衡的算法是在ZoneAwareLoadBalancer中實現(xiàn)的。

Spring Cloud Ribbon如何實現(xiàn)均衡器功能

  1. 如何發(fā)起一個實現(xiàn)了負載均衡器的請求
@Autowired
RestTemplate restTemplate;

@HystrixCommand(fallbackMethod = "helloFallback")
public String hiService(String name) {
    return restTemplate.getForObject("http://service-hi/hi?name="+name,String.class);
}

RestTemplate 是Spring自己封裝的http請求的客戶端,也就是說它只能發(fā)送一個正常的Http請求,這跟我們要求的負載均衡是有出入的,還有就是這個請求的鏈接上的域名是我們微服的一個服務名,而不是一個真正的域名,那它是怎么實現(xiàn)負載均衡功能的呢?
我們來看看RestTemplate的父類InterceptingHttpAccessor。

public abstract class InterceptingHttpAccessor extends HttpAccessor {

private List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();

/**
 * Sets the request interceptors that this accessor should use.
 */
public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {
    this.interceptors = interceptors;
}

/**
 * Return the request interceptor that this accessor uses.
 */
public List<ClientHttpRequestInterceptor> getInterceptors() {
    return interceptors;
}

@Override
public ClientHttpRequestFactory getRequestFactory() {
    ClientHttpRequestFactory delegate = super.getRequestFactory();
    if (!CollectionUtils.isEmpty(getInterceptors())) {
        return new InterceptingClientHttpRequestFactory(delegate, getInterceptors());
    }
    else {
        return delegate;
    }
}

}

從源碼我們可以知道InterceptingHttpAccessor中有一個攔截器列表List<ClientHttpRequestInterceptor>,如果這個列表為空,則走正常請求流程,如果不為空則走攔截器,所以只要給RestTemplate添加攔截器,而這個攔截器中的邏輯就是Ribbon的負載均衡的邏輯。通過下面的方式可以為RestTemplate配置添加攔截器。

@LoadBalanced
RestTemplate restTemplate() {
    return new RestTemplate();
}

具體的攔截器的生成在LoadBalancerAutoConfiguration這個配置類中,所有的RestTemplate的請求都會轉(zhuǎn)到Ribbon的負載均衡器上(當然這個時候如果你用RestTemplate發(fā)起一個正常的Http請求時走不通,因為它找不到對應的服務。)
這樣就實現(xiàn)了Ribbon的請求的觸發(fā)。

2.攔截器都做了什么?
上面提到過,發(fā)起http后請求后,請求會到達到達攔截器中,在攔截其中實現(xiàn)負載均衡,先看看代碼:

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;

public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
    this.loadBalancer = loadBalancer;
    this.requestFactory = requestFactory;
}

public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
    // for backwards compatibility
    this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}

@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
        final ClientHttpRequestExecution execution) throws IOException {
    final URI originalUri = request.getURI();
    String serviceName = originalUri.getHost();
    Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
    return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
}

我們可以看到在intercept()方法中實現(xiàn)攔截的具體邏輯,首先會根據(jù)傳進來的請求鏈接,獲取微服的名字serviceName,然后調(diào)用LoadBalancerClient的execute(String serviceId, LoadBalancerRequest<T> request)方法,這個方法直接返回了請求結(jié)果,所以正真的路由邏輯在LoadBalancerClient的實現(xiàn)類中,而這個實現(xiàn)類就是RibbonLoadBalancerClient,看看execute()的源碼:

@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
    ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
    Server server = getServer(loadBalancer);
    if (server == null) {
        throw new IllegalStateException("No instances available for " + serviceId);
    }
    RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
            serviceId), serverIntrospector(serviceId).getMetadata(server));

    return execute(serviceId, ribbonServer, request);
}

首先是獲得均衡器ILoadBalancer這個類上面講到過這是Netflix Ribbon中的均衡器,這是一個抽象類,具體的實現(xiàn)類是ZoneAwareLoadBalancer上面也講到過,每一個微服名對應一個均衡器,均衡器中維護者微服名下所有的服務清單。getLoadBalancer()方法通過serviceId獲得對應的均衡器,getServer()方法通過對應的均衡器在對應的路由的算法下計算得到需要路由到Server,Server中有該服務的具體域名等相關(guān)信息。得到了具體的Server后執(zhí)行正常的Http請
求,整個請求的負載均衡邏輯就完成了。

我畫了個Ribbon請求的一個流程圖,縱向是調(diào)用順序,橫向是繼承或?qū)崿F(xiàn)的關(guān)系,如下圖:


ribbon流程圖.png

總結(jié)

這篇文章講到是Spring Cloud Ribbon的源碼解析,在微服中Ribbon和 Hystrix通常是一起使用的,其實直接使用Ribbon和Hystrix實現(xiàn)服務間的調(diào)用并不是很方便,通常在Spring Cloud中我們使用Feign完成服務間的調(diào)用,而Feign是對Ribbon和Hystrix做了進一步的封裝方便大家使用,對Ribbon的學習能幫你更好的完成Spring Cloud中服務間的調(diào)用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容