• spring cloud Ribbon的使用和实现原理


    转载链接:https://blog.csdn.net/qq_20597727/article/details/82860521

    简介

    这篇文章主要介绍一下ribbon在程序中的基本使用,在这里是单独拿出来写用例测试的,实际生产一般是配置feign一起使用,更加方便开发。同时这里也通过源码来简单分析一下ribbon的基本实现原理。

    基本使用

    这里使用基于zookeeper注册中心+ribbon的方式实现一个简单的客户端负载均衡案例。

    服务提供方

    首先是一个服务提供方。代码如下。

    application.properties配置文件

    spring.application.name=discovery-service
    server.port=0
    service-B.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

    bootstrap.properties配置文件

    spring.cloud.zookeeper.connect-string=192.168.0.15:2181

    引导程序,提供了一个ribbonService的rest接口服务,注册程序到zookeeper中。

     1 @SpringBootApplication
     2 @EnableDiscoveryClient
     3 @RestController
     4 public class DiscoverClient {
     5     public static void main(String[] args) {
     6         SpringApplication.run(DiscoverClient.class, args);
     7     }
     8  9     @RequestMapping("/ribbonService")
    10     public String ribbonService(){
    11         return "hello too ribbon";
    12     }
    13 }

    服务调用方

    服务调用方就是进行负载均衡的一方,利用ribbo的RestTemplate进行负载调用服务。

    RibbonConfig,配置ribbon的RestTemplate,通过@LoadBalanced注解实现,具体原理稍后分析。

     1 @Configuration
     2 public class RibbonConfig {
     3  4     /**
     5      * 实例化ribbon使用的RestTemplate
     6      * @return
     7      */
     8     @Bean
     9     @LoadBalanced
    10     public RestTemplate rebbionRestTemplate(){
    11         return new RestTemplate();
    12     }
    13     
    14     /**
    15     * 配置随机负载策略,需要配置属性service-B.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
    16     */
    17     @Bean
    18     public IRule ribbonRule() {
    19         return new RandomRule();
    20     }
    21 }
    22

    引导程序

     1 @SpringBootApplication(scanBasePackages = "garine.learn.ribbon.loadblance")
     2 @EnableDiscoveryClient
     3 @RestController
     4 public class TestRibbonApplocation {
     5     public static void main(String[] args) {
     6         SpringApplication.run(TestRibbonApplocation.class, args);
     7     }
     8  9     @Autowired
    10     @LoadBalanced
    11     RestTemplate restTemplate;
    12 13     @GetMapping("/{applicationName}/ribbonService")
    14     public String ribbonService(@PathVariable("applicationName") String applicationName){
    15         return restTemplate.getForObject("http://" + applicationName+"/ribbonService", String.class);
    16     }
    17 }

    配置文件同上,服务名称修改即可。

    测试

    1. 启动两个discovery-service,由于端口设置为0,所以是随机端口。

    2. 启动服务调用方

    3. 浏览器访问服务调用方的提供的接口,路径参数需要加上调用的服务名称,例如http://localhost:8080/discovery-service/ribbonService,然后服务调用方使用ribbon的RestTemplate调用服务提供方的接口。

    4. 结果返回:hello too ribbon ,同时服务提供方启动的两个服务都可能被调用,取决于怎么配置负载策略。

    上面就是一个简单使用ribbon的例子,结合feign使用基本上是做类似上面所写的工作,那么ribbon到底是怎么实现的呢?

    原理与源码分析

    ribbon实现的关键点是为ribbon定制的RestTemplate,ribbon利用了RestTemplate的拦截器机制,在拦截器中实现ribbon的负载均衡。负载均衡的基本实现就是利用applicationName从服务注册中心获取可用的服务地址列表,然后通过一定算法负载,决定使用哪一个服务地址来进行http调用。

    Ribbon的RestTemplate

    RestTemplate中有一个属性是List<ClientHttpRequestInterceptor> interceptors,如果interceptors里面的拦截器数据不为空,在RestTemplate进行http请求时,这个请求就会被拦截器拦截进行,拦截器实现接口ClientHttpRequestInterceptor,需要实现方法是

    ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
          throws IOException;

    也就是说拦截器需要完成http请求,并封装一个标准的response返回。

    ribbon中的拦截器

    在Ribbon 中也定义了这样的一个拦截器,并且注入到RestTemplate中,是怎么实现的呢?

    在Ribbon实现中,定义了一个LoadBalancerInterceptor,具体的逻辑先不说,ribbon就是通过这个拦截器进行拦截请求,然后实现负载均衡调用。

    拦截器定义在org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration.LoadBalancerInterceptorConfig#ribbonInterceptor

     1 @Configuration
     2 @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
     3 static class LoadBalancerInterceptorConfig {
     4    @Bean
     5     //定义ribbon的拦截器
     6    public LoadBalancerInterceptor ribbonInterceptor(
     7          LoadBalancerClient loadBalancerClient,
     8          LoadBalancerRequestFactory requestFactory) {
     9       return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
    10    }
    11 12    @Bean
    13    @ConditionalOnMissingBean
    14     //定义注入器,用来将拦截器注入到RestTemplate中,跟上面配套使用
    15    public RestTemplateCustomizer restTemplateCustomizer(
    16          final LoadBalancerInterceptor loadBalancerInterceptor) {
    17       return restTemplate -> {
    18                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
    19                        restTemplate.getInterceptors());
    20                list.add(loadBalancerInterceptor);
    21                restTemplate.setInterceptors(list);
    22            };
    23    }
    24 }

    ribbon中的拦截器注入到RestTemplate

    定义了拦截器,自然需要把拦截器注入到、RestTemplate才能生效,那么ribbon中是如何实现的?上面说了拦截器的定义与拦截器注入器的定义,那么肯定会有个地方使用注入器来注入拦截器的。

    在org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration#loadBalancedRestTemplateInitializerDeprecated方法里面,进行注入,代码如下。

     1 @Configuration
     2 @ConditionalOnClass(RestTemplate.class)
     3 @ConditionalOnBean(LoadBalancerClient.class)
     4 @EnableConfigurationProperties(LoadBalancerRetryProperties.class)
     5 public class LoadBalancerAutoConfiguration {
     6  7    @LoadBalanced
     8    @Autowired(required = false)
     9    private List<RestTemplate> restTemplates = Collections.emptyList();
    10 11    @Bean
    12    public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
    13          final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
    14        //遍历context中的注入器,调用注入方法。
    15       return () -> restTemplateCustomizers.ifAvailable(customizers -> {
    16             for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
    17                 for (RestTemplateCustomizer customizer : customizers) {
    18                     customizer.customize(restTemplate);
    19                 }
    20             }
    21         });
    22    }
    23    //......
    24    }

    遍历context中的注入器,调用注入方法,为目标RestTemplate注入拦截器,注入器和拦截器都是我们定义好的。

    还有关键的一点是:需要注入拦截器的目标restTemplates到底是哪一些?因为RestTemplate实例在context中可能存在多个,不可能所有的都注入拦截器,这里就是@LoadBalanced注解发挥作用的时候了。

    LoadBalanced注解

    严格上来说,这个注解是spring cloud实现的,不是ribbon中的,它的作用是在依赖注入时,只注入实例化时被@LoadBalanced修饰的实例。

    例如我们定义Ribbon的RestTemplate的时候是这样的

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

    因此才能为我们定义的RestTemplate注入拦截器。

    那么@LoadBalanced是如何实现这个功能的呢?其实都是spring的原生操作,@LoadBalance的源码如下

     1 /**
     2  * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
     3  * @author Spencer Gibb
     4  */
     5 @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
     6 @Retention(RetentionPolicy.RUNTIME)
     7 @Documented
     8 @Inherited
     9 @Qualifier
    10 public @interface LoadBalanced {
    11 }

    很明显,‘继承’了注解@Qualifier,我们都知道以前在xml定义bean的时候,就是用Qualifier来指定想要依赖某些特征的实例,这里的注解就是类似的实现,restTemplates通过@Autowired注入,同时被@LoadBalanced修饰,所以只会注入@LoadBalanced修饰的RestTemplate,也就是我们的目标RestTemplate。

    拦截器逻辑实现

    LoadBalancerInterceptor源码如下。

     1 public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
     2  3     private LoadBalancerClient loadBalancer;
     4     private LoadBalancerRequestFactory requestFactory;
     5  6     public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
     7         this.loadBalancer = loadBalancer;
     8         this.requestFactory = requestFactory;
     9     }
    10 11     public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
    12         // for backwards compatibility
    13         this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
    14     }
    15 16     @Override
    17     public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
    18             final ClientHttpRequestExecution execution) throws IOException {
    19         final URI originalUri = request.getURI();
    20         String serviceName = originalUri.getHost();
    21         Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
    22         return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
    23     }
    24 }

    拦截请求执行

     1 @Override
     2 public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
     3    ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
     4     //在这里负载均衡选择服务
     5    Server server = getServer(loadBalancer);
     6    if (server == null) {
     7       throw new IllegalStateException("No instances available for " + serviceId);
     8    }
     9    RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
    10          serviceId), serverIntrospector(serviceId).getMetadata(server));
    11 //执行请求逻辑
    12    return execute(serviceId, ribbonServer, request);
    13 }

    我们重点看getServer方法,看看是如何选择服务的

    1 protected Server getServer(ILoadBalancer loadBalancer) {
    2    if (loadBalancer == null) {
    3       return null;
    4    }
    5     //
    6    return loadBalancer.chooseServer("default"); // TODO: better handling of key
    7 }

    代码配置随机loadBlancer,进入下面代码

     1 public Server chooseServer(Object key) {
     2     if (counter == null) {
     3         counter = createCounter();
     4     }
     5     counter.increment();
     6     if (rule == null) {
     7         return null;
     8     } else {
     9         try {
    10             //使用配置对应负载规则选择服务
    11             return rule.choose(key);
    12         } catch (Exception e) {
    13             logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
    14             return null;
    15         }
    16     }
    17 }

    这里配置的是RandomRule,所以进入RandomRule代码

     1 public Server choose(ILoadBalancer lb, Object key) {
     2     if (lb == null) {
     3         return null;
     4     }
     5     Server server = null;
     6  7     while (server == null) {
     8         if (Thread.interrupted()) {
     9             return null;
    10         }
    11         //获取可用服务列表
    12         List<Server> upList = lb.getReachableServers();
    13         List<Server> allList = lb.getAllServers();
    14 15         //随机一个数
    16         int serverCount = allList.size();
    17         if (serverCount == 0) {
    18             /*
    19              * No servers. End regardless of pass, because subsequent passes
    20              * only get more restrictive.
    21              */
    22             return null;
    23         }
    24 25         int index = rand.nextInt(serverCount);
    26         server = upList.get(index);
    27 28         if (server == null) {
    29             /*
    30              * The only time this should happen is if the server list were
    31              * somehow trimmed. This is a transient condition. Retry after
    32              * yielding.
    33              */
    34             Thread.yield();
    35             continue;
    36         }
    37 38         if (server.isAlive()) {
    39             return (server);
    40         }
    41 42         // Shouldn't actually happen.. but must be transient or a bug.
    43         server = null;
    44         Thread.yield();
    45     }
    46 47     return server;
    48 49 }

    随机负载规则很简单,随机整数选择服务,最终达到随机负载均衡。我们可以配置不同的Rule来实现不同的负载方式。

  • 相关阅读:
    [转]十步完全理解SQL
    [转]Java日期时间使用总结
    [转]Mybatis出现:无效的列类型: 1111 错误
    [转]java.lang.OutOfMemoryError: PermGen space及其解决方法
    [转]Spring3核心技术之事务管理机制
    [转]Spring的事务管理难点剖析(1):DAO和事务管理的牵绊
    设计模式之装饰模式
    进程通信
    设计模式之备忘录模式
    设计模式之单例模式
  • 原文地址:https://www.cnblogs.com/dalianpai/p/11690322.html
Copyright © 2020-2023  润新知