自定义了一个负载均衡策略,发现一个问题,服务a先调用服务b再调用服务c后,之后再调b服务404,简单的debug后发现再调b服务的时候loadbalancer里面的IRule已经变成了服务c的IRule,感觉是c服务初始化的时候把b服务的IRule覆盖了,百思不得其解。于是跟踪源码,发现了两个之前想不明白的问题,一个是LoadBalancer有IRule属性,IRule里又有LoadBalancer属性,负载均衡策略选择服务是使用的IRule里的LoadBalancer,二是每个被调用服务第一次被调用的时候会初始化一个子上下文,加载自己的bean。
之前通过在配置文件里面new IRule()定义策略,等于在父上下文里创建了单例bean,因为@ConditionalOnMissingBean注解,服务子上下文初始化的时候不会再次创建bean,所以多个服务的情况下会共用一个IRule,每次被调用服务初始化LoadBalancer都会把IRule设置成自己的,导致其他服务出现问题。
下面会通过对ribbon配置类、初始化、调用过程的源码追踪,大概的解析下ribbon的使用过程
Ribbon是springcloud提供的一个客户端负载均衡器,和RestTemplate配合使用,通过拦截器对rest请求做一个拦截处理,获取可用服务列表,根据策略选择服务调用。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-ribbonartifactId>
<version>${spring-cloud-netflix.version}version>
dependency>
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Autowired
private final RestTemplate restTemplate;
//分别调用服务service-b和服务service-c的service接口
@RequestMapping(value = "/service", method = RequestMethod.GET)
public String service() {
String b = restTemplate.getForObject("http://service-b/service", String.class);
String c = restTemplate.getForObject("http://service-c/service", String.class);
return b + ";" + c;
}
通过springboot的spi启动配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration
此类通过spi在项目启动时加载,会初始化很多重要bean
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,//之后加载loadBalancer的配置
AsyncLoadBalancerAutoConfiguration.class })
public class RibbonAutoConfiguration {
@Autowired(required = false)
private List<RibbonClientSpecification> configurations = new ArrayList<>();
@Autowired
private RibbonEagerLoadProperties ribbonEagerLoadProperties;//饥饿加载配置
@Bean
public HasFeatures ribbonFeature() {
return HasFeatures.namedFeature("Ribbon", Ribbon.class);
}
//spring创建loadbalancer和clientConfig的工厂
@Bean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
//创建ribbon负载均衡选择的客户端
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
//创建RibbonLoadBalanced重试工厂
@Bean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
@ConditionalOnMissingBean
public LoadBalancedRetryFactory loadBalancedRetryPolicyFactory(
final SpringClientFactory clientFactory) {
return new RibbonLoadBalancedRetryFactory(clientFactory);
}
//创建配置工厂
@Bean
@ConditionalOnMissingBean
public PropertiesFactory propertiesFactory() {
return new PropertiesFactory();
}
//饥饿加载初始化
//ribbon默认是在调用服务的时候初始化,此配置可以设置服务在启动时初始化
@Bean
@ConditionalOnProperty("ribbon.eager-load.enabled")
public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
return new RibbonApplicationContextInitializer(springClientFactory(),
ribbonEagerLoadProperties.getClients());
}
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration
在RibbonAutoConfiguration之后加载,设置ClientHttpRequestInterceptor拦截器到restTemplate
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
}
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
//创建拦截器
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
//设置拦截器
restTemplate.setInterceptors(list);
};
}
}
org.springframework.cloud.netflix.ribbon.PropertiesFactory
属性工厂,ILoadBalancer、IRule等设置了映射字符串,这些类的对象通过get()方法创建,通过环境变量中配置serviceName.ribbon.NFLoadBalancerClassName可以指定调用服务的配置。get()在RibbonClientConfiguration通过@Bean注解创建bean对象时被调用
public class PropertiesFactory {
@Autowired
private Environment environment;
private Map<Class, String> classToProperty = new HashMap<>();
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));
}
//在环境变量里找到配置的类名
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;
}
//调用SpringClientFactory根据className创建对象
@SuppressWarnings("unchecked")
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) SpringClientFactory.instantiateWithConfig(toInstantiate,
config);
}
catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Unknown class to load " + className
+ " for class " + clazz + " named " + name);
}
}
return null;
}
}
org.springframework.cloud.context.named.NamedContextFactory
此类是springCloud提供的一个非常重要的工厂类,通常在spring项目中,只有一个springApplicationContext,但是在微服务体系中,一个服务中会调用多个服务,每个被调用服务都有自己的ApplicationContext,它们的父context为当前服务的context。获取bean不再是context.getBean(classType),而是context.getInstanse(serviceName,classType)。ribbon配置类加载SpringClientFactory时会调用构造方法,config为RibbonClientConfiguration类
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements DisposableBean, ApplicationContextAware {
private final String propertySourceName;
private final String propertyName;
//被调用服务的spring上下文
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
private Map<String, C> configurations = new ConcurrentHashMap<>();
private ApplicationContext parent;
private Class<?> defaultConfigType;
//构造器,子类SpringClientFactory创建的时候调用,defaultConfigType为RibbonClientConfiguration
public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
String propertyName) {
this.defaultConfigType = defaultConfigType;
this.propertySourceName = propertySourceName;
this.propertyName = propertyName;
}
@Override
public void setApplicationContext(ApplicationContext parent) throws BeansException {
this.parent = parent;//通过ApplicationContextAware得到父context并设置
}
public void setConfigurations(List<C> configurations) {
for (C client : configurations) {//设置被调用服务的configuration
this.configurations.put(client.getName(), client);
}
}
@Override
public void destroy() {//遍历销毁
Collection<AnnotationConfigApplicationContext> values = this.contexts.values();
for (AnnotationConfigApplicationContext context : values) {
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);
}
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
if (this.configurations.containsKey(name)) {//注册配置类
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
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);
// jdk11 issue
// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
context.setClassLoader(this.parent.getClassLoader());
}
context.setDisplayName(generateDisplayName(name));
context.refresh();//刷新context,初始化bean
return context;
}
//从context中获取实例
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;
}
org.springframework.cloud.netflix.ribbon.SpringClientFactory
此类是基于NamedContextFactory封装的工厂类,用来获取ILoadBalancer和IClientConfig
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
//固定值ribbon命名空间
static final String NAMESPACE = "ribbon";
public SpringClientFactory() {
//调用父类NamedContextFactory构造器设置属性
//设置配置类为RibbonClientConfiguration
super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
}
/**
* 获取rest客户端,restTemplate拦截器设置请求时创建,用来远程调用服务接口
*/
public <C extends IClient<?, ?>> C getClient(String name, Class<C> clientClass) {
return getInstance(name, clientClass);
}
/**
* 通过服务名获取负载均衡器实例,通过这个按策略选择服务调用,restTemplate拦截器设置请求时创建
*/
public ILoadBalancer getLoadBalancer(String name) {
return getInstance(name, ILoadBalancer.class);
}
/**
* 获取rest客户端配置,restTemplate拦截器设置请求时创建,用来远程调用服务接口
*/
public IClientConfig getClientConfig(String name) {
return getInstance(name, IClientConfig.class);
}
static <C> C instantiateWithConfig(Class<C> clazz, IClientConfig config) {
return instantiateWithConfig(null, clazz, config);
}
/**
* 通过服务名和类型获取实例,每个服务都有自己的applicationContext
*/
@Override
public <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);
}
/**
* 通过服务名获取自己的applicationContext
*/
@Override
protected AnnotationConfigApplicationContext getContext(String name) {
return super.getContext(name);
}
}
org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration
每个被调用服务加载配置刷新上下文的时候调用,创建ribbon客户端的各个bean,会先通过PropertiesFactory找环境变量中的配置创建对应类型的组件bean,没有则调无参构造器创建
@Configuration
@EnableConfigurationProperties
@Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class,
RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class })
public class RibbonClientConfiguration {
/**
* Ribbon client default connect timeout.
*/
public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
/**
* Ribbon client default read timeout.
*/
public static final int DEFAULT_READ_TIMEOUT = 1000;
/**
* Ribbon client default Gzip Payload flag.
*/
public static final boolean DEFAULT_GZIP_PAYLOAD = true;
@RibbonClientName
private String name = "client";
// TODO: maybe re-instate autowired load balancers: identified by name they could be
// associated with ribbon clients
@Autowired
private PropertiesFactory propertiesFactory;
@Bean
@ConditionalOnMissingBean
public 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
@ConditionalOnMissingBean
public 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
@ConditionalOnMissingBean
public 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;
}
@Bean
@ConditionalOnMissingBean
public 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;
}
org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor
http拦截器,restTemplate请求的时候会执行ClientHttpRequestInterceptor的intercept方法,上述配置类会构造此拦截器并设置到restTemplate中
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer,
LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();//从request中解析到uri
String serviceName = originalUri.getHost();//从uri中获取服务名
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName,//执行过滤器
this.requestFactory.createRequest(request, body, execution)//创建请求);
}
}
总结一下,初始化过程如下:
先介绍几个调用过程中的核心类
顶层接口ILoadBalancer提供了添加、选择、获取、标记下线服务的方法
通过RibbonClientConfiguration可知默认会使用ZoneAwareLoadBalancer,创建时会一级级的构造父类,其祖父类BaseLoadBalancer初始化的时候会设置Rule和Ping组件。
顶层接口IRule提供了选择服务、设置和获取负载均衡器的方法
下面举例介绍下轮询的Rule
public class RoundRobinRule extends AbstractLoadBalancerRule {
private AtomicInteger nextServerCyclicCounter;
private static final boolean AVAILABLE_ONLY_SERVERS = true;
private static final boolean ALL_SERVERS = false;
private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);
public RoundRobinRule() {
nextServerCyclicCounter = new AtomicInteger(0);
}
public RoundRobinRule(ILoadBalancer lb) {
this();
setLoadBalancer(lb);//父类AbstractLoadBalancerRule设置属性lb
}
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
}
Server server = null;
int count = 0;
while (server == null && count++ < 10) {
List<Server> reachableServers = lb.getReachableServers();//获取可用服务
List<Server> allServers = lb.getAllServers();//获取所有服务
int upCount = reachableServers.size();
int serverCount = allServers.size();
if ((upCount == 0) || (serverCount == 0)) {
log.warn("No up servers available from load balancer: " + lb);
return null;
}
int nextServerIndex = incrementAndGetModulo(serverCount);//关键轮询方法
server = allServers.get(nextServerIndex);//获取服务
if (server == null) {
/* Transient. */
Thread.yield();
continue;
}
if (server.isAlive() && (server.isReadyToServe())) {
return (server);
}
// Next.
server = null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: "
+ lb);
}
return server;
}
/**
* Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
*
* @param modulo The modulo to bound the value of the counter.
* @return The next value.
*/
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextServerCyclicCounter.get();//得到当前index
int next = (current + 1) % modulo;//加1后对服务总数取模
if (nextServerCyclicCounter.compareAndSet(current, next))//通过cas更新当前index为下个服务,成功则返回,失败则开启下次循环
return next;
}
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
可见lb中有Irule属性,Irule有会存lb属性,最后调用的时候直接调用IRule的choose方法选择服务
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();//获取uri
String serviceName = originalUri.getHost();//获取服务名
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
//执行请求
return this.loadBalancer.execute(serviceName,
//构造loadbalancerrequest
this.requestFactory.createRequest(request, body, execution));
}
public LoadBalancerRequest<ClientHttpResponse> createRequest(
final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) {
return instance -> {
HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance,
this.loadBalancer);
if (this.transformers != null) {
for (LoadBalancerRequestTransformer transformer : this.transformers) {
serviceRequest = transformer.transformRequest(serviceRequest,
instance);
}
}
return execution.execute(serviceRequest, body);
};
}
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
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);//执行request请求返回结果
}
public <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);
}
RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId);
RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
try {
T returnVal = request.apply(serviceInstance);
statsRecorder.recordStats(returnVal);
return returnVal;
}
// catch IOException and rethrow so RestTemplate behaves correctly
catch (IOException ex) {
statsRecorder.recordStats(ex);
throw ex;
}
catch (Exception ex) {
statsRecorder.recordStats(ex);
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
public class AsyncLoadBalancerInterceptor implements AsyncClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
public AsyncLoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
this.loadBalancer = loadBalancer;
}
public ListenableFuture<ClientHttpResponse> intercept(final HttpRequest request, final byte[] body, final AsyncClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
return (ListenableFuture)this.loadBalancer.execute(serviceName, new LoadBalancerRequest<ListenableFuture<ClientHttpResponse>>() {
//上面apply接口的实现,会执行上述第2步最后的执行
public ListenableFuture<ClientHttpResponse> apply(final ServiceInstance instance) throws Exception {
HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, AsyncLoadBalancerInterceptor.this.loadBalancer);
return execution.executeAsync(serviceRequest, body);
}
});
}
}