SpringCloud之Feign与Ribbon双手互搏

493 阅读12分钟

一、简单介绍

Feign是声明性的web服务客户端。是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,封装了http调用流程。

调用流程

Feign Client 原理和使用

二、pom配置

feign底层基于http协议,适应绝大部分内外部API调用的应用场景,并且SpringCloud对feign已经有了比较好的封装。使用上可以依赖于SpringCloud封装过的feign:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

Feign在默认情况下使用的是JDK原生的URLConnection发送HTTP请求,没有连接池,但是对每个地址会保持一个长连接,即利用HTTP的 persistence connection。建议替换为Apache HttpClient,作为底层的http client包,从而获取连接池、超时时间等与性能息息相关的控制能力:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

在配置文件中启用ApacheHttpClient:

feign.httpclient.enabled=true

三、参数配置

  • 配置文件方式

    feign:
      client:
        config:
          feignName:
            connectTimeout: 5000
            readTimeout: 5000
            loggerLevel: full
            errorDecoder: com.example.SimpleErrorDecoder
            retryer: com.example.SimpleRetryer
            requestInterceptors:
              - com.example.FooRequestInterceptor
              - com.example.BarRequestInterceptor
            decode404: false
            encoder: com.example.SimpleEncoder
            decoder: com.example.SimpleDecoder
            contract: com.example.SimpleContract
    
  • 配置类方式

    /**
     * @author :Nickels
     * @date :2021/3/15
     * @desc :配置文件可参照spring FeignClientsConfiguration
     */
    @Configuration
    public class BikeFeignConfiguration {
    
        /**
         *消息转换对象
         */
        @Autowired
        private ObjectFactory<HttpMessageConverters> messageConverters;
    
        @Autowired(
                required = false
        )
        private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList();
    
        private Logger logger;
    
        /**
         * 请求拦截配置
         * @return
         */
        @Bean
        public RequestInterceptor requestInterceptor(){
            return new FeignRequestInterceptor();
        }
    
    
        /**
         * basic 认证配置
         * @return
         */
        @Bean
        public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
            return new BasicAuthRequestInterceptor("user", "password");
        }
    
    
    
        /**
         * 超时时间配置
         * @return
         */
        @Bean
        public Request.Options options() {
            return new Request.Options(5000L, TimeUnit.MILLISECONDS, 10000L,TimeUnit.MILLISECONDS,true);
        }
    
        @Bean
        @ConditionalOnMissingBean
        public Retryer feignRetryer() {
            //重试的间隔时间是动态变化的,越往后间隔时间越长,但最长不会超过设置的最大间隔(maxPeriod)
            return new Retryer.Default(1000L,2000L,3);
            //return Retryer.NEVER_RETRY; //Retryer不去重试
        }
    
        /**
         * 日志级别
         * @return
         */
        @Bean
        Logger.Level feignLoggerLevel() {
            return Logger.Level.FULL;
        }
    
    
        /**
         * @ConditionalOnBean // 当给定的在bean存在时,则实例化当前Bean
         * @ConditionalOnMissingBean // 当给定的在bean不存在时,则实例化当前Bean
         * @ConditionalOnClass // 当给定的类名在类路径上存在,则实例化当前Bean
         * @ConditionalOnMissingClass // 当给定的类名在类路径上不存在,则实例化当前Bean
         * @return
         */
        @Bean
        @ConditionalOnMissingBean
        @ConditionalOnMissingClass({"org.springframework.data.domain.Pageable"})
        public Encoder fiegnEncoder() {
            return new SpringEncoder(this.messageConverters);
    
        }
    
    
    
        @Bean
        @ConditionalOnClass(
                name = {"org.springframework.data.domain.Pageable"}
        )
        @ConditionalOnMissingBean
        public Encoder feignEncoderPageable() {
            return new PageableSpringEncoder(new SpringEncoder(this.messageConverters));
        }
    
        @Bean
        @ConditionalOnMissingBean
        public Decoder feignDecoder() {
    //        return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
            return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(feignHttpMessageConverter())));
        }
    
        @Bean
        @ConditionalOnMissingBean
        public ErrorDecoder errorDecoder() {
            return new ErrorDecoder.Default();
        }
    
    
        @Bean
        @ConditionalOnMissingBean({FeignLoggerFactory.class})
        public FeignLoggerFactory feignLoggerFactory() {
            return new DefaultFeignLoggerFactory(this.logger);
        }
    
        @Bean
        @ConditionalOnMissingBean
        public Contract feignContract(ConversionService feignConversionService) {
            return new SpringMvcContract(this.parameterProcessors, feignConversionService);
        }
    
    
    
        /**
         * 装配消息转换类
         * @return
         */
        @Bean
        public ObjectFactory<HttpMessageConverters> feignHttpMessageConverter() {
            HttpMessageConverters converters = null;
            if (ClassUtils.isPresent("feign.jaxb.JAXBContextFactory",Thread.currentThread().getContextClassLoader())) {
                converters = new HttpMessageConverters(new Jaxb2Jackson2HttpMessageConverter());
            } else {
                converters = new HttpMessageConverters(new CustMappingJackson2HttpMessageConverter());
            }
            final HttpMessageConverters httpMessageConverters = converters;
            return new ObjectFactory<HttpMessageConverters>() {
                @Override
                public HttpMessageConverters getObject() throws BeansException {
                    return httpMessageConverters;
                }
            };
        }
    
        public class Jaxb2Jackson2HttpMessageConverter extends Jaxb2RootElementHttpMessageConverter {
    
            @Override
            public boolean canRead(Class<?> clazz, MediaType mediaType) {
                return (ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement",Thread.currentThread().getContextClassLoader())
                        || clazz.isAnnotationPresent(XmlType.class)) && canRead(mediaType);
            }
    
            public Jaxb2Jackson2HttpMessageConverter() {
                MediaType[] mediaTypes = new MediaType[]{
                        MediaType.TEXT_HTML,
                        MediaType.TEXT_PLAIN,
                        MediaType.TEXT_XML,
                        MediaType.APPLICATION_XML,
                };
                setSupportedMediaTypes(Arrays.asList(mediaTypes));
            }
        }
    
    
        public class CustMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
            CustMappingJackson2HttpMessageConverter() {
                MediaType[] mediaTypes = new MediaType[]{
                        MediaType.APPLICATION_JSON,
                        MediaType.APPLICATION_OCTET_STREAM,
                        MediaType.APPLICATION_JSON_UTF8,
                        MediaType.TEXT_HTML,
                        MediaType.TEXT_PLAIN,
                        MediaType.TEXT_XML,
                        MediaType.APPLICATION_ATOM_XML,
                        MediaType.APPLICATION_FORM_URLENCODED,
                        MediaType.APPLICATION_PDF,
                };
                setSupportedMediaTypes(Arrays.asList(mediaTypes));
            }
        }
    }
    
    

四、使用方式

  • 常规方式

    常规的方式,我们一般是去定义一个interface并添加@FeignClient注解的方式

    /**
     * @author :Nickels
     * @date :2021/5/19
     * @desc :
     */
    @FeignClient(name = "bike-business")
    public interface BusinessFeign {
    
        @GetMapping("/bike-business/hello")
        String helloBusiness();
    }
    	
    
  • 动态创建

    动态常见feign我们需要用到Feign.Builder去创建一个具体的实例客户端,可通过url和name两种方式创建

    定义动态创建客户端接口

    /**
     * @author :Nickels
     * @date :2021/3/15
     * @desc :
     */
    public interface BikeFeignService {
    
        <T> T createClientByUrl(Class<T> t,String url);
    
        <T> T createClientByName(Class<T> apiType, String name);
    }
    

    接口的实现

    /**
     * @author :Nickels
     * @date :2021/3/15
     * @desc :
     */
    @Service
    @Import(BikeFeignConfiguration.class)
    public class BikeFeignServiceImpl implements BikeFeignService {
    
        private final Feign.Builder urlBuilder;
        private final Feign.Builder nameBuilder;
    
        @Autowired
        public BikeFeignServiceImpl(Decoder decoder, Encoder encoder, Client client, Contract contract, ErrorDecoder errorDecoder,
                                    Request.Options options, Retryer retryer, FeignLoggerFactory loggerFactory) {
            Logger logger = loggerFactory.create(this.getClass());//必须构建,否则日志中无法看到重试的次数
    
            this.urlBuilder = Feign.builder()
                    .client(client)
                    .encoder(encoder)
                    .decoder(decoder)
                    .contract(contract)
                    .errorDecoder(errorDecoder)
                    .options(options)
                    .logger(logger)
                    .logLevel(Logger.Level.FULL);
    
            nameBuilder = Feign.builder()
                    .options(options)
                    .retryer(retryer)
                    .logger(logger)
                    .logLevel(Logger.Level.FULL)
                    .client(client)
                    .encoder(encoder)
                    .decoder(decoder)
                    .errorDecoder(errorDecoder)
                    .contract(contract);
    
    //        if (client instanceof LoadBalancerFeignClient) { // 无需均衡负载
    //            client = ((LoadBalancerFeignClient) client).getDelegate();
    //        }
        }
    
    
        @Override
        public <T> T createClientByUrl(Class<T> apiType, String url) {
            return this.urlBuilder.target(apiType, url);
        }
    
        @Override
        public <T> T createClientByName(Class<T> apiType, String name) {
            return nameBuilder.target(apiType, name);
        }
    }
    
    

    使用方式:如果通过服务名去创建客户端,那么需要手动添加http://+服务名的方式

    BusinessFeign feign = bikeFeignService.createClientByName(BusinessFeign.class, "http://bike-business");
    String s = feign.helloBusiness();
    System.out.println("openfeign resp " + s + ".....");
    

五、负载均衡

  • feign与ribbon的爱恨纠葛

    官方时刻:

    1. Feign是一个声明式的web service客户端,它使得编写web service客户端更为容易。创建接口,为接口添加注解,即可使用Feign。Feign可以使用Feign注解或者JAX-RS注解,还支持热插拔的编码器和解码器。Spring Cloud为Feign添加了Spring MVC的注解支持,并整合了Ribbon和Eureka来为使用Feign时提供负载均衡。
    2. Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。简单地说,Ribbon是一个客户端负载均衡器。
    3. 总结就是一个是声明式的客户端,一个是用于负载均衡的工具,而feign客户端集成了ribbon的负载均衡,在2.4以下版本一般是通过ribbon来实现客户端的负载均衡,高版本目前使用的是spring系的spring cloud loadbalance
  • ribbon的主场

    1. ribbon的使用

      • 通过restTemplate实现ribbon的负载均衡

        一般通过@LoadBalanced注解的方式即可实现负载均衡

        @Configuration
        public class RibbonConfig {
        
            @Bean
            @LoadBalanced
            public RestTemplate restTemplate(){
                return new RestTemplate();
            }
        }
        
        

        使用的过程中只需要通过注解注入即可

        @RestController
        @RequestMapping
        @Slf4j
        public class RibbonController {
        
            @Autowired
            RestTemplate restTemplate;
        
            @GetMapping("/ribbon")
            public String loadBalance(){
        				//实例名访问业务接口地址 http://instanceName/interfaceName
                String resp = restTemplate.getForObject("http://bike-business/bike-business/hello", String.class);
                log.info("rest resp : {}",resp);
        
                return resp;
        
            }
        }
        

        我们开启了两个相同实例名称的业务系统,通过postman调用接口获得如下运行结果,可以看出在没有任何额外配置的情况下,大概是轮训的一种负载均衡策略,具体是不是,我们需要进行源码的分析。

        rest resp : hello business port : 8122 .....
        rest resp : hello business port : 8125 .....
        rest resp : hello business port : 8122 .....
        rest resp : hello business port : 8125 .....
        rest resp : hello business port : 8122 .....
        rest resp : hello business port : 8125 .....
        
      • ribbon的源码分析

        LoadBalancerClient是负载均衡很重要的一个类,其继承了ServiceInstanceChooser

        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);
        }
        
        public interface ServiceInstanceChooser {
            ServiceInstance choose(String serviceId);
        
            <T> ServiceInstance choose(String serviceId, Request<T> request);
        }
        
        1. ServiceInstance choose(String serviceId);根据传入的服务名serviceId,从负载均衡器中挑选一个对应服务的实例。
        2. T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request);使用从负载均衡器中挑选出的服务实例来执行请求内容。
        3. URI reconstructURI(ServiceInstance instance, URI original);为系统构建一个合适的host:port形式的URI。在分布式系统中,我们使用逻辑上的服务名称作为host来构建URI(替代服务实例的host:port形式)进行请求,例如实例代码中的请求方式就是通过实例名+接口名的方式进行请求

        如果要自定义负载均衡的配置策略,那么根据springboot的自动装配规则,应该有一个与类向对应的*AutoConfiguration.class,只需要找到LoadBalancerAutoConfiguration自动装配类,查看其默认配置即可

        @Configuration(
            proxyBeanMethods = false
        )
        @ConditionalOnClass({RestTemplate.class})
        @ConditionalOnBean({LoadBalancerClient.class})
        @EnableConfigurationProperties({LoadBalancerRetryProperties.class})
        public class LoadBalancerAutoConfiguration
        

        配置类中两个关键的注解:

        • @ConditionalOnClass({RestTemplate.class}):RestTemplate必须存在于当前工程的环境中。
        • @ConditionalOnBean({LoadBalancerClient.class}):在Spring的Bean工程中必须有LoadBalancerClient的实现bean。

        配置类主要做了三件事:

        1. 创建了一个LoadBalancerInterceptor的Bean,用于实现对客户端发起请求时进行拦截,以实现客户端负载均衡。
        2. 创建了一个RestTemplateCustomizer的Bean,用于给RestTemplate增加LoadbalancerInterceptor
        3. 维护了一个被@LoadBalanced注解修饰的RestTemplate对象列表,并在这里进行初始化,通过调用RestTemplateCustomizer的实例来给需要客户端负载均衡的RestTemplate增加LoadBalancerInterceptor拦截器。

        LoadBalancerClient只是一个接口,只需要找到LoadBalancerClient的ribbon实现类RibbonLoadBalancerClient才能分析具体的实现,其中查看主要方法choose()最终获取实例使用的是ribbon中ILoadBalancer.chooseServer

        public class RibbonLoadBalancerClient implements LoadBalancerClient {
        
            public ServiceInstance choose(String serviceId, Object hint) {
              //获取服务实例
                Server server = this.getServer(this.getLoadBalancer(serviceId), hint);
                return server == null ? null : new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
            }
        		//获取interceptor对RequestTemplate进行请求拦截
            private ServerIntrospector serverIntrospector(String serviceId) {
                ServerIntrospector serverIntrospector = (ServerIntrospector)this.clientFactory.getInstance(serviceId, ServerIntrospector.class);
                if (serverIntrospector == null) {
                    serverIntrospector = new DefaultServerIntrospector();
                }
        
                return (ServerIntrospector)serverIntrospector;
            }
        
        		//获取实例使用的是ribbon中ILoadBalancer.chooseServer
            protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
                return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default");
            }
         
        }
        
        

        ILoadBalancer源码结构,他的实现有多种,从RibbonClientConfiguration配置类,可以知道在整合时默认采用ZoneAvoidanceRule来实现负载均衡器。

        img

        public interface ILoadBalancer {
          	//向负载均衡器中维护的实例列表增加服务实例
            void addServers(List<Server> var1);
        		//通过某种策略,从负载均衡器中挑选出一个具体实例
            Server chooseServer(Object var1);
        		//用来通知和标识负载均衡器中某个实例已经停止服务,不然负载均衡器在下一次获取服务实例清单前都会认为服务实例均是正常服		 //务的
            void markServerDown(Server var1);
        
            /** @deprecated */
            @Deprecated
            List<Server> getServerList(boolean var1);
        		//获取当前正常服务的实例列表
            List<Server> getReachableServers();
        		//获取所有已知的服务实例列表,包括正常服务和停止服务的实例
            List<Server> getAllServers();
        }
        

        以上分析了ribbon的基本脉络:通过LoadBalancerInterceptor拦截器对RequestTemplate的请求进行拦截,并利用Spring Cloud的负载均衡器LoadBalancerClient将以服务名为host的URI转换成具体的服务实例地址的过程。同时通过分析LoadBalancerClient的Ribbon实现RibbonLoadBalancerClient,可以知道在使用Ribbon实现负载均衡器的时候,实际使用的还是Ribbon中定义ILoadBalancer接口的实现,自动化配置会采用ZoneAwareLoadBalancer的实例来实现客户端负载均衡

        最终的负载均衡策略还是在ILoadBalancer的实现类里实现的

      • 负载均衡策略

        RibbonClientConfiguration配置中有几个常用的配置,Iping,Irule,ILoadBalancer

        • Iping:是保证服务可用的基石,总共有五种:
          1. NIWSDiscoveryPing:借助Eureka服务发现机制获取节点状态。节点状态是UP,则认为是可用状态
          2. PingUrl:主动向服务节点发起一次http调用,对方有响应则认为节点是可用状态
          3. DummyPing:默认返回true,即认为所有节点都可用,这也是单独使用Ribbon时的默认模式
          4. NoOpPing:返回true
          5. PingConstant:返回设置的常量值
        • Irule:负责选择什么样的负载均衡算法
          1. RoundRobinRule:轮询(默认)
          2. RandomRule:随机
          3. RetryRule:重试(先按照轮询规则获取服务,如果获取服务失败则在指定时间内进行重试)
          4. AvailabilityFilteringRule:会先过滤掉由于多次访问故障而处于断路器状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问
          5. WeightedResponseTimeRule:根据平均响应时间计算所有服务的权重,响应时间越快的服务权重越大被选中的概率越大。刚启动时如果统计信息不足,则使用RoundRobinRule(轮询)策略,等统计信息足够,会切换到WeightedResponseTimeRule
          6. BestAvailableRule:做为ClientConfigEnabledRoundRobinRule的子类,它可以选择出最空闲的实例,也就是请求数最少的实例进行返回。
          7. ZoneAvoidanceRule:复合判断Server所在区域的性能和Server的可用性选择服务器,在没有Zone的情况下是类似轮询的算法;

        以上的Iping和Irule具体选用哪种,可通过配置类或配置文件的方式选择:

        # boot2。4以上版本默认使用springcloud loadbalance 该配置将不生效
        #目前只提供了RandomLoadBalancer和RoundRobinLoadBalancer两种负载均衡策略,默认是轮询
        [instance_name]:
           ribbon:
             NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
        
      		@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());
          }
      
      		@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 ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
              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));
          }
      
  • openfiegn的负载均衡

    1. spring cloud loadbalance,高版本的默认负载均衡策略

      • FeignBlockingLoadBalancerClient默认的客户端,里面配置了LoadBalancerClient,并通过其中choose方法获取负载均衡的实例对象。

        public class FeignBlockingLoadBalancerClient implements Client {
        
          	//客户端负载均衡管理器
            private final LoadBalancerClient loadBalancerClient;
        
            public Response execute(Request request, Options options) throws IOException {
                URI originalUri = URI.create(request.url());
                String serviceId = originalUri.getHost();
                Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);
                String hint = this.getHint(serviceId);
                DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest(new RequestDataContext(LoadBalancerUtils.buildRequestData(request), hint));
                Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator.getSupportedLifecycleProcessors(this.loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class), RequestDataContext.class, ResponseData.class, ServiceInstance.class);
                supportedLifecycleProcessors.forEach((lifecycle) -> {
                    lifecycle.onStart(lbRequest);
                });
              	//通过choose方法获取均衡策略实例
                ServiceInstance instance = this.loadBalancerClient.choose(serviceId, lbRequest);
                org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(instance);
                String message;
                if (instance == null) {
                    message = "Load balancer does not contain an instance for the service " + serviceId;
                    if (LOG.isWarnEnabled()) {
                        LOG.warn(message);
                    }
        
                    supportedLifecycleProcessors.forEach((lifecycle) -> {
                        lifecycle.onComplete(new CompletionContext(Status.DISCARD, lbRequest, lbResponse));
                    });
                    return Response.builder().request(request).status(HttpStatus.SERVICE_UNAVAILABLE.value()).body(message, StandardCharsets.UTF_8).build();
                } else {
                    message = this.loadBalancerClient.reconstructURI(instance, originalUri).toString();
                    Request newRequest = this.buildRequest(request, message);
                    return LoadBalancerUtils.executeWithLoadBalancerLifecycleProcessing(this.delegate, options, newRequest, lbRequest, lbResponse, supportedLifecycleProcessors);
                }
            }
        
        }
        
        
        
      • BlockingLoadBalancerClient为负载均衡客户端LoadBalancerClient的实现类(ribbon的实现类为 RibbonLoadBalancerClient),通过choose方法我们发现是通过loadBalancerClientFactory去获取的实例,那么需要进一步追踪loadBalancerClientFactory具体配置

        public class BlockingLoadBalancerClient implements LoadBalancerClient {
            private final LoadBalancerClientFactory loadBalancerClientFactory;
        
            public BlockingLoadBalancerClient(LoadBalancerClientFactory loadBalancerClientFactory) {
                this.loadBalancerClientFactory = loadBalancerClientFactory;
            }
        		//执行的choose方法,我们发现是通过loadBalancerClientFactory去获取的实例
            public ServiceInstance choose(String serviceId) {
                ReactiveLoadBalancer<ServiceInstance> loadBalancer = this.loadBalancerClientFactory.getInstance(serviceId);
                if (loadBalancer == null) {
                    return null;
                } else {
                    Response<ServiceInstance> loadBalancerResponse = (Response)Mono.from(loadBalancer.choose()).block();
                    return loadBalancerResponse == null ? null : (ServiceInstance)loadBalancerResponse.getServer();
                }
            }
        }
        
      • LoadBalancerClientFactory是通过加载LoadBalancerClientConfiguration里面的配置,我们只需要查看其中的默认配置,便能了解到spring cloud loadbalance默认均衡策略了

        public class LoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification> implements Factory<ServiceInstance> {
            public static final String NAMESPACE = "loadbalancer";
            public static final String PROPERTY_NAME = "loadbalancer.client.name";
        
            public LoadBalancerClientFactory() {
                super(LoadBalancerClientConfiguration.class, "loadbalancer", "loadbalancer.client.name");
            }
        
            public String getName(Environment environment) {
                return environment.getProperty("loadbalancer.client.name");
            }
        
            public ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) {
                return (ReactiveLoadBalancer)this.getInstance(serviceId, ReactorServiceInstanceLoadBalancer.class);
            }
        }
        
      • LoadBalancerClientConfiguration我们发现了配置了RoundRobinLoadBalancer也就是轮训的策略,通过发现ReactorLoadBalancer实现类只有RandomLoadBalancer(随机)、RoundRobinLoadBalancer(轮训)目前两种策略,如果我们要自定义负载均衡策略,只需要重写该配置即可

        @Configuration(
            proxyBeanMethods = false
        )
        @ConditionalOnDiscoveryEnabled
        public class LoadBalancerClientConfiguration {
            private static final int REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER = 193827465;
        
            public LoadBalancerClientConfiguration() {
            }
        
          	//默认的负载均衡策略
            @Bean
            @ConditionalOnMissingBean
            public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
                String name = environment.getProperty("loadbalancer.client.name");
                return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
            }
        
        }
        
        
        
      • 自定义负载均衡策略

         
        
        /**
         * @author :Nickels
         * @date :2021/5/20
         * @desc :
         */
        @Configuration(
                proxyBeanMethods = false
        )
        //@ConditionalOnDiscoveryEnabled
        public class CustomerLoadBalancerClientConfiguration {
        
            /**
             * 需要自定义均衡策略的实例名称,可在配置文件中配置
             */
            @Value("${loadbalance.instanceName}")
            private String instanceName;
        
            /**
             * 自定义负载均衡策略,可参考LoadBalancerClientConfiguration默认配置的是RoundRobinLoadBalancer
             * @param loadBalancerClientFactory
             * @return
             */
            @Bean
            @ConditionalOnMissingBean
            public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(LoadBalancerClientFactory loadBalancerClientFactory) {
        //        String name = environment.getProperty("loadbalancer.client.name");
                return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(instanceName, ServiceInstanceListSupplier.class), instanceName);
            }
        
        }
        
        
        

        源码地址:spring-cloud