微服务负载均衡组件Ribbon——源码解读篇

从这几篇源码解读可以发现一个规律,那就是引进某个服务,会经常看到@Configuration和@Import(核心服务注册类)这两个注解,伴随着@ConditonalOnClass(XXX.class)、@ConditionalOnBean(XXX.class)、@ConditionOnProperty(***)和@ConditionalOnMissingBean(XXX.class)。作为Spring Boot神奇的特性-简化配置,@Configuration起了很大的作用,此处做个伏笔,感兴趣的可以先阅读JavaDoc,关于@Configuration的详细解释会在之后的Spring Boot系列文章中阐明。按照惯例,此处从核心服务类开始展开对Ribbon的源码解读

@Configuration
@Import(RibbonClientConfigurationRegistrar.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RibbonClient {

   String value() default "";

   // 配置Ribbon客户端名字
   String name() default "";

    // Ribbon客户端的自定义配置,可以配置生成客户端的各个组件,比如说ILoadBalancer、ServerListFilter和IRule。默认的配置为RibbonClientConfiguration.java
   Class[] configuration() default {};

}

RibbonClientConfigurationRegistrar.class实现了接口ImportBeanDefinitionRegistrar,这个接口是Spring动态注册BeanDefinition的接口,用来注册Ribbon所需要的BeanDefinition

我们看下接口ImportBeanDefinitionRegistrar的描述,处理被注解@Configuration标注的类时用来标记bean definition,说白了就是这个接口用来实现动态注入,可以通过下面的小Demo进行Debug,一步一步看下代码Spring Boot做了什么

Interface to be implemented by types that register additional bean definitions when processing @{@link Configuration} classes. Useful when operating at the bean definition level (as opposed to {@code @Bean} method/instance level) is desired or necessary.  

小Demo,案例来自Spring - Using ImportBeanDefinitionRegistrar

package thinking.in.spring.boot;

/**
* @Author: Bruce
* @Date: 2019/7/10 0:18
* @Version 1.0
*/
public class AppBean {

    private String str;

    public void setStr(String str) {
        this.str = str;
    }

    public void process() {
        System.out.println(str);
    }
}
package thinking.in.spring.boot.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import thinking.in.spring.boot.MyBeanRegistrar;
import thinking.in.spring.boot.bean.ClientBean;

/**
* @Author: Bruce
* @Date: 2019/7/10 0:19
* @Version 1.0
*/
@Configuration
@Import(MyBeanRegistrar.class)
public class MyConfig {

    @Bean
    ClientBean clientBean() {
        return new ClientBean();
    }
}
package thinking.in.spring.boot.bean;

import org.springframework.beans.factory.annotation.Autowired;
import thinking.in.spring.boot.AppBean;

/**
* @Author: Bruce
* @Date: 2019/7/10 0:20
* @Version 1.0
*/
public class ClientBean {

    @Autowired
    private AppBean appBean;

    public void doSomething() {
        appBean.process();
    }
}
package thinking.in.spring.boot;

import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

/**
* @Author: Bruce
* @Date: 2019/7/10 0:14
* @Version 1.0
*/
public class MyBeanRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        GenericBeanDefinition gbd = new GenericBeanDefinition();
        gbd.setBeanClass(AppBean.class);
        gbd.getPropertyValues().addPropertyValue("str", "value set from registrar");
        registry.registerBeanDefinition("AppBean", gbd);
    }
}
package thinking.in.spring.boot;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import thinking.in.spring.boot.bean.ClientBean;
import thinking.in.spring.boot.config.MyConfig;

/**
* @Author: Bruce
* @Date: 2019/7/10 0:21
* @Version 1.0
*/
public class ImportBeanDefinitionRegistrarExample {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);

        ClientBean bean = applicationContext.getBean(ClientBean.class);
        bean.doSomething();
    }
}

通过上面的小Demo相信可以粗略了解到实现接口ImportBeanDefinitionRegistrar的作用,那么接下来就通过源码分析Ribbon又是如何具体实现
RibbonClientConfigurationRegistrar.class

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   // 获取@RibbonClient的参数值,获取clientName后进行configuration的注册
   Map attrs = metadata
         .getAnnotationAttributes(RibbonClients.class.getName(), true);
   if (attrs != null && attrs.containsKey("value")) {
      AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
      for (AnnotationAttributes client : clients) {
         // 向BeanDefinitionRegistry注册一个RibbonClientSpecification的BeanDefinition 
         // getClientName()获取RibbonClient的value或者name数值
         registerClientConfiguration(registry, getClientName(client),
               client.get("configuration"));
      }
   }
   if (attrs != null && attrs.containsKey("defaultConfiguration")) {
      String name;
      if (metadata.hasEnclosingClass()) {
         name = "default." + metadata.getEnclosingClassName();
      }
      else {
         name = "default." + metadata.getClassName();
      }
      registerClientConfiguration(registry, name,
            attrs.get("defaultConfiguration"));
   }
   Map client = metadata
         .getAnnotationAttributes(RibbonClient.class.getName(), true);
   String name = getClientName(client);
   if (name != null) {
      registerClientConfiguration(registry, name, client.get("configuration"));
   }
}

在方法 registerBeanDefinitions()中,我们看到了调用registerClientConfiguration()方法来注册,那么注册了什么,跟进代码可以看到,使用构造器的设计模式构建了BeanDefinition,然后在注册表registry(beanDefinitionMap,实际上是一个ConcurrentHashMap的容器)中添加key(也就是beanName)为客户端名.RibbonClientSpecification,value为对应的Bean Definition。RibbonClientSpecification是NameContextFactory.Specification的实现类,供SpringClientFactory使用,而RibbonAutoConfiguration里会进行SpringClientFactory实例的初始化
RibbonClientConfigurationRegistrar.class

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
      Object configuration) {
   BeanDefinitionBuilder builder = BeanDefinitionBuilder
         .genericBeanDefinition(RibbonClientSpecification.class);
   builder.addConstructorArgValue(name);
   builder.addConstructorArgValue(configuration);
   // 向BeanDefinitionRegistry注册一个RibbonClientSpecification
   registry.registerBeanDefinition(name + ".RibbonClientSpecification",
         builder.getBeanDefinition());
}

BeanDefinitionBuilder.class

public static BeanDefinitionBuilder genericBeanDefinition(Class beanClass) {
   // 通过Builder模式创建BeanDefinitionBuilder
   BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
   builder.beanDefinition.setBeanClass(beanClass);
   return builder;
}

Spring Boot框架有个特性,就是利用SPI来做自动化配置,对Ribbon组件,可以在文件spring.factories中看到它的自动配置类是org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration
Spring Cloud作为搭建在Spring Boot之上的生态,更是沿用了这个编程习惯。了解Spring Boot框架就会发现,Spring Boot会有一个spring.factories的文件,里面配置了许多的配置类和监听类等,这样就可以和各个组件一起使用,但是又不必耦合在一起,算是SPI的典型应用,这也是Spring Boot经典的自动配置特性。事实上这个特性就是留给开发人员去开发自定义自动装配类,Spring Cloud Ribbon就是对Ribbon的Spring Boot改造,新增了Spring Boot的特性。由于本篇文章重点是关于Ribbon的源码,大家可以阅读下篇官方文章了解一二。

49. Creating Your Own Auto-configuration

微服务负载均衡组件Ribbon——源码解读篇_第1张图片

微服务负载均衡组件Ribbon——源码解读篇_第2张图片

org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration.class

@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
// LoadBalancerAutoConfiguration.class和AsyncLoadBalancerAutoConfiguration.class会在本配置类前执行
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
      AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
      ServerIntrospectorProperties.class })
public class RibbonAutoConfiguration {

   @Autowired(required = false)
   // RibbonClientConfigurationRegistrar中注册的RibbonClientSpecification会被注入到configurations里
   private List configurations = new ArrayList<>();

   @Autowired
   private RibbonEagerLoadProperties ribbonEagerLoadProperties;

   @Bean
   public HasFeatures ribbonFeature() {
      return HasFeatures.namedFeature("Ribbon", Ribbon.class);
   }

   @Bean
   public SpringClientFactory springClientFactory() {
      SpringClientFactory factory = new SpringClientFactory();
      factory.setConfigurations(this.configurations);
      return factory;
   }

   @Bean
   @ConditionalOnMissingBean(LoadBalancerClient.class)
   public LoadBalancerClient loadBalancerClient() {
    // 创建新的RibbonLoadBalancerClient实例,传入配置好的SpringClientFactory,最终会调用SpringClientFactory.getLoadBalancerContext()方法来获得实例
      return new RibbonLoadBalancerClient(springClientFactory());
   }
}

此处可以看到方法loadBalancerClient()返回一个新的Ribbon负载客户端,那么客户端跟负载均衡是什么关系,跟进代码看下
首先看下接口LoadBalancerClient

public interface LoadBalancerClient extends ServiceInstanceChooser {

    T execute(String serviceId, LoadBalancerRequest request) throws IOException;

    T execute(String serviceId, ServiceInstance serviceInstance,
         LoadBalancerRequest request) throws IOException;

   // 下面这段话点明了可以使用服务名来调用网络请求,Ribbon会自动将其转换为host:port格式,这也是我在实战篇中提到的使用服务名来调用网络请求,避免host的硬编码,可见开发人员设计之初的思维还是挺缜密的
   /**
    * Creates a proper URI with a real host and port for systems to utilize. Some systems
    * use a URI with the logical service name as the host, such as
    * http://myservice/path/to/service. This will replace the service name with the
    * host:port from the ServiceInstance.
    * @param instance service instance to reconstruct the URI
    * @param original A URI with the host as a logical service name.
    * @return A reconstructed URI.
    */
   URI reconstructURI(ServiceInstance instance, URI original);
}

可以看到接口LoadBalancerClient还继承了接口ServiceInstanceChooser,跟进去看下,从描述可以知道choose()方法会根据服务Id从服务器列表中选择相应的服务实例,那么具体是如何实现的,跟进实现类RibbonLoadBalancerClient.class一探究竟

public interface ServiceInstanceChooser {

   /**
    * Chooses a ServiceInstance from the LoadBalancer for the specified service.
    * @param serviceId The service ID to look up the LoadBalancer.
    * @return A ServiceInstance that matches the serviceId.
    */
   ServiceInstance choose(String serviceId);
}

RibbonLoadBalancerClient.class

@Override
public ServiceInstance choose(String serviceId) {
   return choose(serviceId, null);
}

public ServiceInstance choose(String serviceId, Object hint) {
// 获取服务器
   Server server = getServer(getLoadBalancer(serviceId), hint);
   if (server == null) {
      return null;
   }
// 将服务器封装成RibbonServer
   return new RibbonServer(serviceId, server, isSecure(server, serviceId),
         serverIntrospector(serviceId).getMetadata(server));
}

public  T execute(String serviceId, LoadBalancerRequest request, Object hint)
      throws IOException {
   // 每次发送请求都会获得ILoadBalancer
   ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
   Server server = getServer(loadBalancer, hint);
   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);
}

从下图可知ZoneAwareLoadBalancer是Spring Cloud提供ILoadBalancer的默认实例,代码org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration类中也可以看出各个Bean 的默认实现,例如ZoneAwareLoadBalancer为ILoadBalancer的默认实现,又或者com.netflix.client.config.DefaultClientConfigImpl

微服务负载均衡组件Ribbon——源码解读篇_第3张图片

@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
      ServerList serverList, ServerListFilter serverListFilter,
      IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
   if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
      return this.propertiesFactory.get(ILoadBalancer.class, config, name);
   }
    // 传入参数IClientConfig、IRule、IPing、ServerList、ServerListFilter、ServerListUpdater
   return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
         serverListFilter, serverListUpdater);
}

DummyPing为IPing的默认实现

@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
   if (this.propertiesFactory.isSet(IPing.class, name)) {
      return this.propertiesFactory.get(IPing.class, config, name);
   }
   return new DummyPing();
}

那么我们分析在这个类是如何实现负载均衡的,
从ZoneAwareLoadBalancer的构造函数可以知道它传进去了许多负载均衡的配置类,跟进代码看下具体是如何实现的
ZoneAwareLoadBalancer.class

@Override
public Server chooseServer(Object key) {
    if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
        logger.debug("Zone aware logic disabled or there is only one zone");
        return super.chooseServer(key);
    }
    Server server = null;
    try {
        // 获取当前有关负载均衡的服务器状态
        LoadBalancerStats lbStats = getLoadBalancerStats();
        Map zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
        logger.debug("Zone snapshots: {}", zoneSnapshot);
        if (triggeringLoad == null) {
            // 获取平均负载的阈值
            triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(
                    "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2d);
        }


        if (triggeringBlackoutPercentage == null) {
            // 获取平局实例故障率的阈值
            triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
                    "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999d);
        }
        // 根据两个阈值来获取所有可用的服务区列表
        Set availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
        logger.debug("Available zones: {}", availableZones);
        if (availableZones != null &&  availableZones.size() < zoneSnapshot.keySet().size()) {
            // 随机从可用的服务区列表中选择一个服务区
            String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
            logger.debug("Zone chosen: {}", zone);
            if (zone != null) {
                // 得到zone对应的BaseLoadBalancer
                BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
                server = zoneLoadBalancer.chooseServer(key);
            }
        }
    } catch (Exception e) {
        logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
    }
    if (server != null) {
        return server;
    } else {
        logger.debug("Zone avoidance logic is not invoked.");
        return super.chooseServer(key);
    }
}

可以知道通过方法ZoneAvoidanceRule.getAvailableZones()来获取可用服务区列表,那么具体是如何实现的呢?
下图是实现的一个获取可用区域的流程图

微服务负载均衡组件Ribbon——源码解读篇_第4张图片

注:图片来自Spring Cloud微服务架构进阶

ZoneAvoidanceRule.class

public static Set getAvailableZones(
        Map snapshot, double triggeringLoad,
        double triggeringBlackoutPercentage) {
    if (snapshot.isEmpty()) {
        return null;
    }
    Set availableZones = new HashSet(snapshot.keySet());
    if (availableZones.size() == 1) {
        return availableZones;
    }
    Set worstZones = new HashSet();
    double maxLoadPerServer = 0;
    boolean limitedZoneAvailability = false;

       // 遍历所有的服务区来判定
    for (Map.Entry zoneEntry : snapshot.entrySet()) {
        String zone = zoneEntry.getKey();
        ZoneSnapshot zoneSnapshot = zoneEntry.getValue();
        // 判定该服务区中的服务实例数
        int instanceCount = zoneSnapshot.getInstanceCount();
        if (instanceCount == 0) {
            // 如果服务区没有服务实例,删除该服务区
            availableZones.remove(zone);
            limitedZoneAvailability = true;
        } else {
            double loadPerServer = zoneSnapshot.getLoadPerServer();
            if (((double) zoneSnapshot.getCircuitTrippedCount())
                    / instanceCount >= triggeringBlackoutPercentage
                    || loadPerServer < 0) {
               // 实例故障率(断路器断开次数/实例数)大于等于阈值(阈值为0.99999)或者服务区实例平均负载小于0,删除该服务区
                availableZones.remove(zone);
                limitedZoneAvailability = true;
            } else {
                // 如果服务区的平均负载和最大负载小于0.000001d,则添加该服务区到worstZones(HashSet容器)集合中
                if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
                    // they are the same considering double calculation
                    // round error
                    worstZones.add(zone);
                } else if (loadPerServer > maxLoadPerServer) {
                    maxLoadPerServer = loadPerServer;
                    worstZones.clear();
                    worstZones.add(zone);
                }
            }
        }
    }
    // 如果最大负载小于设置的阈值则直接返回
    if (maxLoadPerServer < triggeringLoad && !limitedZoneAvailability) {
        // zone override is not needed here
        return availableZones;
    }
    String zoneToAvoid = randomChooseZone(snapshot, worstZones);
    // 如果最大平均负载大于等于设定的阈值,随机从worstZones中剔除一个
    if (zoneToAvoid != null) {
        availableZones.remove(zoneToAvoid);
    }
    return availableZones;
}

至此,关于Ribbon的负载均衡实现就大略说这些。

 

 

推荐阅读:
Spring Cloud 微服务架构进阶

6. Client Side Load Balancer: Ribbon

Spring - Using ImportBeanDefinitionRegistrar

你可能感兴趣的:(微服务组件)