一、自定义Rule
1.1 需求
在gataway服务自定义负载均衡规则,使同一个用户的请求打在同一个服务器
1.2 错误示例
pom
依赖
<!-- springboot web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
LoadBanancerConfig.java
@Configuration
public class LoadBanancerConfig {
//自定义负载均衡
@Bean
public IRule ribbonRule() {
return new UserLoadBalancerRule();
}
//自定义过滤器(可选)
@Bean
public UserLoadBalancerClientFilter userLoadBalanceClientFilter(LoadBalancerClient client, LoadBalancerProperties properties) {
return new UserLoadBalancerClientFilter(client, properties);
}
}
UserLoadBalancerClientFilter.java
public class UserLoadBalancerClientFilter extends LoadBalancerClientFilter {
public UserLoadBalancerClientFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) {
super(loadBalancer, properties);
}
@Override
protected ServiceInstance choose(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
Long userId = UserInfo.getCurrUserId(request);
if(userId == null){
return super.choose(exchange);
}
if (this.loadBalancer instanceof RibbonLoadBalancerClient) {
RibbonLoadBalancerClient client = (RibbonLoadBalancerClient) this.loadBalancer;
String serviceId = ((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost();
//这里使用userId做为选择服务实例的key
return client.choose(serviceId, userId);
}
return super.choose(exchange);
}
}
UserLoadBalancerRule.java
public class UserLoadBalancerRule extends AbstractLoadBalancerRule {
private static Logger log = LoggerFactory.getLogger(UserLoadBalancerRule.class);
/**
* 自定义选择策略
*
* @param key userId
* @return
*/
public Server choose(Object key) {
if (key == null || "default".equals(key)) {
return this.choose(this.getLoadBalancer(), null);
} else {
return this.choose(this.getLoadBalancer(), Long.valueOf(String.valueOf(key)));
}
}
/**
* 自定义选择策略
*
* @param lb
* @param userId
* @return
*/
public Server choose(ILoadBalancer lb, Long userId) {
if (lb == null) {
return null;
} else {
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
//自定义部分代码开始
int index = 0;
//获取userId
log.info("userId:{}", userId);
if (userId == null) {
//无法获取userId,随机选择
index = chooseRandomInt(serverCount);
} else {
//根据userId选择实例
index = (int) (userId % serverCount);
}
server = upList.get(index);
log.info("server:{}", server.getId() + " " + server.getMetaInfo().getAppName());
//自定义部分代码结束
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
}
return server;
}
}
protected int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
测试
@RestController
@RequestMapping("testController")
public class TestController {
@Autowired
private RestTemplate restTemplate;
//RestTemplate开启负载均衡
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@GetMapping("test")
public String test(){
String b = restTemplate.getForObject("http://SM-SHUMIWEB//xxx", String.class);
String c = restTemplate.getForObject("http://SM-FILE//xxx", String.class);
return b + ";" + c;
}
}
1.3 出现问题
gataway服务调用服务b再调用服务c,再调用服务b报错404
org.springframework.web.client.HttpClientErrorException: 404 null
1.4 正确示例
只需更改IRule注册到容器中的方式即可
启动类 GatewayServerApplication.java
添加注解@RibbonClients
@RibbonClients(defaultConfiguration = LoadBanancerConfig.class)
@SpringBootApplication
public class GatewayServerApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayServerApplication.class, args);
}
}
★1.5 问题分析
1.在自定义的IRule中,使用的ILoadBalancer对象是IRule对象中保存的
public Server choose(Object key) {
if (key == null || "default".equals(key)) {
return this.choose(this.getLoadBalancer(), null);
} else {
return this.choose(this.getLoadBalancer(), Long.valueOf(String.valueOf(key)));
}
}
2.在RibbonClientConfiguration
类中进行各个bean的初始化,由于@ConditionalOnMissingBean
注解的作用,只有在容器中没有IRule对象时才在这里进行初始化,
但由于我们在LoadBanancerConfig
上加了@Configuration
注解,那么在项目启动时,IRule对象就注册到了Spring容器中,那么之后使用的IRule对象都是同一个对象,即在Spring容器中注册的自定义IRule bean
@Bean
public IRule ribbonRule() {
return new ResourceIpLoadBalancerRule();
}
3.SpringClientFactory默认情况下,每个eureka的serviceId(服务),都会分配自己独立的Spring的上下文,即ApplicationContext,这个上下文中包含了必要的一些bean,比如: ILoadBalancer
、ServerListFilter
等。
即每个服务都会进入该方法进行初始化。
@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);
}
//进入到BaseLoadBalancer中实例化对象
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
4.在BaseLoadBalancer
中initWithConfig
方法实例化ILoadBalancer对象,并且会对rule对象赋值
void initWithConfig(IClientConfig clientConfig, IRule rule, IPing ping, LoadBalancerStats stats) {
this.config = clientConfig;
String clientName = clientConfig.getClientName();
this.name = clientName;
int pingIntervalTime = Integer.parseInt(""
+ clientConfig.getProperty(
CommonClientConfigKey.NFLoadBalancerPingInterval,
Integer.parseInt("30")));
int maxTotalPingTime = Integer.parseInt(""
+ clientConfig.getProperty(
CommonClientConfigKey.NFLoadBalancerMaxTotalPingTime,
Integer.parseInt("2")));
setPingInterval(pingIntervalTime);
setMaxTotalPingTime(maxTotalPingTime);
//在这个方法中对rule对象赋值
setRule(rule);
setPing(ping);
setLoadBalancerStats(stats);
rule.setLoadBalancer(this);
if (ping instanceof AbstractLoadBalancerPing) {
((AbstractLoadBalancerPing) ping).setLoadBalancer(this);
}
logger.info("Client: {} instantiated a LoadBalancer: {}", name, this);
boolean enablePrimeConnections = clientConfig.get(
CommonClientConfigKey.EnablePrimeConnections, DefaultClientConfigImpl.DEFAULT_ENABLE_PRIME_CONNECTIONS);
if (enablePrimeConnections) {
this.setEnablePrimingConnections(true);
PrimeConnections primeConnections = new PrimeConnections(
this.getName(), clientConfig);
this.setPrimeConnections(primeConnections);
}
init();
}
IRule对象中保存了一份ILoadBalancer对象,rule.setLoadBalancer(this)
中的this为当前正在实例化的lb
public void setRule(IRule rule) {
if (rule != null) {
this.rule = rule;
} else {
/* default rule */
this.rule = new RoundRobinRule();
}
if (this.rule.getLoadBalancer() != this) {
//给rule中的lb赋值
this.rule.setLoadBalancer(this);
}
}
5.综上,访问b服务后再访问c服务,在实例化c服务的ILoadBalancer对象时,重新对IRule对象赋了值,这个值即为c服务的lb,
同时rule对象是Spring实例化的,有且仅有一份,第二次访问c服务就把rule中的lb对象覆盖了,
而在我们自定义的IRule中选择服务时,使用的lb是rule中保存的值,所以再进行访问b服务时,lb还是c服务的,进行choose服务时就找不到正确的service实例。
★1.6 正确示例分析
从获取ILoadBalancer实例开始分析
1.RibbonLoadBalancerClient
中,通过SpringClientFactory
工厂获取实例
private SpringClientFactory clientFactory;
protected ILoadBalancer getLoadBalancer(String serviceId) {
//获取SpringClientFactory获取ILoadBalancer实例
return this.clientFactory.getLoadBalancer(serviceId);
}
2.SpringClientFactory
中
2.1 调用方法getLoadBalancer
public ILoadBalancer getLoadBalancer(String name) {
return getInstance(name, ILoadBalancer.class);
2.2 调用方法getInstance
,该方法是NamedContextFactory
类中getInstance(String name, Class<T> type)
的重载方法。参数name是serviceId(服务),type是要获取对象的Class
@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);
3.SpringClientFactory
父类NamedContextFactory
中
3.1 调用方法getInstance(String name, Class<T> type)
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;
}
3.2 调用方法protected AnnotationConfigApplicationContext getContext(String name)
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
//第一次初始化ILoadBalancer,创建该服务的instance
this.contexts.put(name, createContext(name));
}
}
}
//如果SpringClientFactory工厂中存该服务的instance,直接获取
return this.contexts.get(name);
}
3.3 调用方法protected AnnotationConfigApplicationContext createContext(String name)
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//通过命名找到对应的configuration,完成configuration包含的bean的定义
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
//key为"default."开头的configuration,所有的子context公用,完成default.配置下的configuration包含的bean的定义。
//!!!完成自定义配置类LoadBanancerConfig的定义!!!
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));
//!!!完成bean的实例化!!!
context.refresh();
return context;
}
自定义的IRule的Bean在context.refresh()
中完成创建,每个服务都有自己独立的上下文,并且在自己的上下文中都各自创建了IRule的Bean。
4.总结
使用@RibbonClients(defaultConfiguration = LoadBanancerConfig.class)
引入自定义的配置类,不同的服务在创建自己的context上下文时,在各自的context中创建了IRule的Bean。
而不通过@RibbonClients
注解,只通过@Configuration
引入自定义的的配置类,只会在项目启动时,在Spring容器中创建IRule的Bean,而这个Bean被不同服务的context共用。
二、源码解读
2.1 源码入口
通过springboot的spi启动配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration
2.2 主要配置类
2.2.1 RibbonAutoConfiguration
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration
在项目启动时加载
@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
//加载完这个类之后加载类LoadBalancerAutoConfiguration
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
ServerIntrospectorProperties.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;
}
//创建ribbion负载均衡选择的客户端
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
//创建RibbionloadBalanced重试工厂
@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();
}
//饥饿加载初始化
//ribbion默认是在调用服务的时候初始化,此配置可以设置服务在启动时初始化
@Bean
@ConditionalOnProperty("ribbon.eager-load.enabled")
public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
return new RibbonApplicationContextInitializer(springClientFactory(),
ribbonEagerLoadProperties.getClients());
}
@Configuration
@ConditionalOnClass(HttpRequest.class)
@ConditionalOnRibbonRestClient
protected static class RibbonClientHttpRequestFactoryConfiguration {
@Autowired
private SpringClientFactory springClientFactory;
@Bean
public RestTemplateCustomizer restTemplateCustomizer(
final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) {
return restTemplate -> restTemplate
.setRequestFactory(ribbonClientHttpRequestFactory);
}
@Bean
public RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory() {
return new RibbonClientHttpRequestFactory(this.springClientFactory);
}
}
// TODO: support for autoconfiguring restemplate to use apache http client or okhttp
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnRibbonRestClientCondition.class)
@interface ConditionalOnRibbonRestClient {
}
private static class OnRibbonRestClientCondition extends AnyNestedCondition {
OnRibbonRestClientCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@Deprecated // remove in Edgware"
@ConditionalOnProperty("ribbon.http.client.enabled")
static class ZuulProperty {
}
@ConditionalOnProperty("ribbon.restclient.enabled")
static class RibbonProperty {
}
}
/**
* {@link AllNestedConditions} that checks that either multiple classes are present.
*/
static class RibbonClassesConditions extends AllNestedConditions {
RibbonClassesConditions() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnClass(IClient.class)
static class IClientPresent {
}
@ConditionalOnClass(RestTemplate.class)
static class RestTemplatePresent {
}
@ConditionalOnClass(AsyncRestTemplate.class)
static class AsyncRestTemplatePresent {
}
@ConditionalOnClass(Ribbon.class)
static class RibbonPresent {
}
}
}
2.2.2 LoadBalancerAutoConfiguration
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);
};
}
}
2.3 初始化相关
2.3.1 PropertiesFactory
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;
}
}
2.3.2 NamedContextFactory
org.springframework.cloud.context.named.NamedContextFactory
此类是springCloud提供的一个工厂类,通常在spring项目中,只有一个springApplicationContext,但是在微服务体系中,一个服务中会调用多个服务,每个被调用服务都有自己的springApplicationContext,它们的父context为当前服务的context。
获取bean不再是context.getBean(classType),而是context.getInstanse(serviceName,classType)。
ribbion配置类加载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;
}
2.3.3 SpringClientFactory
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);
}
}
2.3.4 RibbonClientConfiguration
org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration
每个被调用服务加载配置刷新上下文的时候调用,创建ribbion客户端的每个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;
}
2.3.5 LoadBalancerInterceptor
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)//创建请求);
}
}
2.3.6 初始化过程总结
1.通过spi加载RibbonAutoConfiguration,创建bean对象
SpringClientFactory(获取ribbion的loadBalancer和ClientConfig的工厂)
RibbonLoadBalancerClient(负载均衡客户端,继承LoadBalancerClient,主要有选择服务choose(serviceId)和执行请求execute(serviceId, LoadBalancerRequest request)方法)
PropertiesFactory(属性工厂,可以通过环境变量配置创建各被调用服务的组件)
2.加载LoadBalancerAutoConfiguration,创建LoadBalancerInterceptor拦截器,将上述配置的loadBalancerClient设置到里面,再将拦截器设置到restTemplate里面
3.ribbion时会从SpringClientFactory中获取需要的bean,当不存在时,首次加载时会创建被调用服务的子applicationContext,然后加载RibbonClientConfiguration时创建里面的各组件bean
2.4 调用流程
1.调用restTemplate.getForObject()
,进入restTemplate的doExecute方法,执行ClientHttpRequestInterceptor的intercept方法,进入LoadBalancerInterceptor的intercept方法。
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
2.this.requestFactory.createRequest(request, body, execution)
方法创建一个LoadBalancerRequest后续调用。
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);
};
}
3.进入到RibbonLoadBalancerClient
中的execute方法
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));
//执行request请求返回结果
return execute(serviceId, ribbonServer, request);
}
若不存在对应的ILoadBalancer实例,则进入到RibbonClientConfiguration
中初始化。
初始化顺序为:
@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
@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;
}
//创建IRule实例
@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 ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
return new PollingServerListUpdater(config);
}
//创建ILoadBalancer实例
@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
public RetryHandler retryHandler(IClientConfig config) {
return new DefaultLoadBalancerRetryHandler(config);
}
@Bean
@ConditionalOnMissingBean
public RibbonLoadBalancerContext ribbonLoadBalancerContext(
ILoadBalancer loadBalancer,IClientConfig config, RetryHandler retryHandler) {
return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler);
}
以上bean的创建都被注解@ConditionalOnMissingBean
修饰,所以第二次及之后访问就不再需要创建。
4.执行execute方法,request.apply(serviceInstance)
最终会调到第2步的execution.execute(serviceRequest, body)
方法,返回最终结果
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);
}
});
}
}
2.5 负载均衡器LoadBalance
顶层接口ILoadBalancer提供了添加、选择、获取、标记下线服务的方法
通过RibbonClientConfiguration可知默认会使用ZoneAwareLoadBalancer,创建时会一级级的构造父类,其祖父类BaseLoadBalancer初始化的时候会设置Rule和Ping组件。
2.6 负载均衡策略IRule
顶层接口IRule提供了选择服务、设置和获取负载均衡器的方法
2.6.1 未实现IRule接口
1.RibbonClientConfiguration
这个类中
@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;
}
由于注解@ConditionalOnMissingBean,因为未实现IRule接口,容器中就没有这个bean,就会执行这个方法,从而会返回一个默认的ZoneAvoidanceRule对象。
2.相同类中,执行ribbonLoadBalancer()
方法
@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);
}
//这个rule就是刚刚初始化的ZoneAvoidanceRule对象
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
3.一直跟进方法,进入return new ZoneAwareLoadBalancer<>();
并持续跟进
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.serverListImpl = serverList;
this.filter = filter;
this.serverListUpdater = serverListUpdater;
if (filter instanceof AbstractServerListFilter) {
((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
}
restOfInit(clientConfig);
}
public BaseLoadBalancer(IClientConfig config, IRule rule, IPing ping) {
//继续跟进
initWithConfig(config, rule, ping, createLoadBalancerStatsFromConfig(config));
}
void initWithConfig(IClientConfig clientConfig, IRule rule, IPing ping, LoadBalancerStats stats) {
this.config = clientConfig;
String clientName = clientConfig.getClientName();
this.name = clientName;
int pingIntervalTime = Integer.parseInt(""
+ clientConfig.getProperty(
CommonClientConfigKey.NFLoadBalancerPingInterval,
Integer.parseInt("30")));
int maxTotalPingTime = Integer.parseInt(""
+ clientConfig.getProperty(
CommonClientConfigKey.NFLoadBalancerMaxTotalPingTime,
Integer.parseInt("2")));
setPingInterval(pingIntervalTime);
setMaxTotalPingTime(maxTotalPingTime);
////继续跟进
setRule(rule);
setPing(ping);
//省略...
}
4.进入到BaseLoadBalancer
类中setRule
方法
public void setRule(IRule rule) {
if (rule != null) {
this.rule = rule;
} else {
/* default rule */
this.rule = new RoundRobinRule();
}
if (this.rule.getLoadBalancer() != this) {
this.rule.setLoadBalancer(this);
}
}
这个rule就是开始创建的ZoneAvoidanceRule
对象
2.6.2 实现IRule接口
@Configuration
public class LoadBanancerConfig {
//这里创建了IRule这个Bean
@Bean
public IRule ribbonRule() {
return new ResourceIpLoadBalancerRule();
}
}
因为@ConditionalOnMissingBean
注解的存在,并且已经存在了IRule这个Bean,就不会执行ribbonRule()
这个方法了,之后的执行流程都是一样的。
2.6.3 总结:自定义IRule实现方式
ribbion的IRule的覆盖实现方式:
-
自定义ribbonRule()方法,并注册Bean到Spring容器中
-
@ConditionalOnMissingBean注解,Spring容器中已存在IRule对象,不再执行默认赋值方法。
@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; }
2.7 @RibbonClients与NamedContextFactory
此处不是重点,不多赘述,参考文章:https://blog.csdn.net/qq_22351805/article/details/113566246
SpringCloud的子上下文,NamedContextFactory
目的是为其集合configurations中每个元素创建启动一个新的子应用上下文容器ApplicationContent。
子上下文维护自身所有的bean,可从父context依赖到父context的bean,但是父context无法依赖到子context的bean。
通过命名不同的子context,不同的子context之间可以共用configuration,也可以各自在共享的基础上自定义差异化的configuration。@RibbonClients
就是使用场景之一,针对每个要调用的服务名,每个服务名定义一个子context对象,各自维护定期更新服务列表,server选择等任务。
@RibbonClients
使用注解的方式完成Specification的实现和SpringClientFactory
创建基于此概念。