本文章首发自本人公众号: 壹枝花算不算浪漫 ,如若转载请标明来源!
感兴趣的小伙伴可关注个人公众号:壹枝花算不算浪漫
前面文章已经梳理清楚了Eureka相关的概念及源码,接下来开始研究下Ribbon的实现原理。
我们都知道Ribbon在spring cloud中担当 负载均衡 的角色, 当两个Eureka Client互相调用的时候,Ribbon能够做到调用时的负载,保证多节点的客户端均匀接收请求。(这个有点类似于前端调用后端时Nginx做的负载均衡)
本讲主通过一个简单的demo来了解ribbon内部实现,这里主要是对ribbon有个宏观的认识,后续篇章会一步步通过debug的方式对ribbon的细节做一个全面的讲解。
原创不易,如若转载 请标明来源!
博客地址: 一枝花算不算浪漫 微信公众号:壹枝花算不算浪漫
首先看下我们这里的demo,目录结构如下:
这里有3个模块,eurekaServer作为注册中心,serviceA和serviceB分别作为EurekaClient。
代码地址上传到了自己的git: github.com/barrywangme…
启动了eureka client如下: 服务A 2个 : 一个端口号为8087,另一个为8088 服务B 1个
查看注册中心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
处理的细节。