zoukankan      html  css  js  c++  java
  • 三、Spring Cloud Netflix Ribbon 客户端的负载均衡

    本章简单使用Spring Cloud Netflix Ribbon进行客户端的负载均衡,并分析其原理实现。
    官网地址 spring-cloud-ribbon

    一、 RESTful HTTP协议通信

    REST,全称是Resource Representational State Transfer,即:资源在网络中以某种形式进行状态转移。规范了HTTP通信协议的标准:

    • HTTP METHOD 约束资源操作类型 GET/POST/PUT/DELETE
    Verd 描述
    GET(SELECT) 查询
    POST(CREATE) 提交
    PUT(UPDATE) 修改,客户端需要提供新建资源的所有属性
    DELETE(DELETE) 删除
    • REST是面向资源的:

    以订单接口为例
    根据id获取订单信息:对应类型 GET METHOD

    /order(GET)  /order/${id}
    

    保存订单: 对应类型 POST METHOD

    /order(POST) 
    

    修改订单:对应类型 PUT METHOD

    /order(PUT) 
    

    删除订单:对应类型 DELETE METHOD

    /order(DELETE)  /order/${id}
    

    查询订单列表: 使用

    /orders
    
    • 名词

    以根据id查询订单明细为例,普通接口命名可能是queryOrderById,而RESTful接口则是(GET) /order/${id}

    • HTTP返回码
    状态码 描述
    2XX 请求正常处理并返回
    3XX 重定向,请求的资源位置发生变化
    4XX 客户端发送的请求有误
    5XX 服务器端的错误

    这里有一篇RESTful详细的介绍,转载下 老_张 RESTful API浅谈

    二、RestTemplate

    当服务由单体架构一步步演变为集群->垂直拆分->SOA->微服务后,不可避免的涉及到服务间的相互通信。
    此时我们可以使用 HttpClient 、 RestTemplate 、 OkHttp 、JDK HttpUrlConnection这几种方式来达到通信数据交互的目的。
    以电商的用户、订单服务为例,当用户服务内需要根据用户找到所有订单时,则用户服务会与订单服务建立HTTP通信:

    下面基于SpringBoot简单演示接口实现:

    1. OrderService

    提供订单查询接口http://localhost:8081/orders供UserService调用:

    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class OrderController {
    
        @GetMapping("/orders")
        public String getAllOrder() {
    
            return "Shen all orders";
        }
    
    }
    

    2. UserService

    UserServer使用RestTemplate进行服务调用

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.web.client.RestTemplateBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    @RestController
    public class UserController {
    
        @Autowired
        RestTemplate restTemplate;
    
    //    @Bean
    //    public RestTemplate restTemplate() {
    //        return new RestTemplate();
    //    }
    
        @Bean
        public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
            return restTemplateBuilder.build();
        }
    
        @GetMapping("/user/{id}")
        public String findById(@PathVariable("id") int id) {
    
            // 调用远程订单服务
            // HttpClient 、 RestTemplate 、 OkHttp 、JDK HttpUrlConnection
            String rst = restTemplate.getForObject("http://localhost:8081/orders", String.class);
            return rst;
        }
    
    }
    
    

    三、Spring Cloud Netflix Ribbon 集群负载均衡调用

    上例子是以单服务节点的调用为例,真实场景中为了避免单点故障、提升并发效率,都会部署多台服务,此时就涉及客户端在同时调用多台服务时的负载均衡问题。

    此时服务提供者OrderService的地址,可以先维护在客户端UserService的配置文件内,先看一下以Ribbon方法来实现客户端的负载均衡。

    1. 声明LoadBalancerClient实现负载均衡

    OrderService

    服务提供方,订单服务,此时将服务部署在8081和8082两个不同端口,Intellij IDEA中支持同一个SpringBoot项目同时配置多个端口:

    UserService

    服务调用方、客户端 用户服务:

    • 首先引入 netflix ribbot依赖
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
                <version>2.2.3.RELEASE</version>
            </dependency>
    
    • 之后,application.properties配置文件内根据Ribbon规则配置服务地址
    # 配置服务提供者的地址
    spring-cloud-order-service.ribbon-listOfServers=
      localhost:8081,localhost:8082 
    
    • 最后,客户端实现负载均衡改造
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.web.client.RestTemplateBuilder;
    import org.springframework.cloud.client.ServiceInstance;
    import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    @RestController
    public class UserController {
    
        @Autowired
        RestTemplate restTemplate;
    
        @Autowired
        LoadBalancerClient loadBalancerClient;
    
        @Bean
        public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
            return restTemplateBuilder.build();
        }
    
        @GetMapping("/user/{id}")
        public String findById(@PathVariable("id") int id) {
    
            // 调用远程订单服务
            // HttpClient 、 RestTemplate 、 OkHttp 、JDK HttpUrlConnection
    //        String rst = restTemplate.getForObject("http://localhost:8081/orders", String.class);
    
            ServiceInstance serviceInstance = loadBalancerClient.choose("spring-cloud-order-service");
            String url = String.format("http://%s:%s" + "/orders", serviceInstance.getHost(), serviceInstance.getPort());
            System.out.println(url);
            
            String rst = restTemplate.getForObject(url, String.class);
            return rst;
        }
    
    }
    
    

    当我们多次调用客户端/user/{id}服务后,控制台输出如下,可见用户服务在调用集群订单服务时,已经实现了负载均衡,其实内部算法是默认的轮询机制。

    http://localhost:8081/orders
    http://localhost:8082/orders
    http://localhost:8081/orders
    http://localhost:8082/orders
    http://localhost:8081/orders
    http://localhost:8082/orders
    http://localhost:8081/orders
    http://localhost:8082/orders
    http://localhost:8081/orders
    http://localhost:8082/orders
    http://localhost:8081/orders
    http://localhost:8082/orders
    http://localhost:8081/orders
    http://localhost:8082/orders
    

    2. 通过注解@LoadBalanced注解实现自动负载均衡

    除了上面直接声明 @Autowired LoadBalancerClient loadBalancerClient;负载均衡客户端外,也可以通过@LoadBalanced注解来声明一个RestTemplate来达到负载均衡的目的

    UserService

    其余配置不变,将声明RestTemplate的Bean加上@LoadBalanced注解,服务调用地址也改为http://spring-cloud-order-service,与配置文件内的key相对应。

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    @RestController
    public class UserController2 {
    
        @Autowired
        RestTemplate restTemplate;
    
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    
        @GetMapping("/user2/{id}")
        public String getBuId(@PathVariable("id") int id) {
    
            String rst = restTemplate.getForObject("http://spring-cloud-order-service" + "/orders", String.class);
            return rst;
        }
    
    }
    

    OrderService

    订单服务其实可以不变,不过为了方便看到客户端负载均衡调用的结果,可以在订单服务内输出服务端口,来验证:

    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class OrderController {
    
        @Value("${server.port}")
        private int port;
    
        @GetMapping("/orders")
        public String getAllOrder() {
            System.out.println(port);
            return "Shen all orders";
        }
    
    }
    
    

    这样就更方便的实现了负载均衡的调用。

    本地案例demo地址:Spring-Cloud-Netflix-Ribbon Demo

    四、Ribbon 源码分析

    Ribbon其实是基于客户端的负载均衡的一个组件,从上面【二、1】例子的使用其实可以推测,Ribbon实现负载均衡很可能做了两个事情:

    • 解析配置拿到服务方地址列表
    • 基于负载均衡算法实现请求的分发

    从【二、2】的例子,为RestTemplate添加了@LoadBalanced注解可以自动负载均衡,可以尝试推测:

    • 上面两个步骤需要做
    • 需要在某个地方扫描加了@LoadBalanced注解的RestTemplate,并对RestTemplate做改造,来实现地址的动态解析

    A、对RestTemplate添加@LoadBalanced注解进行修饰

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

    我们只是为 RestTemplate 添加了 LoadBalanced注解,为什么可以实现负载均衡,肯定是某些地方会扫描 加了@LoadBalanced注解的 RestTemplate,并做相应改造

    B、LoadBalancerAutoConfiguration获取到添加了@LoadBalanced的RestTemplate,进行包装

    • 我们可以先看下@LoadBalanced源码,可以看到除了配置了一些支持的特性外,本质是一个 @Qualifier,相当于是一个继承了@Qualifier@LoadBalanced标记。
    @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Qualifier
    public @interface LoadBalanced {
    
    }
    
    • 而直接使用@LoadBalanced注解,可以看做是使用了Spring的Bean的自动装配的特性,那么应该是在spring.factories内配置了对应声明Bean的Configuration,

    果然,我们可以在jar包内的META-INF/spring.factories内找到维护了EnableAutoConfigurationLoadBalancerAutoConfiguration配置:

    1. @LoadBalanced注解是在spring-cloud-commons-2.2.3.RELEASE.jar内的
    2. 在该jar包的META-INF的spring.factories内可以找到对应Configuration: LoadBalancerAutoConfiguration
    • 现在我们就可以定位,LoadBalancerAutoConfiguration 内是如何对RestTemplate进行改造,来达到加了LoadBalanced注解就可以自动负载均衡
    
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(RestTemplate.class)  // 自动装配条件 onClass
    @ConditionalOnBean(LoadBalancerClient.class)   // 自动装配条件  onBean
    @EnableConfigurationProperties(LoadBalancerRetryProperties.class)
    public class LoadBalancerAutoConfiguration {
    
            // Spring 的依赖注入
            // 拿到加了@LoadBalanced标记的RestTemplate放入list集合中去,也就是我们之前在UserController2 里声明的`@LoadBalanced RestTemplate`
    	@LoadBalanced
    	@Autowired(required = false)
    	private List<RestTemplate> restTemplates = Collections.emptyList();
    
    	@Autowired(required = false)
    	private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
    
    	@Bean
    	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
    			final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
    		return () -> restTemplateCustomizers.ifAvailable(customizers -> {
    			for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
    				for (RestTemplateCustomizer customizer : customizers) {
    					customizer.customize(restTemplate);
    				}
    			}
    		});
    	}
    
    

    C、LoadBalancerAutoConfiguration为这些RestTemplate添加拦截器

    在Configuration类做了一系列的Bean的初始化,且Bean的初始化其实是有先后顺序的依赖关系的:

    • LoadBalancerAutoConfiguration 源码

    LoadBalancerAutoConfiguration 类里声明了一系列Bean,并且Bean的初始化由先后依赖关系

    
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(RestTemplate.class)
    @ConditionalOnBean(LoadBalancerClient.class)
    @EnableConfigurationProperties(LoadBalancerRetryProperties.class)
    public class LoadBalancerAutoConfiguration {
    
    	@LoadBalanced
    	@Autowired(required = false)
    	private List<RestTemplate> restTemplates = Collections.emptyList();  // 获取到所有加了 @LoadBalanced 声明的 RestTemplate 集合
    
    	@Autowired(required = false)
    	private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
    
            // `restTemplates`集合是在这里使用的,所有可以以这里的@Bean,Bean的初始化为出发点
            // 因为是@Bean进行方法参数的依赖注入,存在Bean的初始化的依赖关系;大致有 C01 -> C02 -> C03 -> C04 -> C05 几个步骤的Bean初始化
    	@Bean
    	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
    			final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {   // C01. 初始化SmartInitializingSingleton Bean,依赖注入 RestTemplateCustomizer -> C02
    		return () -> restTemplateCustomizers.ifAvailable(customizers -> {
    			for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {  // C06. 获取到所有restTemplates集合,
    				for (RestTemplateCustomizer customizer : customizers) { 
    					customizer.customize(restTemplate);   // C07. 对这些RestTemplate进行包装,其实调用的是 RestTemplateCustomizer.customize 至C08  
    				}
    			}
    		});
    	}
    
    	@Bean
    	@ConditionalOnMissingBean
    	public LoadBalancerRequestFactory loadBalancerRequestFactory(  // C05.初始化 LoadBalancerRequestFactory Bean,也依赖于 LoadBalancerClient Bean的初始化 -> C04
    			LoadBalancerClient loadBalancerClient) {
    		return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
    	}
    
    	@Configuration(proxyBeanMethods = false)
    	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
    	static class LoadBalancerInterceptorConfig {
    
    		@Bean
    		public LoadBalancerInterceptor ribbonInterceptor(
    				LoadBalancerClient loadBalancerClient,
    				LoadBalancerRequestFactory requestFactory) {   //  C03. 初始化LoadBalancerInterceptor,依赖于 LoadBalancerClient -> C04   和   LoadBalancerRequestFactory -> C05
    			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
    		}
    
    		@Bean
    		@ConditionalOnMissingBean
    		public RestTemplateCustomizer restTemplateCustomizer(
    				final LoadBalancerInterceptor loadBalancerInterceptor) {  // C02.  初始化RestTemplateCustomizer Bean,依赖于 LoadBalancerInterceptor -> C03
    			return restTemplate -> {
    				List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());   // C08. 取得默认的拦截器列表
    				list.add(loadBalancerInterceptor);  // C09. 添加 loadBalancerInterceptor 拦截器,类型为 LoadBalancerInterceptor 
    				restTemplate.setInterceptors(list);  // C10. 为restTemplate添加 LoadBalancerInterceptor 拦截器
    			};
    		}
    
    	}
    
    
    }
    
    
    • RibbonAutoConfiguration 源码,

    对应 C04. 初始化 LoadBalancerClient Bean
    前面C01-C03几步可以看到,在对RestTemplate进行封装时,都需要 LoadBalancerClient Bean的实例,那么这个Bean是在什么时候进行初始化的呢?
    因为我们使用的是Ribbon,所以推测 LoadBalancerClient 的自动装配是在 Ribbon 的 jar包内实现的。
    定位到 META-INF/spring.factories文件的AutoConfiguration配置

    @Configuration
    @Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
    @RibbonClients
    @AutoConfigureAfter(
    		name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
    @AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
    		AsyncLoadBalancerAutoConfiguration.class })  // AutoConfigureBefore,定义了RibbonAutoConfiguration 的初始化是在LoadBalancerAutoConfiguration之前,也和我们上面几步分析的Bean初始化顺序一致
    @EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
    		ServerIntrospectorProperties.class })
    public class RibbonAutoConfiguration {
    
    	@Autowired(required = false)
    	private List<RibbonClientSpecification> configurations = new ArrayList<>();
    
    	@Autowired
    	private RibbonEagerLoadProperties ribbonEagerLoadProperties;
    
    
            // LoadBalancerClient Bean的初始化,使用的是 RibbonLoadBalancerClient
    	@Bean
    	@ConditionalOnMissingBean(LoadBalancerClient.class)
    	public LoadBalancerClient loadBalancerClient() {   // C04. 初始化 LoadBalancerClient Bean
    		return new RibbonLoadBalancerClient(springClientFactory());  // 其实就是自动装配了 RibbonLoadBalancerClient 
    	}
    
    

    至此,我们已经看到了一条Bean初始化的先后顺序:

    首先:在 spring-cloud-netflix-ribbon-2.2.3.RELEASE.jar 内 根据Bena的自动装配规则初始化: LoadBalancerClient
    RibbonAutoConfiguration : [ LoadBalancerClient(RibbonLoadBalancerClient) ]
    
    -> 
    
    之后: 在 spring-cloud-commons-2.2.3.RELEASE.jar 内,通过一系列Bean的先后初始化达到对RestTemplate进行包装的目的
    LoadBalancerAutoConfiguration :[ LoadBalancerRequestFactory -> LoadBalancerInterceptor -> RestTemplateCustomizer -> SmartInitializingSingleton ]
     也正是这条链路来实现了对RestTemplate的重新包装
    
    

    而这些Bean的初始化,其实是为了对加了@LoadBalanced注解的RestTemplate进行包装,包装内部逻辑是为RestTemplate添加拦截器LoadBalancerInterceptor restTemplate.setInterceptors(list);

    D、LoadBalancerInterceptor对请求进行拦截

    因为之前是为 RestTemplate 添加了 LoadBalancerInterceptor 拦截器,所以当我们进行restTemplate.getForObject("http://spring-cloud-order-service" + "/orders", String.class);调用时,一定会进入拦截器LoadBalancerInterceptor#intercept内:

    • LoadBalancerInterceptor#intercept 对 请求进行拦截

    LoadBalancerInterceptor#intercept 拦截器 intercept() 方法对请求 request 进行拦截

    public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
    
    	private LoadBalancerClient loadBalancer;  // D01. 此时的LoadBalancerClient,其实就是我们之前分析过得,在 RibbonAutoConfiguration 内声明的 RibbonLoadBalancerClient
    
    	@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,this.requestFactory.createRequest(request, body, execution));  //   D02. 取得负载均衡器,执行相应逻辑
    	}
    
    }
    
    
    • 负载均衡器来获取其中一个srever
    org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#execute(java.lang.String, org.springframework.cloud.client.loadbalancer.LoadBalancerRequest<T>)
    	@Override
    	public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
    			throws IOException {
    		return execute(serviceId, request, null);  // D03. execute 
    	}
    
    org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#execute(java.lang.String, org.springframework.cloud.client.loadbalancer.LoadBalancerRequest<T>, java.lang.Object)
    	public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
    			throws IOException {
    		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);  // D04. 获得负载均衡器
    		Server server = getServer(loadBalancer, hint);  // D05. 根据负载均衡器获得一个 server,就是我们维护在配置文件中的服务地址之一
    		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);
    	}
    
    • 获取server逻辑

    源码过多,在此列出方法流转的流程,主要逻辑是
    先解析到配置文件内的地址列表,然后根据负载均衡算法得到一个服务地址

    
    org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#getServer(com.netflix.loadbalancer.ILoadBalancer, java.lang.Object)
      com.netflix.loadbalancer.ZoneAwareLoadBalancer#chooseServer
        com.netflix.loadbalancer.BaseLoadBalancer#chooseServer
          com.netflix.loadbalancer.PredicateBasedRule#choose
    	 com.netflix.loadbalancer.AbstractServerPredicate#chooseRoundRobinAfterFiltering(java.util.List<com.netflix.loadbalancer.Server>, java.lang.Object)
    	   com.netflix.loadbalancer.AbstractServerPredicate#incrementAndGetModulo
    			
    

    E、负载均衡算法

    所支持的负载均衡算法可以从com.netflix.loadbalancer.IRule类图中看出:

    RoundRobinRule(轮询算法)
    RandomRule(随机算法)
    AvailabilityFilteringRule():会先过滤由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问
    WeightedResponseTimeRule():根据平均响应的时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高,刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够会切换到WeightedResponseTimeRule
    RetryRule():先按照RoundRobinRule的策略获取服务,如果获取失败则在制定时间内进行重试,获取可用的服务。
    BestAviableRule():会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
    ZoneAvoidanceRule():默认规则,符合判断server所在区域的性能和server的可用性选择服务器

    其中Ribbon默认的负载均衡算法为RoundRobinRule轮询算法,实现也比较简单

    com.netflix.loadbalancer.RoundRobinRule#choose(com.netflix.loadbalancer.ILoadBalancer, java.lang.Object)
    
        public Server choose(ILoadBalancer lb, Object key) {
            if (lb == null) {
                log.warn("no load balancer");
                return null;
            }
    
            Server server = null;
            int count = 0;
            while (server == null && count++ < 10) {
                List<Server> reachableServers = lb.getReachableServers();
                List<Server> allServers = lb.getAllServers();   // 1. 获取到所有服务地址
                int upCount = reachableServers.size();
                int serverCount = allServers.size();  // 2. 服务地址数量
    
                if ((upCount == 0) || (serverCount == 0)) {
                    log.warn("No up servers available from load balancer: " + lb);
                    return null;
                }
    
                int nextServerIndex = incrementAndGetModulo(serverCount);  // 3. 递增拿到当前轮训数量
                server = allServers.get(nextServerIndex);  // 8. 根据下标从服务列表List中获取到对应地址
    
                if (server == null) {
                    /* Transient. */
                    Thread.yield();
                    continue;
                }
    
                if (server.isAlive() && (server.isReadyToServe())) {
                    return (server);  // 9. 返回服务地址
                }
    
                // Next.
                server = null;
            }
    
            if (count >= 10) {
                log.warn("No available alive servers after 10 tries from load balancer: "
                        + lb);
            }
            return server;
        }
    
        private AtomicInteger nextServerCyclicCounter;
        private int incrementAndGetModulo(int modulo) {
            for (;;) {
                int current = nextServerCyclicCounter.get();   // 4. 获取当前游标
                int next = (current + 1) % modulo;  // 5. +1,和服务总数量取模
                if (nextServerCyclicCounter.compareAndSet(current, next))   // 6. cas保证线程安全
                    return next;  // 7. 返回递增后的游标
            }
        }
    

    总结:

    使用Ribbon的两种方式:

    • 为RestTemplate添加@LoadBalanced
    • 直接使用@Autowired LoadBalancerClient loadBalancerClient;客户端

    Ribbon原理的主要几种类型:

    • ILoadBalancer 负载均衡器
    • IRule 负载均衡算法规则(权重机制(区间算法))
    • IPing 存活状态监测 定时任务在不断地发起请求,ping,每10s去访问一次目标服务的地址,如果不可用则剔除无效服务
    • ServerList 定时任务每30s执行一次更新服务列表
    • 自定义负载均衡算法、自定义Ping

    官网介绍可从:https://spring.io/ -> 头部导航 Project/SpringCloud -> 左侧导航 Spring Cloud Netflix -> Learn/Reference Doc. -> 左侧导航 7. Client Side Load Balancer: Ribbon 内看到
    本地案例demo地址:Spring-Cloud-Netflix-Ribbon Demo

  • 相关阅读:
    NET导入Excel带进度条。
    直接拿来用,最火的.NET开源项目(beta)
    Darren Ji
    接口和抽象类有什么区别
    wpf博客
    jQuery动态改变图片显示大小(修改版)
    S32K的make过程
    TortoiseGit安装及使用
    Python:tkinter
    GCC学习笔记(二):编译选项
  • 原文地址:https://www.cnblogs.com/Qkxh320/p/SpringCloud_netflix_ribbon.html
Copyright © 2011-2022 走看看