前面文章已經梳理清楚了Eureka相關的概念及源碼,接下來開始研究下Ribbon的實現原理。前端
咱們都知道Ribbon在spring cloud中擔當負載均衡的角色, 當兩個Eureka Client互相調用的時候,Ribbon可以作到調用時的負載,保證多節點的客戶端均勻接收請求。(這個有點相似於前端調用後端時Nginx作的負載均衡)java
本講主經過一個簡單的demo來了解ribbon內部實現,這裏主要是對ribbon有個宏觀的認識,後續篇章會一步步經過debug的方式對ribbon的細節作一個全面的講解。git
目錄以下:github
原創不易,如若轉載 請標明來源!spring
博客地址:一枝花算不算浪漫
微信公衆號:壹枝花算不算浪漫 (文章底部有公衆號二維碼)後端
首先看下咱們這裏的demo,目錄結構以下:springboot
這裏有3個模塊,eurekaServer做爲註冊中心,serviceA和serviceB分別做爲EurekaClient。微信
代碼地址上傳到了本身的git:
https://github.com/barrywangmeng/spring-cloud-learn負載均衡
啓動了eureka client以下:
服務A 2個: 一個端口號爲8087,另外一個爲8088
服務B 1個
ide
查看註冊中心Dashboard
服務B調用服務A中的接口
查看負載均衡狀況
第一次調用服務B的greeting方法:
第二次調用服務A的greeting方法:
這裏能夠看到服務A調用的時候加了一個註解: @LoadBalanced
服務B第一次調用到了服務A的8088那個節點
服務B第二次調用到了服務A的8087那個節點
這裏就能夠證實使用@LoadBalanced
自動對咱們的http請求加了負載均衡,接下來咱們就用@LoadBalanced
來一步步往下看。
接下來看下@LoadBalanced
的源碼:
/** * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient * @author Spencer Gibb */@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Qualifierpublic @interface LoadBalanced {}
這裏主要看註釋,這裏意思是使用這個註解後能夠將普通的RestTemplate
使用 LoadBalanceClient
這個類去處理。
接着咱們看下LoadBalanced
相關的配置。
咱們知道,springboot + springcloud 對應的組件都會有相應的XXXAutoConfigure配置類,同理,咱們在LoadBalanced
同級包下能夠找到對應的AutoConfigure類:LoadBalancerAutoConfiguration
, 先看下類的定義:
@Configuration @ConditionalOnClass(RestTemplate.class) @ConditionalOnBean(LoadBalancerClient.class) @EnableConfigurationProperties(LoadBalancerRetryProperties.class) public class LoadBalancerAutoConfiguration { }
看到這裏有個 @ConditionalOnClass(RestTemplate.class)
,這個含義是 只有存在RestTemplate
這個類的時該配置纔會生效。
接着看LoadBalancerAutoConfiguration
中的一些方法:
public class LoadBalancerAutoConfiguration { @LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList(); @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializer( final List<RestTemplateCustomizer> customizers) { return new SmartInitializingSingleton() { @Override public void afterSingletonsInstantiated() { for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) { for (RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); } } } }; } }
咱們能夠看下loadBalancedRestTemplateInitializer
方法,這個裏面會遍歷restTemplates
而後調用customize()
方法進行特殊處理。
public class LoadBalancerAutoConfiguration { @LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList(); @Configuration @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class LoadBalancerInterceptorConfig { @Bean public LoadBalancerInterceptor ribbonInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) { return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); } @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer( final LoadBalancerInterceptor loadBalancerInterceptor) { return new RestTemplateCustomizer() { @Override public void customize(RestTemplate restTemplate) { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); } }; } } }
這裏面是爲每個restTemplate
添加一個loadBalancerInterceptor
攔截器,緊接着看一下LoadBalancerInterceptor.java
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor { private LoadBalancerClient loadBalancer; private LoadBalancerRequestFactory requestFactory; @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)); } }
這裏面就很簡單了,將serviceName(這裏就是對應咱們demo中的:ServiceA)和request、body、excution等組成的新的request傳遞給LoadBalancerClient
,而後調用其中的execute
,這個方法的實現繼續往下看RibbonLoadBalancerClient
接下來再看一下 LoadBalanceClient
:
public interface LoadBalancerClient extends ServiceInstanceChooser { <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException; <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException; URI reconstructURI(ServiceInstance instance, URI original); }
這個接口只有一個實現類: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); }
接着就能夠在這個方法上愉快的debug了,咱們先看看ILoadBalancer
是幹嗎的:
咱們經過debug能夠看到 獲取的ILoadBalancer
已經獲取到服務A全部的節點信息了,這一章就先不延伸下去了,後面會詳細來講ILoadBalancer
處理的細節。
這一篇主要講解了一個RestTemplate
加上@LoadBalanced
註解後是如何獲取到請求服務的多個節點信息的,經過debug 咱們能夠很清晰的看到請求流程,最後畫一個圖來總結一下:
本文章首發自本人博客:https://www.cnblogs.com/wang-meng 和公衆號:壹枝花算不算浪漫,如若轉載請標明來源!
感興趣的小夥伴可關注我的公衆號:壹枝花算不算浪漫