先上源码的总结图,此图会在博客的后半段着重讲解:
假设我有多个微服务应用,名称都叫:SERVICE-EUREKA,当我执行RPC调用时:
restTemplate.getForObject("http://SERVICE-EUREKA/hello?name=" + name, String.class)
Ribbon的作用就是从当前容器中获取 “SERVICE-EUREKA” 对应的Eureka的服务信息,然后根据一定的规则从中选择一个服务,然后将 “SERVICE-EUREKA” 替换为所选择服务的 IP 地址,然后发起HTTP RPC 调用。
从Ribbon的功能上看挺简单的,接下来简要的梳理下SpringCloud整合Ribbon的源码。
SpringCloud-commons依赖默认引入LoadBalancerAutoConfiguration配置:
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@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);
}
}
}
};
}
......
}
其对所有标识了@LoadBalanced的RestTemplate的Bean做自定义拓展,这样就能自定义拓展组件对Http请求做相应拓展。
Eureka默认整合Ribbon,在其Jar包下的spring.factories文件内,引入 RibbonEurekaAutoConfiguration 配置类:
......
@RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class)
public class RibbonEurekaAutoConfiguration {
......
}
上述配置类主要是通过@RibbonClients 引入了一个默认配置类,会将这个配置类注册为一个RibbonClientSpecification类型的Bean,EurekaRibbonClientConfiguration 这个类作为此Bean的构造函数的一个参数:
public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
......
private void registerClientConfiguration(BeanDefinitionRegistry registry,
Object name, Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(RibbonClientSpecification.class);
builder.addConstructorArgValue(name); // default.RibbonClientSpecification类全名
builder.addConstructorArgValue(configuration); // EurekaRibbonClientConfiguration 配置类
registry.registerBeanDefinition(name + ".RibbonClientSpecification",
builder.getBeanDefinition());
}
}
在neiflix-core的Jar包下的spring.factories文件内,会引入RibbonAutoConfiguration配置类,虽然此类也标识了 @RibbonClients注解,但是因为注解上没有任何有用信息,其目的引入@RibbonClient注册的解析器。虽然@RibbonClients没有属性,但也会注册相应的RibbonClientSpecification,只是构造函数没有值而已。
不过此配置类上定义了一个Bean,SpringClientFactory:
public class RibbonAutoConfiguration {
@Autowired(required = false)
private List<RibbonClientSpecification> configurations = new ArrayList<>();
@Bean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
......
@Configuration
@ConditionalOnClass(HttpRequest.class)
@ConditionalOnRibbonRestClient
protected static class RibbonClientConfig {
@Autowired
private SpringClientFactory springClientFactory;
@Bean
public RestTemplateCustomizer restTemplateCustomizer(
final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) {
return new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
restTemplate.setRequestFactory(ribbonClientHttpRequestFactory);
}
};
}
@Bean
public RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory() {
return new RibbonClientHttpRequestFactory(this.springClientFactory);
}
}
}
从上述三个类的代码上看,主要是定义了一个SpringClientFactory 的Bean,将刚刚说明的两个配置类填充入此Bean。然后通过SpringClientFactory 定义另外两个Bean:
- RibbonClientHttpRequestFactory : Http请求工厂,作用是通过URL和Method生成HttpRequest对象。将此工厂通过RestTemplate的自定义拓展器注入到所有标识了@LoadBalanced的RestTemplate的Bean中
- RibbonLoadBalancerClient :负载均衡客户端,此Bean会被注入一个HttpRequest的拦截器中,从服务中选择一个服务,使用其IP地址替换之前生成的HttpRequest的服务名称。
Ribbon的配置将在以上两个步骤起作用,配置信息都存放在SpringClientFactory 中。SpringClientFactory 是SpringCloud拓展NamedContextFactory,用来整合Ribbon的一个容器工厂类。
下面接着看SpringClientFactory 的实例化过程:
// Spring内置客户端工厂
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
static final String NAMESPACE = "ribbon";
public SpringClientFactory() {
super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
}
......
}
// 命名的上下文工厂,可以理解为可命名的内置容器工厂
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements DisposableBean, ApplicationContextAware {
private Class<?> defaultConfigType; // 默认配置类
private final String propertySourceName;
private final String propertyName;
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>(); // 内置容器集合,key是应用名称
private Map<String, C> configurations = new ConcurrentHashMap<>(); // 可用配置类,key是应用名称或者default开头
public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
String propertyName) {
this.defaultConfigType = defaultConfigType;
this.propertySourceName = propertySourceName;
this.propertyName = propertyName;
}
// 将RibbonClientSpecification 的配置按 name > class 的形式保存
public void setConfigurations(List<C> configurations) {
for (C client : configurations) {
this.configurations.put(client.getName(), client);
}
}
}
在定义SpringClientFactory 的Bean过程中,将 RibbonClientConfiguration 作为内置容器的默认配置,RibbonAutoConfiguration和EurekaRibbonClientConfiguration作为可用配置,这两个配置的key都是default . 开头的,但RibbonAutoConfiguration未指定配置类,会被忽略。当然也可能有自定义的配置,比如用 @RibbonClient 来引入的配置信息,也会被放入可用配置集合中,key是注解上指定的应用名称。
接下来我们看Ribbon调用SpringClientFactory 配置的场所:
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost(); // 获取服务名称, 也就是最开始的 "SERVICE-EUREKA"
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
}
Ribbon的负载均衡客户端会被注入一个拦截器,此拦截器拦截RestTemplate发送的请求。
public class RibbonLoadBalancerClient implements LoadBalancerClient {
private SpringClientFactory clientFactory;
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
ILoadBalancer loadBalancer = getLoadBalancer(serviceId); // 根据服务名称获取负载均衡器
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);
}
// 从SpringClientFactory 里获取指定服务名称对应的负载均衡器
protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.clientFactory.getLoadBalancer(serviceId);
}
// 从SpringClientFactory 里获取指定服务名称对应的服务内省器
private ServerIntrospector serverIntrospector(String serviceId) {
ServerIntrospector serverIntrospector = this.clientFactory.getInstance(serviceId,
ServerIntrospector.class);
if (serverIntrospector == null) {
serverIntrospector = new DefaultServerIntrospector();
}
return serverIntrospector;
}
}
当然 RibbonLoadBalancerClient 里还有几个从SpringClientFactory 获取指定组件的方法,这里就不一一列举了。RibbonClientHttpRequestFactory里的从SpringClientFactory 获取指定组件的方法也类似,不再列举。
下面我们就以getLoadBalancer(serviceId) 方法为例,看看SpringClientFactory 获取指定服务的特定组件的方法,是如何关联到之前定义的Ribbon配置的:
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
/** 获取指定服务的负载均衡器
* Get the load balancer associated with the name.
* @throws RuntimeException if any error occurs
*/
public ILoadBalancer getLoadBalancer(String name) {
return getInstance(name, ILoadBalancer.class);
}
@Override
public <C> C getInstance(String name, Class<C> type) {
C instance = super.getInstance(name, type); // 尝试从父类获取
if (instance != null) {
return instance;
}
// 不存在,那么获取IClientConfig,然后通过此类来实例化
IClientConfig config = getInstance(name, IClientConfig.class);
return instantiateWithConfig(getContext(name), type, config);
}
}
我们先看 super.getInstance(name, type) 的实现逻辑:
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements DisposableBean, ApplicationContextAware {
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>(); // 内置容器集合
private Map<String, C> configurations = new ConcurrentHashMap<>(); // 所有配置类,value类型为:RibbonClientSpecification
private Class<?> defaultConfigType; // 默认配置类
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;
}
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);
}
// 创建指定服务名称对应的内置容器
protected AnnotationConfigApplicationContext createContext(String name) {
// 创建容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 如果配置类上含有指定应用名称的键值对,那么将此 RibbonClientSpecification 的 configuration 类都注册到内置容器中
// 可通过@RibbonClient将自定义Ribbon服务配置引入此容器中
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
// 配置类的key 是否是 "default." 开头的,如果是,那么也注册到服务对应的内置容器中
// EurekaRibbonClientConfiguration 就是默认的配置,在此步骤注册
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
// 注册默认的配置和环境变量替换器 , RibbonClientConfiguration 就是在此步骤注册
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType);
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 beans
context.setParent(this.parent);
}
// 刷新容器,实例化所有Bean
context.refresh();
return context;
}
}
上述代码最关键的就是创建服务对应的内置容器,三种配置类按照顺序注册入容器中。注意:他们注册的顺序是有实际含义的!!!
Ribbon默认的两个配置类里,他们定义的Bean的方法上大多数都标识了 @ConditionalOnMissingBean 注解,也就是说只有当前容器内还没有定义此类型的Bean时才会生效:
// Eureka整合Ribbon的配置
public class EurekaRibbonClientConfiguration {
@Autowired
private PropertiesFactory propertiesFactory;
......
@Bean
@ConditionalOnMissingBean
public ServerList<?> ribbonServerList(IClientConfig config, Provider<EurekaClient> eurekaClientProvider) {
if (this.propertiesFactory.isSet(ServerList.class, serviceId)) {
return this.propertiesFactory.get(ServerList.class, config, serviceId);
}
DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(
config, eurekaClientProvider);
DomainExtractingServerList serverList = new DomainExtractingServerList(
discoveryServerList, config, this.approximateZoneFromHostname);
return serverList;
}
}
// Ribbon的原生配置
public class RibbonClientConfiguration {
@Autowired
private PropertiesFactory propertiesFactory;
......
@Bean
@ConditionalOnMissingBean
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;
}
}
就像上述对于ServerList< ? > 类型的Bean的定义,两个配置类都定义了,但是因为他们都标识了@ConditionalOnMissingBean 注解,也就是说哪个配置类先加载,就用哪个配置类的Bean。
我们在看服务对应的内置容器的初始化过程源码,他们先加载服务名称对应的配置类,然后加载 “default .” 开头的配置类,最后加载默认的配置类。也就是我们通过 @RibbonClient 引入的配置类最先加载,然后加载后续的配置。
如果我们通过@RibbonClient注解的方式引入一个自定义配置类:
@RibbonClient(value = "SERVICE-EUREKA", configuration = {DefaultRibbonClientConfig.class})
@Configuration
public class DefaultRibbonClientConfig {
/**
* 自定义负载均衡准则
*
* @return
*/
@Bean
public IRule ribbonRule() {
return new RoundRobinRule();
}
}
那么我们就自定义了IRule 这个类型的Bean,然后将此Bean引入了内置容器中,对服务名称为 “SERVICE-EUREKA” 的服务其作用。
最好不要将此配置类加载到Spring主容器中,因为其仅仅对某一个服务起作用。这样我们可以对不同的服务定制不同的配置,Bean的类型和名称都相同,他们都存在于各自服务名称对应的子容器中,互不干扰。
如果指定的组件Bean不在自定义配置类,或者说没有自定义配置类,那么接着加载 “default.”开头的配置类: EurekaRibbonClientConfiguration 。如果还不存在,那么最后加载默认的配置类:RibbonClientConfiguration。
如上述情况,两个配置类中都定义了ServerList 类型的Bean,按照加载顺序,最终会使用 EurekaRibbonClientConfiguration 定义的 ServerList 。
在实例化EurekaRibbonClientConfiguration 的ServerList 的Bean时,会先查看 PropertiesFactory 里 是否已经设置了此组件对应的数据。如果是,那么返回预先定义好的;如果不是,那么实时生成。下面我们就看看PropertiesFactory 里的组件设置源码:
public class PropertiesFactory {
@Autowired
private Environment environment;
private Map<Class, String> classToProperty = new HashMap<>();
// 初始化组件类型和key的映射关系
public PropertiesFactory() {
classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName");
classToProperty.put(IPing.class, "NFLoadBalancerPingClassName");
classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName");
classToProperty.put(ServerList.class, "NIWSServerListClassName");
classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName");
}
// 环境里是否设置了指定服务的指定组件实现
public boolean isSet(Class clazz, String name) {
return StringUtils.hasText(getClassName(clazz, name));
}
// 获取指定服务的指定组件在环境里的key
public String getClassName(Class clazz, String name) {
if (this.classToProperty.containsKey(clazz)) {
String classNameProperty = this.classToProperty.get(clazz);
String className = environment.getProperty(name + "." + NAMESPACE + "." + classNameProperty);
return className;
}
return null;
}
// 获取指定组件
public <C> C get(Class<C> clazz, IClientConfig config, String name) {
String className = getClassName(clazz, name);
if (StringUtils.hasText(className)) {
try {
Class<?> toInstantiate = Class.forName(className);
return (C) instantiateWithConfig(toInstantiate, config);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Unknown class to load "+className+" for class " + clazz + " named " + name);
}
}
return null;
}
}
从上述代码上能够看出,如果在Spring的环境里配置了相应组件的键值对,比如:
SERVICE-EUREKA.ribbon.NFLoadBalancerRuleClassName = com.netflix.loadbalancer.RoundRobinRule
那么服务SERVICE-EUREKA的负载均衡准则就会使用RoundRobinRule,忽略EurekaRibbonClientConfiguration 和 RibbonClientConfiguration 的默认配置。
配置的加载和使用策略就如本篇博客最开始的那张图一样。