ribbon简单点讲就是通过@LoadBalanced去负载到相应的服务中去,其中通过http(一般配合RestTemplate)请求去访问提供者接口,ribbon通过拦截器/代理将服务名等替换为ip等信息进行访问,如果提供者存在集群情况,ribbon会根据相应的算法(默认轮询、随机、权重等)进行负载均衡。
而feign就是对于ribbon的一个简单封装。
一、Ribbon源码分析:
1、以LoadBalancerAutoConfiguration为入口(找这些组件一般以AutoConfiguration等默认配置类为入口)
这个配置类就是初始化几个bean。然后用restTemplateCustomizers去定制所有我们加了@LoadBalanced注解的restTemplate
定制的过程:将我们注入的restTemplate的拦截器放进拦截器集合list中。因为之后使用restTemplate会对拦截器集合进行统一拦截处理。(即之后调用restTemplate会处理其拦截方法)
//简单点就是将使用了@LoadBalanced的restTemplate的拦截器放进拦截器集合中,使得其拦截器在使用restTemplate时生效
@Configuration( proxyBeanMethods = false ) @ConditionalOnClass({RestTemplate.class}) @ConditionalOnBean({LoadBalancerClient.class}) @EnableConfigurationProperties({LoadBalancerRetryProperties.class}) public class LoadBalancerAutoConfiguration { //存放了所有被@LoadBalanced修饰的restTemplate @LoadBalanced @Autowired(required = false ) private List<RestTemplate> restTemplates = Collections.emptyList(); @Autowired(required = false) private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList(); public LoadBalancerAutoConfiguration() { } //1、SmartInitializingSingleton //该bean会用restTemplateCustomizers定制所有的restTemplate @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) { return () -> { restTemplateCustomizers.ifAvailable((customizers) -> { Iterator var2 = this.restTemplates.iterator(); while(var2.hasNext()) { RestTemplate restTemplate = (RestTemplate)var2.next(); Iterator var4 = customizers.iterator(); while(var4.hasNext()) { RestTemplateCustomizer customizer = (RestTemplateCustomizer)var4.next(); customizer.customize(restTemplate); } } }); }; } //2、RestTemplateCustomizer //该bean用于定制restTemplate。定制的逻辑是将LoadBalancerInterceptor拦截器封装到restTemplate的拦截器集合去(之后restTemplate会对拦截器集合进行统一拦截处理处理) @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer(final RetryLoadBalancerInterceptor loadBalancerInterceptor) { return (restTemplate) -> { List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; } } //3、LoadBalancerInterceptor ,这个拦截器就是我们去替换ip等请求路径的拦截器 @Configuration( proxyBeanMethods = false ) @ConditionalOnMissingClass({"org.springframework.retry.support.RetryTemplate"}) static class LoadBalancerInterceptorConfig { LoadBalancerInterceptorConfig() { } @Bean public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) { return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); } @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) { return (restTemplate) -> { List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; } } }
2、下面我们直接去看看其拦截器的实现:拦截器一般就看intercept方法
我们在使用restTemplate时:
restTemplate.postForObject("http://cloud-payment-service"+"/payment/create",payment, CommonResult.class)
其中URI即截取上面的请求路径,而servername则是获取服务名。然后将服务名称放进execute方法。(后面会根据注册中心获取到相应服务名的真实ip端口等信息)
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException { URI originalUri = request.getURI(); String serviceName = originalUri.getHost(); Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri); return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution)); }
这是我们进入excute方法,看看是怎么进行获取真实服务ip等信息的
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException { //1、获取一个负载均衡器 ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId); //2、多个实例时,根据负载均衡器的算法选择出一个实例出来 Server server = this.getServer(loadBalancer, hint); if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } else { RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server)); return this.execute(serviceId, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request); } }
2.1、那么我们需要进去看看他是怎么获取到一个负载均衡器的(这里我们不进去该方法,因为最后调用的是一个接口,所以我们直接去找他的实现类) 查找RibbonClientConfiguration配置类
此时我们已经找到了默认的负载均衡器ZoneAwareLoadBalancer了,至于该负载均衡器是怎么进行负载均衡的?我们后面讲
@Configuration @EnableConfigurationProperties @Import({HttpClientConfiguration.class, OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class}) public class RibbonClientConfiguration { @Bean @ConditionalOnMissingBean public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) { //如果配置文件有配置使用哪个负载均衡器就优先使用配置文件的。 //否则则使用ZoneAwareLoadBalancer return (ILoadBalancer)(this.propertiesFactory.isSet(ILoadBalancer.class, this.name) ? (ILoadBalancer)this.propertiesFactory.get(ILoadBalancer.class, config, this.name) : new ZoneAwareLoadBalancer(config, rule, ping, serverList, serverListFilter, serverListUpdater)); } //加载负载均衡规则rule @Bean @ConditionalOnMissingBean public IRule ribbonRule(IClientConfig config) { //先从配置文件中拿 if (this.propertiesFactory.isSet(IRule.class, this.name)) { return (IRule)this.propertiesFactory.get(IRule.class, config, this.name); } else { //没有则使用默认的负载均衡规则 ZoneAvoidanceRule rule = new ZoneAvoidanceRule(); rule.initWithNiwsConfig(config); return rule; } } @Bean @ConditionalOnMissingBean public IPing ribbonPing(IClientConfig config) { return (IPing)(this.propertiesFactory.isSet(IPing.class, this.name) ? (IPing)this.propertiesFactory.get(IPing.class, config, this.name) : new DummyPing()); } }
2.2、多个实例时,根据负载均衡器的算法选择出一个实例出来
进入getServer方法:
这里调用了负载均衡器的loadBalancer.chooseServer方法,即选择一个实例出来。该chooseServer方法中会配合注册中心的信息去获取一个实例protected Server getServer(ILoadBalancer loadBalancer, Object hint) { return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default"); }
此时我们要搞清楚是怎么匹配注册中、怎么进行负载均衡的,这是就要回去负载均衡器中查看chooseServer方法是怎么实现的了。
2.3、进入默认的负载均衡器ZoneAwareLoadBalancer中,查看chooseServer方法是怎么实现负载均衡的?+怎么和nacos配合的?
2.3.1 、怎么实现负载均衡的?
public Server chooseServer(Object key) { if (ENABLED.get() && this.getLoadBalancerStats().getAvailableZones().size() > 1) { //省略 } } else { //进入该方法 logger.debug("Zone aware logic disabled or there is only one zone"); return super.chooseServer(key); } }
进入父类方法
public Server chooseServer(Object key) { if (this.counter == null) { this.counter = this.createCounter(); } this.counter.increment(); if (this.rule == null) { return null; } else { try { return this.rule.choose(key); } catch (Exception var3) { logger.warn("LoadBalancer [{}]: Error choosing server for key {}", new Object[]{this.name, key, var3}); return null; } } }
进入rule.choose()方法。发现是一个接口,打开实现类发现就是所谓的8个负载均衡算法实现类
public interface IRule { Server choose(Object var1); void setLoadBalancer(ILoadBalancer var1); ILoadBalancer getLoadBalancer(); }
而我们默认的是走PredicateBasedRule这个实现类(轮询),(其实从RibbonClientConfiguration中注入的ribbonRule中使用的是默认的ZoneAvoidanceRule,而其实现又是PredicateBasedRule所以也可以再一次判断默认就是走PredicateBasedRule规则),进入看下该类:
public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule { public PredicateBasedRule() { } public abstract AbstractServerPredicate getPredicate(); public Server choose(Object key) { ILoadBalancer lb = this.getLoadBalancer(); //这里使用轮询的算法 Optional<Server> server = this.getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key); return server.isPresent() ? (Server)server.get() : null; } }
进入轮询方法:
public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) { //获取可以实例集合。这里在讲怎么配合nacos那里再讲怎么用 List<Server> eligible = this.getEligibleServers(servers, loadBalancerKey); //incrementAndGetModulo进行轮询算法 return eligible.size() == 0 ? Optional.absent() : Optional.of(eligible.get(this.incrementAndGetModulo(eligible.size()))); }
进去incrementAndGetModulo方法看看:
计算方法:current = this.nextIndex.get(); next = (current + 1) % modulo;
这里我们拿到的是上一个next值,然后把我们现在的值current+1后取模然后cas存进去。下一次取的就是这次的next值。
例如我们有2台机器(分别代表0、1),则此时传进去的modulo是2。
第一次时:current为0,next为1。此时返回0回去,则代表访问第一台机器(0号机器)。
第二次时:corrent为1(上次的next),next为0,此时返回1,则代表访问第一台机器。一次轮询。
private int incrementAndGetModulo(int modulo) { int current; int next; do { current = this.nextIndex.get(); next = (current + 1) % modulo; } while(!this.nextIndex.compareAndSet(current, next) || current >= modulo); return current; }
到此怎么进行负载均衡的解析到此结束,下面要解析ribbon怎么去配合nacos拿到实例信息并且拦截替换请求地址的?
2.3.1、怎么配合nacos的?
在讲之前需要再说下ribbon和nacos client端的关系
其中ribbon自己也有自己对于服务实例的缓存列表,nacos client每次从nacos server拉取到服务列表后,ribbon会周期性的去nacos的client端更新相应的列表信息到ribbon本地缓存。
(riibon是可以自己去配置文件配置各个实例的ip信息,所以ribbon是可以不依赖注册中心/其他springcloud组件的)
其实在前面的类中就已经将serverlist(服务实例列表)存进负载均衡器中了
@Bean @ConditionalOnMissingBean public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) { //其中serverList即实例列表 return (ILoadBalancer)(this.propertiesFactory.isSet(ILoadBalancer.class, this.name) ? (ILoadBalancer)this.propertiesFactory.get(ILoadBalancer.class, config, this.name) : new ZoneAwareLoadBalancer(config, rule, ping, serverList, serverListFilter, serverListUpdater)); }
那我们不断的进入ZoneAwareLoadBalancer最后来到其父类DynamicServerListLoadBalancer,看器构造方法
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList<T> serverList, ServerListFilter<T> filter, ServerListUpdater serverListUpdater) { super(clientConfig, rule, ping); this.isSecure = false; this.useTunnel = false; this.serverListUpdateInProgress = new AtomicBoolean(false); this.updateAction = new NamelessClass_1(); this.serverListImpl = serverList; this.filter = filter; this.serverListUpdater = serverListUpdater; if (filter instanceof AbstractServerListFilter) { ((AbstractServerListFilter)filter).setLoadBalancerStats(this.getLoadBalancerStats()); } this.restOfInit(clientConfig); }
进入restOfInit方法:
void restOfInit(IClientConfig clientConfig) { boolean primeConnection = this.isEnablePrimingConnections(); this.setEnablePrimingConnections(false); //1、定时更新nacos的实例列表 this.enableAndInitLearnNewServersFeature(); //2、获取所有nacos实例列表 this.updateListOfServers(); if (primeConnection && this.getPrimeConnections() != null) { this.getPrimeConnections().primeConnections(this.getReachableServers()); } this.setEnablePrimingConnections(primeConnection); LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString()); }
2.3.1.1、此时我们看看怎么去更新nacos实例列表的?
进入其中的方法,其中调用了serverListUpdater.start(this.updateAction);方法,start方法不用进去看,他最后是一个接口,其实现是最后调用了updateAction的doUpdate方法public void enableAndInitLearnNewServersFeature() { LOGGER.info("Using serverListUpdater {}", this.serverListUpdater.getClass().getSimpleName()); this.serverListUpdater.start(this.updateAction); }
public synchronized void start(final UpdateAction updateAction) { if (this.isActive.compareAndSet(false, true)) { Runnable wrapperRunnable = new Runnable() { public void run() { if (!PollingServerListUpdater.this.isActive.get()) { if (PollingServerListUpdater.this.scheduledFuture != null) { PollingServerListUpdater.this.scheduledFuture.cancel(true); } } else { try { //注意这个doUpdate的方法 updateAction.doUpdate(); PollingServerListUpdater.this.lastUpdated = System.currentTimeMillis(); } catch (Exception var2) { PollingServerListUpdater.logger.warn("Failed one update cycle", var2); } } } }; //定时的去调用上面的doupdate方法 this.scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(wrapperRunnable, this.initialDelayMs, this.refreshIntervalMs, TimeUnit.MILLISECONDS); } else { logger.info("Already active, no-op"); } }
而update方法的实现类:(DynamicServerListLoadBalancer类中)其实现类中最后是调用了updateListOfServers方法。
public DynamicServerListLoadBalancer(IClientConfig clientConfig) { this.isSecure = false; this.useTunnel = false; this.serverListUpdateInProgress = new AtomicBoolean(false); class NamelessClass_1 implements UpdateAction { NamelessClass_1() { } public void doUpdate() { DynamicServerListLoadBalancer.this.updateListOfServers(); } } this.updateAction = new NamelessClass_1(); this.initWithNiwsConfig(clientConfig); }
updateListOfServers()?这个方法是不是很眼熟?这个就是上面获取所有nacos实例的方法(往上看)
那么周期更新nacos的实例最后还是去调用了获取nacos全部实例的方法。那么不就将定时更新和全部获取的分析合起来了。也就是说定时更新方法实际调用的还是获取全部实例的方法updateListOfServers
2.1.3.2,统一分析获取全部实例方法updateListOfServers
@VisibleForTesting public void updateListOfServers() { List<T> servers = new ArrayList(); if (this.serverListImpl != null) { //1、获取nacos client的缓存列表 servers = this.serverListImpl.getUpdatedListOfServers(); LOGGER.debug("List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers); if (this.filter != null) { servers = this.filter.getFilteredListOfServers((List)servers); LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers); } } //2、将获取到的列表存到ribbon的本地缓存中 this.updateAllServerList((List)servers); }
进入getUpdatedListOfServers方法,发现是个接口,找到其实现类NacosServerList,
public List<NacosServer> getUpdatedListOfServers() { return this.getServers(); }
进入getServers,这里拿取到nacos的client端缓存的实例列表并返回回去
private List<NacosServer> getServers() { try { //从nacos的client端缓存中拿去列表信息 List<Instance> instances = this.discoveryProperties.namingServiceInstance().selectInstances(this.serviceId, true); return this.instancesToServerList(instances); } catch (Exception var2) { throw new IllegalStateException("Can not get service instances from nacos, serviceId=" + this.serviceId, var2); } }
我们这个时候要回到前面的updateListOfServers方法中,其中有个updateAllServerList方法就是将这个信息存到ribbon的本地缓存中。
(注意,只获取健康的实例)
对于ribbon拿到信息怎么将uri由服务名换成ip端口等的就不用我多说了。
至此ribbon的解析到此结束
ribbon分析图:
二、ribbon的小总结
ribbon通过设置使用了@LoadBalanced的restTemplate的拦截器,当使用其restTemplate时会触发拦截器逻辑。ribbon会先初始化一个负载均衡器(其中包含服务实例列表+负载均衡规则(默认轮询,但可以自己配置或自定义))
对于ribbon怎么获取实例:ribbon本地也会维护一个缓存,会周期性的向nacos client中拉取健康的服务列表。(可以不依赖nacos,自己在配置文件中配置各个ip等,但一般不这么做)
对ribbon怎么进行负载均衡规则选取:如果由自定义/配置文件配置,默认先加载自定义/配置文件的,如果没有则使用轮询的规则。
ribbon在拿到实例信息时会将含有服务名的uri替换成ip/端口的uri,然后根据负载均衡规则发起http请求到相应的实例真实地址。
三、feign源码分析
feign的源码非常简单,就是使用代理将ribbon进行了封装。
@FeignClient(value = "cloud-payment-service",fallback = PaymentHystrixServiceImpl.class)//调用的服务名 @Component public interface PaymentFeignService { @GetMapping(value = "/payment/get/{id}") public CommonResult getPaymentById(@PathVariable("id") Long id); @GetMapping(value="/payment/lb") public String getPaymentLB() throws InterruptedException; } 启动类再加上个@EnableFeignClients注解
feign简单的讲就是根据ribbon拿到的实例信息,然后把上面的服务名+接口地址,然后帮我们把uri替换拼接,最后用http请求去访问。
???看到这里,是否就觉得,ribbon不是帮我们做了吗?没错,ribbon是有做这些,但是如果使用ribbon,我们需要去注入一个带有@LoadBalanced的restTemplate,然后再用这个restTemplate去自己发送请求(然后ribbon内部帮我们去修改uri)
当时feign不用我们去写这么多了,feign使用了声明式接口,帮我们把注入restTemplate和手动调用restTemplate的方法都给封装好了,我们只需要告诉他我们要调用哪个服务的哪个接口即可完成调用。
简单点讲,feign对于ribbon进行进一步封装,简化了我们的使用。
feign用的是okHttpclient
----------------------------------------------------------------------------------------------------
下面进行源码解析:
以@EnableFeignClients注解为入口
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Import({FeignClientsRegistrar.class}) public @interface EnableFeignClients { String[] value() default {}; String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; Class<?>[] defaultConfiguration() default {}; Class<?>[] clients() default {}; }
进入@Import({FeignClientsRegistrar.class})这个类,看到registerBeanDefinitions方法,该方法将@EnableFeignClients里配置的信息和@FeignClients的接口都注入spring容器中
注意:@EnableFeignClients如果没配置默认是同包下的扫描进来
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //扫描@EnableFeignClients里配置的信息并注册 this.registerDefaultConfiguration(metadata, registry); //看名字即注册FeignClients,即我们写的feign的接口注册到spring容器中 this.registerFeignClients(metadata, registry); }
进入下面的方法
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //扫描器 ClassPathScanningCandidateComponentProvider scanner = this.getScanner(); scanner.setResourceLoader(this.resourceLoader); //扫描所有feignclient注解的信息并放到容器中 Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName()); AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class); Class<?>[] clients = attrs == null ? null : (Class[])((Class[])attrs.get("clients")); Object basePackages; if (clients != null && clients.length != 0) { final Set<String> clientClasses = new HashSet(); basePackages = new HashSet(); Class[] var9 = clients; int var10 = clients.length; for(int var11 = 0; var11 < var10; ++var11) { Class<?> clazz = var9[var11]; ((Set)basePackages).add(ClassUtils.getPackageName(clazz)); clientClasses.add(clazz.getCanonicalName()); } AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() { protected boolean match(ClassMetadata metadata) { String cleaned = metadata.getClassName().replaceAll("\\$", "."); return clientClasses.contains(cleaned); } }; scanner.addIncludeFilter(new FeignClientsRegistrar.AllTypeFilter(Arrays.asList(filter, annotationTypeFilter))); } else { scanner.addIncludeFilter(annotationTypeFilter); basePackages = this.getBasePackages(metadata); } Iterator var17 = ((Set)basePackages).iterator(); while(var17.hasNext()) { String basePackage = (String)var17.next(); Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage); Iterator var21 = candidateComponents.iterator(); while(var21.hasNext()) { BeanDefinition candidateComponent = (BeanDefinition)var21.next(); if (candidateComponent instanceof AnnotatedBeanDefinition) { AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition)candidateComponent; AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface"); Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName()); String name = this.getClientName(attributes); this.registerClientConfiguration(registry, name, attributes.get("configuration")); //进入registerFeignClient方法 this.registerFeignClient(registry, annotationMetadata, attributes); } } } }
进入registerFeignClient方法:
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { String className = annotationMetadata.getClassName(); //生成代理的工厂bean(FeignClientFactoryBean),并且将feignclient注解的相关信息给到该工厂bean,并且将其放进spring容器 BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class); this.validate(attributes); definition.addPropertyValue("url", this.getUrl(attributes)); definition.addPropertyValue("path", this.getPath(attributes)); String name = this.getName(attributes); definition.addPropertyValue("name", name); String contextId = this.getContextId(attributes); definition.addPropertyValue("contextId", contextId); definition.addPropertyValue("type", className); definition.addPropertyValue("decode404", attributes.get("decode404")); definition.addPropertyValue("fallback", attributes.get("fallback")); definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory")); definition.setAutowireMode(2); String alias = contextId + "FeignClient"; AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); boolean primary = (Boolean)attributes.get("primary"); beanDefinition.setPrimary(primary); String qualifier = this.getQualifier(attributes); if (StringUtils.hasText(qualifier)) { alias = qualifier; } BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias}); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); }
然后进入该工厂bean中,其中有个loadBalance方法利用jdk动态代理生成代理对象。
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware { protected <T> T loadBalance(Builder builder, FeignContext context, HardCodedTarget<T> target) { Client client = (Client)this.getOptional(context, Client.class); if (client != null) { builder.client(client); Targeter targeter = (Targeter)this.get(context, Targeter.class); return targeter.target(this, builder, context, target); } else { throw new IllegalStateException("No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?"); } } }
代码就不在深入,最后jdk动态代理会生成一个LoadBalancerFeignClient的代理对象。该类中有个execute方法,我们在使用feign接口调用时,实际是在使用LoadBalancerFeignClient的代理对象调用它的execute方法。
public class LoadBalancerFeignClient implements Client { public Response execute(Request request, Options options) throws IOException { try { URI asUri = URI.create(request.url()); String clientName = asUri.getHost(); URI uriWithoutHost = cleanUrl(request.url(), clientName); //使用ribbonRequest去调用整合的ribbon从而实现的负载均衡 RibbonRequest ribbonRequest = new RibbonRequest(this.delegate, request, uriWithoutHost); IClientConfig requestConfig = this.getClientConfig(options, clientName); return ((RibbonResponse)this.lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig)).toResponse(); } catch (ClientException var8) { IOException io = this.findIOException(var8); if (io != null) { throw io; } else { throw new RuntimeException(var8); } } } }
该execute方法就是整合了ribbon去实现的负载均衡。
其实ribbon也有一个LoadBalancerRibbonClient,不过ribbon是利用拦截器去调用的execute方法,而LoadBalancerFeignClient是利用jdk动态代理对象去调用的execute方法。
最后给个分析图:
四、Feign的小总结
feign通过动态代理得到一个代理工厂bean:FeignClientFactoryBean,该工厂bean最后会生成一个LoadBalancerFeignClient的代理对象,当我们去调用feign接口时,feign的代理对像会调用execute方法,将url进行拼接(含服务名的url,url的替换还是需要ribbon来完成),然后使用RibbonRequest去调用整合的Ribbon实现负载均衡。最后feign拿到了拼接好的url(替换了服务名的url)进行http请求(ribbon接到调用时,会通过拦截器去调用ribbon自身的execute方法从而实现负载均衡整个过程)(feign最后还是要用ribbon来替换服务名、服务列表信息等)
至此ribbon/feign解析到此结束,下篇要解析比较重要的nacos原理