1、前面一篇博客我们分析了spring-cloud的loadBalancer标准的内容,今天我们来分析spring-cloud-netflix-ribbon的是实现:
我们在使用spring-cloud-netflix-ribbon的时候,会依赖如下的jar包:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-ribbon</artifactId></dependency>
而spring-cloud-starter-netflix-ribbon这个jar的核心依赖如下:可以看出依赖了Netflix 的ribbon、spring-cloud-nexflix-ribbon 等核心包。
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-netflix-ribbon</artifactId><version>2.2.5.RELEASE</version><scope>compile</scope></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-archaius</artifactId><version>2.2.5.RELEASE</version><scope>compile</scope></dependency><dependency><groupId>com.netflix.ribbon</groupId><artifactId>ribbon</artifactId><version>2.3.0</version><scope>compile</scope><exclusions><exclusion><artifactId>netty-codec-http</artifactId><groupId>io.netty</groupId></exclusion><exclusion><artifactId>netty-transport-native-epoll</artifactId><groupId>io.netty</groupId></exclusion></exclusions></dependency><dependency><groupId>com.netflix.ribbon</groupId><artifactId>ribbon-core</artifactId><version>2.3.0</version><scope>compile</scope><exclusions><exclusion><artifactId>annotations</artifactId><groupId>com.google.code.findbugs</groupId></exclusion></exclusions></dependency><dependency><groupId>com.netflix.ribbon</groupId><artifactId>ribbon-httpclient</artifactId><version>2.3.0</version><scope>compile</scope><exclusions><exclusion><artifactId>annotations</artifactId><groupId>com.google.code.findbugs</groupId></exclusion></exclusions></dependency><dependency><groupId>com.netflix.ribbon</groupId><artifactId>ribbon-loadbalancer</artifactId><version>2.3.0</version><scope>compile</scope><exclusions><exclusion><artifactId>annotations</artifactId><groupId>com.google.code.findbugs</groupId></exclusion></exclusions></dependency>
2、spring-cloud-netflix-ribbon实现的LoadBalancerClient
spring-cloud-netflix-ribbon 中实现了LoadBalancerClient接口的类是RibbonLoadBalancerClient类;我们在前面分析过LoadBalancerClient的核心方法有如下:
1、重构url的方法:reconstructURI(...)
2、根据服务id选择服务的方法:choose(...)
3、执行请求的两个方法:execute(String serviceId, LoadBalancerRequest<T> request)、execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request)。
我们来分析:RibbonLoadBalancerClient.execute(String serviceId, LoadBalancerRequest<T> request)的源码实现:
@Overridepublic <T> T execute(String serviceId, LoadBalancerRequest<T> request)throws IOException {return execute(serviceId, request, null);}public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)throws IOException {获取一个负载均衡器,负载均衡客户端里面可以获取负载均衡器,基本操作。ILoadBalancer loadBalancer = getLoadBalancer(serviceId);通过负载均衡器获取一个服务实例,这个服务实例就是Netflix中定义的Server,这里就是spring-
cloud整合Netflix loadbalancer的桥接。Server server = getServer(loadBalancer, hint);if (server == null) {throw new IllegalStateException("No instances available for " + serviceId);}使用Netflix中定义的实例转换为RibbonServer,注意RibbonServer extends 了 spring-cloud
中的服务实例ServiceInstance。RibbonServer ribbonServer = new RibbonServer(serviceId, server,isSecure(server, serviceId),serverIntrospector(serviceId).getMetadata(server));执行请求return execute(serviceId, ribbonServer, request);}执行请求实现,没错就是调用另一个执行方法:@Overridepublic <T> T execute(String serviceId, ServiceInstance serviceInstance,LoadBalancerRequest<T> request) throws IOException {Server server = null;if (serviceInstance instanceof RibbonServer) {server = ((RibbonServer) serviceInstance).getServer();}if (server == null) {throw new IllegalStateException("No instances available for " + serviceId);}通过serviceId 获取一个负载均衡上下文,这里后续会详细讲解,因为这里比较复杂,牵扯到spring
的NamedContextFactory命名上下文工厂的内容。RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);使用负载均衡上下文 + server实例来获取负载均衡的统计记录,统计记录是用来实现负载均衡算法
的,例如轮询的话,需要有地方记录调用次数。RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);try {然后执行LoadBalancerRequest的apply方法发起远程请求,前面我们说过,这个request是从
LoadBalancerInterceptor的intercept方法里面构建的,且是一个lamda,那么此时的apply方法就会执行
这个lamda的内容,lamada的内容我们上一篇博文详细分析过。此处不在累赘,我们现在关注的就是
serviceInstance是如何选择的,URL是如何重构的。T returnVal = request.apply(serviceInstance);statsRecorder.recordStats(returnVal);return returnVal;}// catch IOException and rethrow so RestTemplate behaves correctlycatch (IOException ex) {statsRecorder.recordStats(ex);throw ex;}catch (Exception ex) {statsRecorder.recordStats(ex);ReflectionUtils.rethrowRuntimeException(ex);}return null;}
上面的源码分析中有两个核心的地方那就是:
1、ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
2、Server server = getServer(loadBalancer, hint);
先分析ILoadBalancer loadBalancer = getLoadBalancer(serviceId); 通过serviceId获取负载均衡器,此处的serviceId就是服务名称,例如:order-service
protected ILoadBalancer getLoadBalancer(String serviceId) {使用客户端工厂获取负载均衡器return this.clientFactory.getLoadBalancer(serviceId);}
尼玛??这么简单的吗??????这只是表象,我们来看看 clientFactory 到底是何方神圣!在 RibbonLoadBalancerClient 中 clientFactory 是一个私有的成员变量
private SpringClientFactory clientFactory;
问题来了,这个clientFactory是什么时候实例化 + 设置到 RibbonLoadBalancerClient 中的呢??? 熟悉 spring-cloud 的朋友应该都知道,不熟悉的回去补补基础知识,我们不在过多讨论,直接来到spring-cloud-netflix-ribbon jar包下面,找到 spring.factories 文件,如下图:
理所当然进入 RibbonAutoConfiguration 配置类中来,其他的不罗嗦,我们找到了如下的两个BeanDefinition:这个代码不做过多解释,就是这样上面提到的clientFactory就被设置到RibbonLoadBalancerClient中了。
这里是不是很神奇,依赖注入所有的RibbonClient的规范,这个规范是在NamedContextFactory中定
义的 某上下文的某些配置类。那么这些RibbonClientSpecification是在什么时候注册到spring boot的应用
上下文中的呢????@Autowired(required = false)private List<RibbonClientSpecification> configurations = new ArrayList<>();@Bean@ConditionalOnMissingBeanpublic SpringClientFactory springClientFactory() {SpringClientFactory factory = new SpringClientFactory();核心!!! 将当前配置类中的configurations 设置到SpringClientFactory 实例中,
configurations 里面存放的是每一个RibbonClient的配置Bean的信息。factory.setConfigurations(this.configurations);return factory;}@Bean@ConditionalOnMissingBean(LoadBalancerClient.class)public LoadBalancerClient loadBalancerClient() {return new RibbonLoadBalancerClient(springClientFactory());}
这里剖析结束后,那么我们将重点关注到 SpringClientFactory 上来,首先查看 SpringClientFactory 的类图:
集成的父类是一个抽象类 NamedContextFactory 这个类的作用就是根据名称动态创建一个spring的应用上下文。
NamedContextFactory 知识点铺垫: spring-cloud的名称上下文工厂NamedContextFactory
顾名思义,名称应用上下文工厂的作用就是通过名称来构建一个应用上下文实例,NamedContextFactory
中抽象的通过名称来构建一个应用上下文方法,源码解析如下:
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>implements DisposableBean, ApplicationContextAware {//配置源名称,应用上下文都有环境实例,而环境实例中存放了很多配置源,每个配置源都有一个名称,这个propertySourceName就是配置源名称private final String propertySourceName;//配置项名称,在通过名称name创建上下文的时候会将key=propertyName value = 上下文name 添加到名称= propertySourceName的配置源中。private final String propertyName;//缓存已经创建的应用上下文实例,key=上下文name value=上下文实例private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();//需要项创建的上下文中,添加那些配置类就存放在这个map里面,key=上下文名称,value=需要注册到上下文中的配置类类型private Map<String, C> configurations = new ConcurrentHashMap<>();//父上下文,由于NamedContextFactory 实现了ApplicationContextAware,因此在springbootd的应用上下文准备好的时候会调用当前类的setApplicationContext方法,就会将parent属性设置为springbootd的应用上下文实例private ApplicationContext parent;//默认的配置类类型,会被注册到被创建的上下文中。private Class<?> defaultConfigType;public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,String propertyName) {this.defaultConfigType = defaultConfigType;this.propertySourceName = propertySourceName;this.propertyName = propertyName;}@Overridepublic void setApplicationContext(ApplicationContext parent) throws BeansException {this.parent = parent;}public void setConfigurations(List<C> configurations) {for (C client : configurations) {this.configurations.put(client.getName(), client);}}public Set<String> getContextNames() {return new HashSet<>(this.contexts.keySet());}@Overridepublic void destroy() {Collection<AnnotationConfigApplicationContext> values = this.contexts.values();for (AnnotationConfigApplicationContext context : values) {// This can fail, but it never throws an exception (you see stack traces// logged as WARN).context.close();}this.contexts.clear();}protected AnnotationConfigApplicationContext getContext(String name) {if (!this.contexts.containsKey(name)) {synchronized (this.contexts) {if (!this.contexts.containsKey(name)) {this.contexts.put(name, createContext(name));}}}return this.contexts.get(name);}//我们重点关注的创建上下文的实现,通过名称来创建上下文,这名称可以随意指定,比如ribbon 就使用服务名称来进行创建,例如order-service。protected AnnotationConfigApplicationContext createContext(String name) {//1、创建一个应用上下文AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();//2、如果在所有的上下文配置类的列表中 存在当前名称上下文的配置类,那就注册到上面new 的context中。if (this.configurations.containsKey(name)) {for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {context.register(configuration);}}//3、如果在所有的上下文配置类的列表中 存在名称是以default.开始的,那就将其配置类注册到上面new 的context中。for (Map.Entry<String, C> entry : this.configurations.entrySet()) {if (entry.getKey().startsWith("default.")) {for (Class<?> configuration : entry.getValue().getConfiguration()) {context.register(configuration);}}}//4、注册PropertyPlaceholderAutoConfiguration到上面new 的context中,用于Beandefinition的占位符替换。context.register(PropertyPlaceholderAutoConfiguration.class,this.defaultConfigType);//5、获取到上面new 的context中的环境实例Environment然后添加一个名称为propertySourceName的配置源;并且把属性//key = propertyName value = 上下文名称name 添加到配置源中context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName,Collections.<String, Object>singletonMap(this.propertyName, name)));if (this.parent != null) {// Uses Environment from parent as well as beanscontext.setParent(this.parent);// jdk11 issue// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101context.setClassLoader(this.parent.getClassLoader());}context.setDisplayName(generateDisplayName(name));//刷新容器context.refresh();return context;}protected String generateDisplayName(String name) {return this.getClass().getSimpleName() + "-" + name;}//通过上下文名称,BeanClass 来获取bean实例public <T> T getInstance(String name, Class<T> type) {AnnotationConfigApplicationContext context = getContext(name);if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,type).length > 0) {return context.getBean(type);}return null;}public <T> ObjectProvider<T> getLazyProvider(String name, Class<T> type) {return new ClientFactoryObjectProvider<>(this, name, type);}public <T> ObjectProvider<T> getProvider(String name, Class<T> type) {AnnotationConfigApplicationContext context = getContext(name);return context.getBeanProvider(type);}public <T> T getInstance(String name, Class<?> clazz, Class<?>... generics) {ResolvableType type = ResolvableType.forClassWithGenerics(clazz, generics);return getInstance(name, type);}@SuppressWarnings("unchecked")public <T> T getInstance(String name, ResolvableType type) {AnnotationConfigApplicationContext context = getContext(name);String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,type);if (beanNames.length > 0) {for (String beanName : beanNames) {if (context.isTypeMatch(beanName, type)) {return (T) context.getBean(beanName);}}}return null;}public <T> Map<String, T> getInstances(String name, Class<T> type) {AnnotationConfigApplicationContext context = getContext(name);if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,type).length > 0) {return BeanFactoryUtils.beansOfTypeIncludingAncestors(context, type);}return null;}/*** Specification with name and configuration.*///配置类规范,每一个规范实例描述的是,名称为name的上下文,它需要注册的配置类列表是啥。public interface Specification {String getName();Class<?>[] getConfiguration();}}
NamedContextFactory讲解完成,那么我们来看看它的实现类 SpringClientFactory 的实现,源码如下:
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {static final String NAMESPACE = "ribbon";//无参数构造函数,public SpringClientFactory() {调用父类的有参数构造函数,这样就会将NamedContextFactory类中的属性有如下设置1、defaultConfigType = RibbonClientConfiguration.class 这样在通过名称构建context的
时候,当context刷新的时候就会装配RibbonClientConfiguration。RibbonClientConfiguration这个类
里面就会去装载当前RibbonClient的客户端配置信息实例IClientConfig,例如order-
service.ribbon.listOfServers=http://xxxx:xx 就是在此处读取的。 注意!!
RibbonClientConfiguration类中有一个标注了@RibbonClientName的属性,而@RibbonClientName 注解
= @Value("${ribbon.client.name}") 这他妈不就是在下面的配置源中添加的配置项吗,例如:
ribbon.client.name=order-service2、propertySourceName = ribbon3、propertyName = ribbon.client.name,这样的话,在通过名称创建上下文的时候,就会在被创建
的上下文实例的环境实例中添加一个名称=ribbon的配置源,然后这个配置源中会配置一个
ribbon.client.name=connext name 例如:ribbon.client.name=order-service/*public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,String propertyName) {this.defaultConfigType = defaultConfigType;this.propertySourceName = propertySourceName;this.propertyName = propertyName;}*/super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");}/*** Get the rest client associated with the name.* @param name name to search by* @param clientClass the class of the client bean* @param <C> {@link IClient} subtype* @return {@link IClient} instance* @throws RuntimeException if any error occurs*/public <C extends IClient<?, ?>> C getClient(String name, Class<C> clientClass) {return getInstance(name, clientClass);}/*** Get the load balancer associated with the name.* @param name name to search by* @return {@link ILoadBalancer} instance* @throws RuntimeException if any error occurs*///获取以个负载均衡器通过名称,这个名称是啥呢,其实就是contect name 也就是服务名称public ILoadBalancer getLoadBalancer(String name) {return getInstance(name, ILoadBalancer.class);}/*** Get the client config associated with the name.* @param name name to search by* @return {@link IClientConfig} instance* @throws RuntimeException if any error occurs*/public IClientConfig getClientConfig(String name) {return getInstance(name, IClientConfig.class);}/*** Get the load balancer context associated with the name.* @param serviceId id of the service to search by* @return {@link RibbonLoadBalancerContext} instance* @throws RuntimeException if any error occurs*/public RibbonLoadBalancerContext getLoadBalancerContext(String serviceId) {return getInstance(serviceId, RibbonLoadBalancerContext.class);}static <C> C instantiateWithConfig(Class<C> clazz, IClientConfig config) {return instantiateWithConfig(null, clazz, config);}static <C> C instantiateWithConfig(AnnotationConfigApplicationContext context,Class<C> clazz, IClientConfig config) {C result = null;try {Constructor<C> constructor = clazz.getConstructor(IClientConfig.class);result = constructor.newInstance(config);}catch (Throwable e) {// Ignored}if (result == null) {result = BeanUtils.instantiateClass(clazz);if (result instanceof IClientConfigAware) {((IClientConfigAware) result).initWithNiwsConfig(config);}if (context != null) {context.getAutowireCapableBeanFactory().autowireBean(result);}}return result;}@Overridepublic <C> C getInstance(String name, Class<C> type) {C instance = super.getInstance(name, type);if (instance != null) {return instance;}IClientConfig config = getInstance(name, IClientConfig.class);return instantiateWithConfig(getContext(name), type, config);}@Overrideprotected AnnotationConfigApplicationContext getContext(String name) {return super.getContext(name);}}
通过上面我们知道了,spring-cloud-netflix-ribbon 会为每一个 RibbonClient 都创建一个ApplicationContext实例, ApplicationContext中存放着当前 RibbonClient 的配置bean,模型图如下:
接下来我们来解答每一个RibbonClient 对应的ApplicationContext中对当前RibbonClient的配置bean 信息是如何获取以及注册到RibbonClient 对应的ApplicationContext中的。在 RibbonAutoConfiguration 中的:
@Autowired(required = false)private List<RibbonClientSpecification> configurations = new ArrayList<>();
这里使用的是自动注入,那么问题来了,这写 RibbonClientSpecification Bean是何时注册到spring boot 应用上下文中的呢?来到配置 RibbonAutoConfiguration 中:
@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients 在这里既然有@RibbonClients 这个注解
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,ServerIntrospectorProperties.class })
public class RibbonAutoConfiguration {。 。 。}@Configuration(proxyBeanMethods = false)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Documented
@Import(RibbonClientConfigurationRegistrar.class)
public @interface RibbonClients {这个就是RibbonClient列表, RibbonClient也是一个注解哈RibbonClient[] value() default {};默认配置类列表,指的是所有RibbonClient的配置类Class<?>[] defaultConfiguration() default {};}
我们发现了配置类上有注解@RibbonClients ,而@RibbonClients 导入了 RibbonClientConfigurationRegistrar 这个类 的作用就是解析BeanDefinition然后注册到spring boot应用上下文中。
在分析 RibbonClientConfigurationRegistrar 之前我们先来看看 如何让使用@RibbonClients 注解:这样可以精确配置某一个RibbonClient的一些属性,例如:负载均衡的规则、服务检查ping的实现,一些基础配置。
@Configuration
@RibbonClients(value = {@RibbonClient(name = "order-service", configuration = {MyRibbonClientConfiguration.OrderRibbonClientConfiguration.class}),@RibbonClient(name = "product-service",configuration = {MyRibbonClientConfiguration.OrderRibbonClientConfiguration.class})},defaultConfiguration = {MyRibbonClientConfiguration.OrderRibbonClientConfiguration.class}
)
public class MyRibbonClientConfiguration {//这个配置类可参考RibbonClientConfiguration进行编写。@Configurationpublic class OrderRibbonClientConfiguration {public static final int DEFAULT_CONNECT_TIMEOUT = 1000;public static final int DEFAULT_READ_TIMEOUT = 1000;public static final boolean DEFAULT_GZIP_PAYLOAD = true;@RibbonClientNameprivate String name = "client";@Autowiredprivate PropertiesFactory propertiesFactory;@Bean@ConditionalOnMissingBeanpublic IClientConfig ribbonClientConfig() {DefaultClientConfigImpl config = new DefaultClientConfigImpl();config.loadProperties(this.name);config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);return config;}@Bean@ConditionalOnMissingBeanpublic IRule ribbonRule(IClientConfig config) {if (this.propertiesFactory.isSet(IRule.class, name)) {return this.propertiesFactory.get(IRule.class, config, name);}ZoneAvoidanceRule rule = new ZoneAvoidanceRule();rule.initWithNiwsConfig(config);return rule;}@Bean@ConditionalOnMissingBeanpublic IPing ribbonPing(IClientConfig config) {if (this.propertiesFactory.isSet(IPing.class, name)) {return this.propertiesFactory.get(IPing.class, config, name);}return new DummyPing();}@Bean@ConditionalOnMissingBean@SuppressWarnings("unchecked")public ServerList<Server> ribbonServerList(IClientConfig config) {if (this.propertiesFactory.isSet(ServerList.class, name)) {return this.propertiesFactory.get(ServerList.class, config, name);}ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();serverList.initWithNiwsConfig(config);return serverList;}}
}
接下来我们分析:RibbonClientConfigurationRegistrar 的原理:
public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {1、找到配置类上的@RibbonClients 注解的所有成员变量。 例如@RibbonClients 的value、defaultConfigurationMap<String, Object> attrs = metadata.getAnnotationAttributes(RibbonClients.class.getName(), true);2、如果配置了value , 我们都知道value里面配置的是每一个RibbonClient的名称name + 配置类列表/*@RibbonClients(value = { @RibbonClient(name = "order-service", configuration = {OrderRibbonClientConfiguration.class}),@RibbonClient(name = "product-service",configuration = {ProductRibbonClientConfiguration.class})},*/if (attrs != null && attrs.containsKey("value")) {如果配置了那就获取到配置的列表,例如我们举例的“order-service”、“product-service” 两个客户端。AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");for (AnnotationAttributes client : clients) {循环配置的多个@RibbonClient,然后注册每一个@RibbonClient的配置类列表到spring boot
上下文中。请移步本类中的registerClientConfiguration方法。核心就是使用@RibbonClient定义的属性,
构建一个RibbonClientSpecification bean 然后注册到spring boot的上下文中。registerClientConfiguration(registry, getClientName(client),client.get("configuration"));}}如果没有配置, 也就是说只打了一个@RibbonClients注解if (attrs != null && attrs.containsKey("defaultConfiguration")) {String name;如果配置了默认的配置类,也同样构建一个RibbonClientSpecification bean 然后注册到spring boot的上下文中。我们一般都没有配置@RibbonClients注解的defaultConfiguration属性,所以在此处会构建一个beanName="default." + metadata.getClassName().RibbonClientSpecification的RibbonClientSpecification的实例到springboot的上下文中,只是说 这个RibbonClientSpecification 实例的:1、name属性 = "default." + metadata.getClassName()2、configuration=空数组if (metadata.hasEnclosingClass()) {name = "default." + metadata.getEnclosingClassName();}else {name = "default." + metadata.getClassName();}registerClientConfiguration(registry, name,attrs.get("defaultConfiguration"));}如果配置类上只有@RibbonClient 注解,也使用@RibbonClient定义的属性,构建一个
RibbonClientSpecification bean 然后注册到spring boot的上下文中。Map<String, Object> client = metadata.getAnnotationAttributes(RibbonClient.class.getName(), true);String name = getClientName(client);if (name != null) {registerClientConfiguration(registry, name, client.get("configuration"));}}private String getClientName(Map<String, Object> client) {if (client == null) {return null;}String value = (String) client.get("value");if (!StringUtils.hasText(value)) {value = (String) client.get("name");}if (StringUtils.hasText(value)) {return value;}throw new IllegalStateException("Either 'name' or 'value' must be provided in @RibbonClient");}private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,Object configuration) {在上面传入的参数是 registry bean定义注册中心,name就是@RibbonClient的name,例如:“order-service”configuration 就是@RibbonClient的configuration,例如:{ProductRibbonClientConfiguration.class}构建一个BeanDefinition,其beanClass = RibbonClientSpecification.class , 哈哈 答案有了吧。BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RibbonClientSpecification.class);builder.addConstructorArgValue(name);builder.addConstructorArgValue(configuration);设置将要被注册的RibbonClientSpecification bean 的 name = name +
".RibbonClientSpecification" , 例如:order-service.RibbonClientSpecification。registry.registerBeanDefinition(name + ".RibbonClientSpecification",builder.getBeanDefinition());}}
模型图如下:
使用方式:
@Configuration@RibbonClients(value = {@RibbonClient(name = "order-service", configuration{OrderRibbonClientConfiguration.class}),@RibbonClient(name = "product-service",configuration = {ProductRibbonClientConfiguration.class})},defaultConfiguration = {DefaultRibbonClientConfiguration.class})public class MyRibbonClientConfiguration {}
spring boot 应用上下文中的 RibbonClientSpecification 情况 :
使用方式:使用默认的RibbonClients
@Configuration@RibbonClients。 。 。public class RibbonAutoConfiguration {。 。 。。 。 。}
spring boot 上下文中RibbonClientSpecification 情况 :
在使用默认的方式@RibbonClients 的话,在RibbonClient的ApplicationContext中都会使用默认的配置类 RibbonClientConfiguration 这个是在构建 SpringClientFactory 实例的时候 设置的,源码如下:
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {static final String NAMESPACE = "ribbon";public SpringClientFactory() {就是这里设置的。super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");}。 。 。 。 。 。
}
既然是这样,那么我们来一探究竟 默认的 RibbonClient 配置类:RibbonClientConfiguration 我们关注核心的东西就好,详细的东西自己去看吧:
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
// Order is important here, last should be the default, first should be optional
// see
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class,RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class })
public class RibbonClientConfiguration {。 。 。当前的作用域就是当前的RibbonClient的ApplicationContext中,因此 @RibbonClientName =
@Value("${ribbon.client.name}"), ribbon.client.name会在创建ApplicationContext实例的时候设置
到上下文的配置列表中的,因此此处可以获取到,例如:order-service@RibbonClientNameprivate String name = "client";// TODO: maybe re-instate autowired load balancers: identified by name they could be// associated with ribbon clients@Autowiredprivate PropertiesFactory propertiesFactory;客户端配置Bena定义@Bean@ConditionalOnMissingBeanpublic IClientConfig ribbonClientConfig() {DefaultClientConfigImpl config = new DefaultClientConfigImpl();config.loadProperties(this.name);config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);return config;}Zone 的 选者规则默认是随机,Zone指的服务注册的区域,如果有多个,那就随机选择。
ZoneAvoidanceRule实现了PredicateBasedRule,PredicateBasedRule里面默认使用轮询来选择服务提供
者实例。@Bean@ConditionalOnMissingBeanpublic IRule ribbonRule(IClientConfig config) {if (this.propertiesFactory.isSet(IRule.class, name)) {return this.propertiesFactory.get(IRule.class, config, name);}ZoneAvoidanceRule rule = new ZoneAvoidanceRule();rule.initWithNiwsConfig(config);return rule;}检查服务状态Ping 使用DummyPing, 不管啥都是激活状态@Bean@ConditionalOnMissingBeanpublic IPing ribbonPing(IClientConfig config) {if (this.propertiesFactory.isSet(IPing.class, name)) {return this.propertiesFactory.get(IPing.class, config, name);}return new DummyPing();}服务列表bean, 负载通过手段获取服务提供者列表,默认使用ConfigurationBasedServerList 表示从
配置文件中获取服务列表,如果集成了eureka-client的话,会使用DomainExtractingServerList 中的
list属性类型是DiscoveryEnabledNIWSServerList的服务获取器从eureka-server上获取服务提供者列表。@Bean@ConditionalOnMissingBean@SuppressWarnings("unchecked")public ServerList<Server> ribbonServerList(IClientConfig config) {if (this.propertiesFactory.isSet(ServerList.class, name)) {return this.propertiesFactory.get(ServerList.class, config, name);}ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();serverList.initWithNiwsConfig(config);return serverList;}服务列表更新器,用于动态更新服务列表,默认会配置一个PollingServerListUpdater,默认30秒会使用ServerListImpl进行服务列表刷新。@Bean@ConditionalOnMissingBeanpublic ServerListUpdater ribbonServerListUpdater(IClientConfig config) {return new PollingServerListUpdater(config);}负载均衡器,默认使用 ZoneAwareLoadBalancer, 那么我们的第一个方法: ILoadBalancer loadBalancer = getLoadBalancer(serviceId); 获取的就是这个Bean。@Bean@ConditionalOnMissingBeanpublic ILoadBalancer ribbonLoadBalancer(IClientConfig config,ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,IRule rule, IPing ping, ServerListUpdater serverListUpdater) {if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {return this.propertiesFactory.get(ILoadBalancer.class, config, name);}return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,serverListFilter, serverListUpdater);}@Bean@ConditionalOnMissingBean@SuppressWarnings("unchecked")public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) {if (this.propertiesFactory.isSet(ServerListFilter.class, name)) {return this.propertiesFactory.get(ServerListFilter.class, config, name);}ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();filter.initWithNiwsConfig(config);return filter;}@Bean@ConditionalOnMissingBeanpublic RibbonLoadBalancerContext ribbonLoadBalancerContext(ILoadBalancer loadBalancer,IClientConfig config, RetryHandler retryHandler) {return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler);}。 。 。}
接下来我们来看看是如何构建 ZoneAwareLoadBalancer 这个实例的:
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,serverListFilter, serverListUpdater);public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList<T> serverList, ServerListFilter<T> filter, ServerListUpdater serverListUpdater) {super(clientConfig, rule, ping, serverList, filter, serverListUpdater);}public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList<T> serverList, ServerListFilter<T> filter, ServerListUpdater serverListUpdater) {super(clientConfig, rule, ping);this.isSecure = false;this.useTunnel = false;this.serverListUpdateInProgress = new AtomicBoolean(false);设置负载均衡器的更新提供者操作this.updateAction = new UpdateAction() {public void doUpdate() {DynamicServerListLoadBalancer.this.updateListOfServers();}};设置负载均衡器的服务列表实现类,例如ConfigurationBasedServerList实例this.serverListImpl = serverList; this.filter = filter;设置负载均衡器的 服务列表更新器this.serverListUpdater = serverListUpdater;if (filter instanceof AbstractServerListFilter) {((AbstractServerListFilter)filter).setLoadBalancerStats(this.getLoadBalancerStats());}发起rest请求进行服务列表的初始化this.restOfInit(clientConfig);}void restOfInit(IClientConfig clientConfig) {boolean primeConnection = this.isEnablePrimingConnections();this.setEnablePrimingConnections(false);启动服务提供者更新器,在这里就会使用后台任务 调用 serverListImpl 进行服务的获取,然后跟行本地缓存。this.enableAndInitLearnNewServersFeature();先进行手动的服务获取 + 更新,因为服务提供者更新器是后台的任务处理,有间隔时间,所以此处需要收到获取 + 更新一次。this.updateListOfServers();if (primeConnection && this.getPrimeConnections() != null) {this.getPrimeConnections().primeConnections(this.getReachableServers());}this.setEnablePrimingConnections(primeConnection);LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());}public void enableAndInitLearnNewServersFeature() {LOGGER.info("Using serverListUpdater {}", this.serverListUpdater.getClass().getSimpleName());this.serverListUpdater.start(this.updateAction);}@VisibleForTestingpublic void updateListOfServers() {List<T> servers = new ArrayList();if (this.serverListImpl != null) {使用服务列表实现类,例如ConfigurationBasedServerList实例获取服务提供者列表。servers = this.serverListImpl.getUpdatedListOfServers();LOGGER.debug("List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);if (this.filter != null) {servers = this.filter.getFilteredListOfServers((List)servers);LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);}}将获取到的服务提供者最新列表设置到 负载均衡器中的 allServerList中,提供后续操作进行服务的选择。this.updateAllServerList((List)servers);}
是不是有答案了: 1、ILoadBalancer loadBalancer = getLoadBalancer(serviceId); 这里获取的ILoadBalancer 就是 RibbonClientConfiguration 中的如下的Bean:
负载均衡器,默认使用 ZoneAwareLoadBalancer, 那么我们的第一个方法: ILoadBalancer loadBalancer = getLoadBalancer(serviceId); 获取的就是这个Bean。@Bean@ConditionalOnMissingBeanpublic ILoadBalancer ribbonLoadBalancer(IClientConfig config,ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,IRule rule, IPing ping, ServerListUpdater serverListUpdater) {if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {return this.propertiesFactory.get(ILoadBalancer.class, config, name);}return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,serverListFilter, serverListUpdater);}
那么顺利成章第二个方法 2、Server server = getServer(loadBalancer, hint); 获取的逻辑就来到了:
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {if (loadBalancer == null) {return null;}// Use 'default' on a null hint, or just pass it on?这里的loadBalancer = new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,serverListFilter, serverListUpdater);return loadBalancer.chooseServer(hint != null ? hint : "default");}
来到 ZoneAwareLoadBalancer.chooseServer(Object key) :
public Server chooseServer(Object key) {如果开启了zone 是有多个,先进行zone的选择。否则调用父类的super.chooseServer(key)进行服务选择。if (ENABLED.get() && this.getLoadBalancerStats().getAvailableZones().size() > 1) {Server server = null;try {LoadBalancerStats lbStats = this.getLoadBalancerStats();Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);logger.debug("Zone snapshots: {}", zoneSnapshot);if (this.triggeringLoad == null) {this.triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty("ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2D);}if (this.triggeringBlackoutPercentage == null) {this.triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty("ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999D);}Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, this.triggeringLoad.get(), this.triggeringBlackoutPercentage.get());logger.debug("Available zones: {}", availableZones);if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) {随机选择一个ZoneString zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);logger.debug("Zone chosen: {}", zone);if (zone != null) {使用Zone获取一个负载均衡器,获取到的是BaseLoadBalancerBaseLoadBalancer zoneLoadBalancer = this.getLoadBalancer(zone);使用获取到的BaseLoadBalancer 去选择服务。server = zoneLoadBalancer.chooseServer(key);}}} catch (Exception var8) {logger.error("Error choosing server using zone aware logic for load balancer={}", this.name, var8);}if (server != null) {return server;} else {logger.debug("Zone avoidance logic is not invoked.");return super.chooseServer(key);}} else {调用父类 BaseLoadBalancer 的 chooseServer(Object key) 方法进行服务选择。logger.debug("Zone aware logic disabled or there is only one zone");return super.chooseServer(key);}}
来到 BaseLoadBalancer 的 chooseServer(Object key) key = serviceId :
public Server chooseServer(Object key) {if (this.counter == null) {this.counter = this.createCounter();}this.counter.increment();if (this.rule == null) {return null;} else {try {使用当前的规则选择服务,这里的规则就是配置中的ZoneAvoidanceRule,
ZoneAvoidanceRule实现了PredicateBasedRule,因此来到PredicateBasedRule的choose(Object key)return this.rule.choose(key);} catch (Exception var3) {logger.warn("LoadBalancer [{}]: Error choosing server for key {}", new Object[]{this.name, key, var3});return null;}}}
PredicateBasedRule.choose(Object key) :
public Server choose(Object key) {先获取负载均衡器ILoadBalancer lb = this.getLoadBalancer();使用负载均衡器获取所有的服务提供者,然后轮询选择一个服务,这就是如果不配置负载均衡的规则,那么默认就会使用轮询的方式来获取服务选者服务提供者。Optional<Server> server = this.getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);return server.isPresent() ? (Server)server.get() : null;}
好了 ribbon 的整体分析就结束,关于eureka-client默认就集成ribbon的方式,我们上面也提到了一点,那就是 DomainExtractingServerList 服务列表实现类中的 DiscoveryEnabledNIWSServerList 这个 DiscoveryEnabledNIWSServerList 的 实现的 getUpdatedListOfServers() 方法会构建一个 EurekaClient 客户端进行服务列表的获取,然后更新 负载均衡器 中的服务提供者缓存。