簡介
這篇文章是關(guān)于Spring Cloud Ribbon源碼的解析的文章,在開始前大家必須搞清楚一件事,那就是Spring Cloud Ribbon和Netflix Ribbon,這個很關(guān)鍵,因我剛開始就弄混了,以為Spring Cloud Ribbon就是Netflix的Ribbon,這對查資料會有很大的誤區(qū)。
Spring Cloud Ribbon 和 Netflix Ribbon
- Spring Cloud Ribbon是在Netflix Ribbon的基礎(chǔ)上做了進一步的封裝,使它更加適合與微服。
- 在用法上Spring Cloud Ribbon的路由的服務清單是根據(jù)"注冊中心"微服列表來的會實時更新,Netflix Ribbon需要手動設置。
- Spring Cloud Ribbon的均衡器使用的是Netflix Ribbon的ZoneAwareLoadBalancer,如下圖所示。

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
上面代碼的步驟如下:
- 相關(guān)數(shù)據(jù)配置在config文件中,通過Archaius ConfigurationManager 加載配置數(shù)據(jù)。
- 通過ClientFactory創(chuàng)建RestClient 和 ZoneAwareLoadBalancer(負載均衡器)。
- 使用構(gòu)建器構(gòu)建http請求。請注意,我們只提供URI的路徑部分(“/”)。一旦服務器被ZoneAwareLoadBalancer(負載均衡器)選中,完整的請求鏈接將由RestClient計算。
- 發(fā)送請求是通過RestClient 的executeWithLoadBalancer()方法觸發(fā)的。
- 可以通過修改配置文件來動態(tài)的修改可用的服務的列表。
通過上面的步驟我們可以知道,網(wǎng)絡請求的動作有RestClient實現(xiàn),負載均衡的服務清單的維護和負載均衡的算法是在ZoneAwareLoadBalancer中實現(xiàn)的。
Spring Cloud Ribbon如何實現(xiàn)均衡器功能
- 如何發(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)系,如下圖:

總結(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)用。