• 基本使用

    订单服务集成Ribbon

    配置过程 分两步

    订单服务调用商品服务

    Ribbon负载均衡原理 [了解]

    获取@LoadBalanced注解标记的RestTemplate。

    Ribbon将所有标记@LoadBalanced注解的RestTemplate保存到一个List集合当中,具体源码如下:

    @LoadBalanced
    @Autowired(required = false)
    private List<RestTemplate> restTemplates = Collections.emptyList();

    具体源码位置是在LoadBalancerAutoConfiguration中。

    RestTemplate添加一个拦截器

    RestTemplate添加拦截器需要有两个步骤,首先是定义一个拦截器,其次是将定义的拦截器添加到RestTemplate中。

    定义一个拦截器

    实现ClientHttpRequestInterceptor接口就具备了拦截请求的功能,该接口源码如下:

    public interface ClientHttpRequestInterceptor {
        /**
         *实现该方法,在该方法内完成拦截请求后的逻辑内容。
         *对于ribbon而言,在该方法内完成了根据具体规则从
         *服务集群中选取一个服务,并向该服务发起请求的操作。
         */

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

    }

    ribbon中对应的实现类是LoadBalancerInterceptor具体源码如下:

    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();
          /**
           *拦截请求,并调用loadBalancer.execute()方法
           *在该方法内部完成server的选取。向选取的server
           *发起请求,并获得返回结果。
           */

          return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
       }
    }

    将拦截器添加到RestTemplate中

    RestTemplate继承了InterceptingHttpAccessor,在InterceptingHttpAccessor中提供了获取以及添加拦截器的方法,具体源码如下:

    public abstract class InterceptingHttpAccessor extends HttpAccessor {

        /**
         * 所有的拦截器是以一个List集合形式进行保存。
         */

       private List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();

       /**
        * 设置拦截器。
        */

       public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {
          this.interceptors = interceptors;
       }

       /**
        * 获取当前的拦截器。
        */

       public List<ClientHttpRequestInterceptor> getInterceptors() {
          return interceptors;
       }

       //省略部分代码...
    }

    通过这两个方法我们就可以将刚才定义的LoadBalancerInterceptor添加到有@LoadBalanced注解标识的RestTemplate中。具体的源码如下(LoadBalancerAutoConfiguration)省略部分代码:

    public class LoadBalancerAutoConfiguration {

        /**
          * 获取所有带有@LoadBalanced注解的restTemplate
         */

       @LoadBalanced
       @Autowired(required = false)
       private List<RestTemplate> restTemplates = Collections.emptyList();

        /**
         * 创建SmartInitializingSingleton接口的实现类。Spring会在所有
         * 单例Bean初始化完成后回调该实现类的afterSingletonsInstantiated()
         * 方法。在这个方法中会为所有被@LoadBalanced注解标识的
         * RestTemplate添加ribbon的自定义拦截器LoadBalancerInterceptor。
         */

       @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);
                   }
                }
             }
          };
       }
        /**
         * 创建Ribbon自定义拦截器LoadBalancerInterceptor
         * 创建前提是当前classpath下不存在spring-retry。
         * 所以LoadBalancerInterceptor是默认的Ribbon拦截
         * 请求的拦截器。
         */

        @Configuration
        @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
       static class LoadBalancerInterceptorConfig {
          @Bean
          public LoadBalancerInterceptor ribbonInterceptor(
                LoadBalancerClient loadBalancerClient,
                LoadBalancerRequestFactory requestFactory)
     
    {
             return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
          }

          /**
           * 添加拦截器具体方法。首先获取当前拦截器集合(List)
           * 然后将loadBalancerInterceptor添加到当前集合中
           * 最后将新的集合放回到restTemplate中。
           */

          @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);
                }
             };
          }
       }
    }

    至此知道了ribbon拦截请求的基本原理,接下来我们看看Ribbon是怎样选取server的。

    Ribbon选取server原理概览

    通过上面的介绍我们知道了当发起请求时ribbon会用LoadBalancerInterceptor这个拦截器进行拦截。在该拦截器中会调用LoadBalancerClient.execute()方法,该方法具体代码如下:

    @Override
    public <T> execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
      /**
       *创建loadBalancer的过程可以理解为组装选取服务的规则(IRule)、
       *服务集群的列表(ServerList)、检验服务是否存活(IPing)等特性
       *的过程(加载RibbonClientConfiguration这个配置类),需要注意
       *的是这个过程并不是在启动时进行的,而是当有请求到来时才会处理。
       */

       ILoadBalancer loadBalancer = getLoadBalancer(serviceId);

       /**
        * 根据ILoadBalancer来选取具体的一个Server。
        * 选取的过程是根据IRule、IPing、ServerList
        * 作为参照。
        */

       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);
    }

    通过代码我们可知,首先创建一个ILoadBalancer,这个ILoadBalancer是Ribbon的核心类。可以理解成它包含了选取服务的规则(IRule)、服务集群的列表(ServerList)、检验服务是否存活(IPing)等特性,同时它也具有了根据这些特性从服务集群中选取具体一个服务的能力。Server server = getServer(loadBalancer);这行代码就是选取举一个具体server。最终调用了内部的execute方法,该方法代码如下(只保留了核心代码):

    @Override
    public <T> execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
       try {
          //发起调用
          T returnVal = request.apply(serviceInstance);
          statsRecorder.recordStats(returnVal);
          return returnVal;
       }
       catch (IOException ex) {
          statsRecorder.recordStats(ex);
          throw ex;
       }
       catch (Exception ex) {
          statsRecorder.recordStats(ex);
          ReflectionUtils.rethrowRuntimeException(ex);
       }
       return null;
    }

    接下来看下request.apply(serviceInstance)方法的具体做了那些事情(LoadBalancerRequestFactory中):

    @Override
    public ClientHttpResponse apply(final ServiceInstance instance)
          throws Exception 
    {
       HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer);
       //省略部分代码...
       /**
        * 发起真正请求。
        */

       return execution.execute(serviceRequest, body);
    }

    看到这里整体流程的原理就说完了,接下来我们结合一张图来回顾下整个过程:

    首先获取所有标识@LoadBalanced注解的RestTemplate(可以理解成获取那些开启了Ribbon负载均衡功能的RestTemplate),然后将Ribbon默认的拦截器LoadBalancerInterceptor添加到RestTemplate中,这样当使用RestTemplate发起http请求时就会起到拦截的作用。当有请求发起时,ribbon默认的拦截器首先会创建ILoadBalancer(里面包含了选取服务的规则(IRule)、服务集群的列表(ServerList)、检验服务是否存活(IPing)等特性)。在代码层面的含义是加载RibbonClientConfiguration配置类)。然后使用ILoadBalancer从服务集群中选择一个服务,最后向这个服务发送请求。

    Ribbon负载均衡规则

    Ribbon默认负载均衡规则

    根据上述Ribbon的原理,可以知道IRule接口负责负载均衡的实现,具体如下:

    随机源码:

    轮询源码:

    修改默认的自定义规则

    修改为随机算法

    修改为按照Nacos配置的权重进行负载均衡

    Ribbon实战优化

    饥饿加载

    Ribbon默认懒加载,意味着只有在发起调用的时候才会创建客户端

    ribbon:
      eager-load:
        # 开启ribbon饥饿加载
        enabled: true
        # 配置user-center使用ribbon饥饿加载,多个使用逗号分隔
        clients: user-center

    参数调优

    主要调整请求的超时时间,是否重试

    # 从注册中心刷新servelist的时间 默认30秒,单位ms
    ribbon.ServerListRefreshInterval=15000
    # 请求连接的超时时间 默认1秒,单位ms
    ribbon.ConnectTimeout=30000
    # 请求处理的超时时间 默认1秒,单位ms
    ribbon.ReadTimeout=30000
    # 对所有操作请求都进行重试,不配置这个MaxAutoRetries不起作用 默认false
    #ribbon.OkToRetryOnAllOperations=true
    # 对当前实例的重试次数 默认0
    # ribbon.MaxAutoRetries=1
    # 切换实例的重试次数 默认1
    ribbon.MaxAutoRetriesNextServer=0
    

    如果你觉得这篇内容对你挺有有帮助的话:

    01-15 01:46