1. 什么是负载均衡?
负载均衡是从多个服务中根据某个策略选择一个进行访问,常见的负载均衡分为两种
-
客户端负载均衡
:即在客户端就进行负载均衡算法分配。例如spring cloud中的
ribbon
,客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择 一个服务器,然后进行访问 -
服务端负载均衡
:在消费者和服务提供方中间使用独立的代理方式进行负载。例如
Nginx
,先发送请求,然后通过
Nginx
的负载均衡算法,在多个服务器之间选择一 个进行访问!
常见的负载均衡算法:
-
随机
:通过随机选择服务进行执行,一般这种方式使用较少; -
轮询
:请求来之后排队处理,轮着来 -
加权轮询
:通过对服务器性能的分型,给高配置,低负载的服务器分配更高的权重,均衡各个 服务器的压力; -
一致性hash
:通过客户端请求的地址的HASH值取模映射进行服务器调度。 -
最少并发
:将请求分配到当前压力最小的服务器上
2. Ribbon的使用
Ribbon属于netflix的产品,依赖如下
<!--添加ribbon的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
但 spring-cloud 体系下的大多数产品都整合和ribbon,如服务发现
nacos-discovery
,RPC调用
feign
组件等等,所以,使用时可以不用再引入
ribbon
依赖
使用Ribbon时只需添加
@LoadBalanced
注解即可,代表当前请求拥有了负载均衡的能力
①:为
RestTemplate
添加
@LoadBalanced
注解
@Configuration
public class RestConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
②:使用
RestTemplate
进行远程调用,此次调用有负载均衡效果!
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/findOrderByUserId/{id}")
public R findOrderByUserId(@PathVariable("id") Integer id) {
String url = "http://order/findOrderByUserId/"+id;
R result = restTemplate.getForObject(url,R.class);
return result;
}
①:自定义负载均衡策略
自定义负载均衡策略方式有多种
-
实现
IRule
接口 -
或者继承
AbstractLoadBalancerRule
类
实现基于
Nacos
权重的负载均衡策略:
nacos
中权重越大的实例请求频次越高!
//继承 AbstractLoadBalancerRule 类
@Slf4j
public class NacosRandomWithWeightRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public Server choose(Object key) {
//获取负载均衡器
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
String serviceName = loadBalancer.getName();
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
try {
//nacos基于权重的算法
Instance instance = namingService.selectOneHealthyInstance(serviceName);
return new NacosServer(instance);
} catch (NacosException e) {
log.error("获取服务实例异常:{}", e.getMessage());
e.printStackTrace();
}
return null;
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
自定义负载均衡策略的配置也有两种
-
全局配置
:当前服务调用其他微服务时,一律使用指定的负载均衡算法@Configuration public class RibbonConfig { /** * 全局配置 * 指定负载均衡策略 * @return */ @Bean public IRule ribbonRule() { // 指定使用基于`Nacos`权重的负载均衡策略:`nacos`中权重越大的实例请求频次越高! return new NacosRandomWithWeightRule(); }
-
局部配置
:当前服务调用指定的微服务时,使用对应的负载均衡算法,比如调用
order
服务使用该算法,调用其他的不使用!# 被调用的微服务名 mall-order: ribbon: # 自定义的负载均衡策略(基于随机&权重) NFLoadBalancerRuleClassName: com.china.test.ribbondemo.rule.NacosRandomWithWeightRule
②:Ribbon的饥饿加载
Ribbon
默认懒加载,意味着只有在发起调用的时候才会创建客户端。在第一次进行服务调用时会做一些初始化工作,比如:创建负载均衡器 ,如果网络情况不好,这次调用可能会超时。
可以开启饥饿加载,在项目启动时就完成初始化工作,解决第一次调用慢的问题
ribbon:
eager-load:
# 开启ribbon饥饿加载,源码对应属性配置类:RibbonEagerLoadProperties
enabled: true
# 配置mall-user使用ribbon饥饿加载,多个使用逗号分隔
clients: mall-order
开启之后,可以看到,第一次调用日志已经没有初始化工作了
3. Ribbon的负载均衡原理
①:收集带有@LoadBalanced注解的RestTemplate,并为其添加一个负载均衡拦截器
上面的使用案例中,如果不加
@LoadBalanced
注解的话,
RestTemplate
没有负载均衡功能的,为什么一个
@LoadBalanced
注解就使
RestTemplate
具有负载均衡功能了呢?下面来看一下Ribbon的负载均衡原理
Ribbon既然在springboot中使用,自然会想到springboot对Ribbon的自动配置类
RibbonAutoConfiguration
!这个自动配置类被加载的前置条件是:需要加载
LoadBalancerAutoConfiguration
类,如下所示
@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 {
而
LoadBalancerAutoConfiguration
类是属于spring-cloud-common包下的,该包是spring cloud的基础包,肯定会被加载的
进入
LoadBalancerAutoConfiguration
类中,该类中注册了几个bean,主要做了以下几件事
-
收集到所有带有
@LoadBalanced
注解的
RestTemplate
,并放入
restTemplates
集合 -
创建一个带有负载均衡功能的拦截器
LoadBalancerInterceptor
-
在容器类初始化完毕后,把所有的
RestTemplate
内部都添加上拦截器
LoadBalancerInterceptor
,当有请求经过
ribbon
,通过
restTemplate
发起调用时,会先走此拦截器,实现负载均衡
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
//Springboot会将所有带有@LoadBalanced注解的RestTemplate,都放进restTemplates这个集合中去
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
//SmartInitializingSingleton :该方法会等待所有类都初始化完毕后执行
// 拿到上面收集到的所有带有@LoadBalanced注解的RestTemplate
// 执行下面的函数式接口方法customize,把拦截器 放入每一个restTemplate中,
//当有请求经过ribbon,通过 restTemplate 发起调用时,会先走此拦截器,实现负载均衡
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
@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);
}
//这是一个函数式接口,此处实现的是接口的customize方法,定义了一个操作,操作内容如下:
// 传入一个restTemplate,并把上面的拦截器 放入restTemplate中
//当有请求经过ribbon,通过 restTemplate 发起调用时
//会先走此拦截器,实现负载均衡
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
LoadBalancerAutoConfiguration
是如何精准的收集到所有的带有
@LoadBalanced
注解的
RestTemplate
呢?点开
@LoadBalanced
注解,发现他是带有
@Qualifier
限定符的!
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier //spring的限定符
public @interface LoadBalanced {
}
@Qualifier
限定符的作用:
-
一个接口有两个实现类,当我们通过
@AutoWired
注解进行注入时,
spring
不知道应该绑定哪个实现类,从而导致报错。 -
这时就可以通过
@Qualifier
注解来解决。通过它可以标识我们需要的实现类。而
@LoadBalanced
的元注解是
@Qualifier
,所以 源码中就可以通过
@LoadBalanced
注解来限定收集所有带有
@LoadBalanced
注解的
RestTemplate
实现
②:选择负载均衡器,执行负载均衡算法(默认轮询)
上面说到请求经过ribbon的
RestTemplate
调用时,会先走其内部的拦截器
LoadBalancerInterceptor
的负载均衡逻辑。既然是走拦截器,那么就可以去看
LoadBalancerInterceptor
的
intercept()
方法,一般该方法就有负载均衡逻辑!
@Override
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);
//负载均衡器的 execute 方法
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
//获取负载均衡器 ILoadBalancer
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// 通过负载均衡选择一个服务器
Server server = getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonServer ribbonServer = new RibbonServer(serviceId, server,
isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
//向某一台服务器 发起HTTP请求
return execute(serviceId, ribbonServer, request);
}
如上,
Ribbon
通过负载均衡 选择一台机器 发起
http
请求已经执行完毕!
Ribbon负载均衡器的默认实现:ZoneAwareLoadBalancer
上面说到
getLoadBalancer(serviceId)
方法可以获取一个负载均衡器,用于执行负载均衡算法,这个负载均衡器已经在
RibbonClientConfiguration
配置类中初始化好了,获取时直接从容器中取即可
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
// 从配置类中找一个负载均衡器 ILoadBalancer
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}
// 如果没有就创建一个负载均衡器的实现 ZoneAwareLoadBalancer
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
可以看到
RibbonClientConfiguration
配置类中,默认初始化的是
ZoneAwareLoadBalancer
,它具备区域感知的能力。
在创建默认负载均衡器时(
new ZoneAwareLoadBalancer
)做了什么呢?
-
从
nacos
注册中心上立即获取最新的服务信息,保存在
ribbon
的本地服务列表中 -
使用延时定时线程池,定时从nacos上拉取最新服务地址,更新
ribbon
的本地服务列表中
进入
new ZoneAwareLoadBalancer
中:
void restOfInit(IClientConfig clientConfig) {
boolean primeConnection = this.isEnablePrimingConnections();
// turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
this.setEnablePrimingConnections(false);
//开启定时延时任务(会延后执行),定时从nacos上拉取最新服务地址,更新`ribbon`的本地服务列表中
enableAndInitLearnNewServersFeature();
//进入后立即执行,从`nacos`注册中心上立即获取最新的服务信息,保存在`ribbon`的本地服务列表中
updateListOfServers();
if (primeConnection && this.getPrimeConnections() != null) {
this.getPrimeConnections()
.primeConnections(getReachableServers());
}
this.setEnablePrimingConnections(primeConnection);
LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
}
开启定时任务方法
enableAndInitLearnNewServersFeature();
如下:
@Override
public synchronized void start(final UpdateAction updateAction) {
if (isActive.compareAndSet(false, true)) {
//开启一个线程,执行更新任务
final Runnable wrapperRunnable = new Runnable() {
@Override
public void run() {
if (!isActive.get()) {
if (scheduledFuture != null) {
scheduledFuture.cancel(true);
}
return;
}
try {
//UpdateAction 又是一个函数式接口,
//doUpdate方法需要看一下传进来的方法内容,下文展示
updateAction.doUpdate();
lastUpdated = System.currentTimeMillis();
} catch (Exception e) {
logger.warn("Failed one update cycle", e);
}
}
};
//开启定时延时任务,定时执行上面的线程
scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
wrapperRunnable,
initialDelayMs,
refreshIntervalMs,
TimeUnit.MILLISECONDS
);
} else {
logger.info("Already active, no-op");
}
}
================== 函数式接口的 updateAction.doUpdate()方法内容如下=============
protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
@Override
public void doUpdate() {
updateListOfServers();
}
};
// updateListOfServers方法如下:
@VisibleForTesting
public void updateListOfServers() {
List<T> servers = new ArrayList<T>();
if (serverListImpl != null) {
// 该方法会从对应的配置中心中取最新数据
servers = serverListImpl.getUpdatedListOfServers();
LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
if (filter != null) {
servers = filter.getFilteredListOfServers(servers);
LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
}
}
// 更新本地服务列表!
updateAllServerList(servers);
}
负载均衡器初始化时,立即从注册中心获取最新服务的方法
updateListOfServers()
,如下:
@VisibleForTesting
public void updateListOfServers() {
List<T> servers = new ArrayList<T>();
if (serverListImpl != null) {
//从注册中心上获取服务地址
servers = serverListImpl.getUpdatedListOfServers();
LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
if (filter != null) {
servers = filter.getFilteredListOfServers(servers);
LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
}
}
//更新本地服务列表!
updateAllServerList(servers);
}
Ribbon负载均衡算法的默认实现:ZoneAvoidanceRule
有了负载均衡器
ZoneAwareLoadBalancer
,接下来执行负载均衡算法即可
getServer(loadBalancer, hint);
,回顾上边第②条的代码:
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
//获取负载均衡器 ILoadBalancer
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// 通过负载均衡算法 选择一个服务器地址
Server server = getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonServer ribbonServer = new RibbonServer(serviceId, server,
isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
//向某一台服务器 发起HTTP请求
return execute(serviceId, ribbonServer, request);
}
进入负载均衡算法选择服务器的方法
getServer(loadBalancer, hint)
中
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
//这个rule就是ribbon的负载均衡算法!
//默认是 ZoneAvoidanceRule ,在没有区域的环境下,类似于轮询(RandomRule)
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
ZoneAvoidanceRule
的核心逻辑如下:使用
cas
+
死循环
轮询服务器地址
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextIndex.get();
//取模得到其中一个
int next = (current + 1) % modulo;
//cas赋值 ,返回nextIndex的机器
if (nextIndex.compareAndSet(current, next) && current < modulo)
return current;
}
}
其中,
ZoneAvoidanceRule
的初始化和负载均衡器
ZoneAwareLoadBalancer
的初始化一样,也在
RibbonClientConfiguration
配置类中完成!
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
if (this.propertiesFactory.isSet(IRule.class, name)) {
return this.propertiesFactory.get(IRule.class, config, name);
}
// 负载均衡策略的 默认实现ZoneAvoidanceRule
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}
Ribbon的负载均衡策略有如下几种
-
RandomRule
:随机策略, 随机选择一个Server。 -
RetryRule
: 重试策略 。对选定的负载均衡策略机上重试机制,在一个配置时间段内当选择Server不成功,则一直尝试使用subRule的方式选择一个可用的server。 -
RoundRobinRule
: 轮询策略 。 轮询index,选择index对应位置的Server。 -
AvailabilityFilteringRule
:可用性过滤策略 。 过滤掉一直连接失败的被标记为circuit tripped的后端Server,并过滤掉那些高并发的后端Server或者使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就是检查status里记录的各个Server的运行状态。 -
BestAvailableRule
:最低并发策略 。 选择一个最小的并发请求的Server,逐个考察Server,如果Server被tripped了,则跳过。 -
WeightedResponseTimeRule
:响应时间加权重策略。根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低。 -
ZoneAvoidanceRule
:区域权重策略。
默认的负载均衡策略
,综合判断server所在区域的性能和server的可用性,轮询选择server并且判断一个AWS Zone的运行性能是否可用,剔除不可用的Zone中的所有server。在没有区域的环境下,类似于轮询(
RandomRule
) -
NacosRule
: 同集群优先调用
4. Feign的原理
Feign和OpenFeign的区别?
-
Feign
:Feign是Netflix开发的声明式、模板化的HTTP客户端,Feign可帮助我们更加便捷、优雅地调用HTTP API。可以单独使用 -
OpenFeign
:Spring Cloud openfeign对Feign进行了 增强,使其支持
Spring MVC
注解,另外还整合了
Ribbon
和
Eureka
,从而使得Feign的使用更加方便
Feign的调用原理图
(可在每一层做扩展)
OpenFeign的常用配置项:
(对应上图,可以在配置中做扩展)
-
日志配置
:有时候我们遇到 Bug,比如接口调用失败、参数没收到等问题,或者想看看调用性能,就需要配置 Feign 的 日志了,以此让 Feign 把请求信息输出来。日志配置分为局部配置和全局配置! -
拦截器配置
:每次 feign 发起http调用之前,会去执行拦截器中的逻辑,就类似mvc中的拦截器。比如:做权限认证。 -
超时时间配置
:通过 Options 可以配置连接超时时间(默认2秒)和读取超时时间(默认5秒),注意:Feign的底层用的是
Ribbon
,但超时时间以Feign配置为准 -
客户端组件配置
:Feign 中默认使用 JDK 原生的
URLConnection
发送 HTTP 请求,我们可以集成别的组件来替换掉 URLConnection,比如 Apache HttpClient,OkHttp。 -
GZIP 压缩配置
:再配置文件中开启压缩可以有效节约网络资源,提升接口性能 -
编码器解码器配置
:Feign 中提供了自定义的编码解码器设置,同时也提供了多种编码器的实现,比如 Gson、Jaxb、Jackson。 我们可以用不同的编码解码器来处理数据的传输。如果你想传输 XML 格式的数据,可以自定义 XML 编码解 码器来实现获取使用官方提供的 Jaxb
5. OpenFeign是如何整合Ribbon的?
通过上边,我们已经知道
Ribbon
可以把微服务的 服务名 通过负载均衡策略替换成某一台机器的
IP
地址,然后通过
http
请求进行访问!如下所示:
http://
mall-order
/order/findOrderByUserId ====> http://
192.168.100.15
/order/findOrderByUserId
而
Feign
则是把参数组装到url中去,实现一个完整的RPC调用
http://mall-order/order/findOrderByUserId/
5(参数)
====> http://192.168.100.15/order/findOrderByUserId/
5(参数)
①:扫描所有@FeignClient注解,以FactoryBean的形式注册到容器中
Feign的使用需要用到
@EnableFeignClients
、
@FeignClient("gulimall-ware")
这两个注解,其中,
@EnableFeignClients
通过
@Import
向容器中添加了一个bean定义注册器,用于扫描
@FeignClient("gulimall-ware")
注解,注册bean定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
//通过`@Import`向容器中添加了一个bean定义注册器 FeignClientsRegistrar
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}
进入
FeignClientsRegistrar
的
registerBeanDefinitions
方法,查看具体注册了什么
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//注册一下默认配置
registerDefaultConfiguration(metadata, registry);
//注册所有@FeignClient注解 标注的类
registerFeignClients(metadata, registry);
}
注册逻辑中,最主要的就是把所有
@FeignClient
注解 标注的类以
FactoryBean
的形式注册到容器中
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 1. 获取一个扫描器
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
//2. 拿到所有@FeignClient注解标注的类
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}
else {
final Set<String> clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
basePackages.add(ClassUtils.getPackageName(clazz));
clientClasses.add(clazz.getCanonicalName());
}
AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
@Override
protected boolean match(ClassMetadata metadata) {
String cleaned = metadata.getClassName().replaceAll("\\$", ".");
return clientClasses.contains(cleaned);
}
};
scanner.addIncludeFilter(
new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
}
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
// 3. 把这些类注入容器
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
修改bean定义为
FactoryBean
的子类
FeignClientFactoryBean
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
//把bean定义构建成一个FactoryBean
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = getContextId(attributes);
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be
// null
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
// 注入!
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
②:RPC调用时,通过LoadBalancerFeignClient整合Ribbon,实现负载均衡调用
既然是把
@FeignClient("gulimall-ware")
注解标注的类 以
FactoryBean
的子类
FeignClientFactoryBean
的形式注入到容器,那么RPC调用时肯定是通过调用
FeignClientFactoryBean
的
getObject
方法来使用的!
@Override
public Object getObject() throws Exception {
return getTarget();
}
getTarget()
:
<T> T getTarget() {
FeignContext context = this.applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
this.url += cleanPath();
return (T) loadBalance(builder, context,
new HardCodedTarget<>(this.type, this.name, this.url));
}
loadBalance()
:
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
//获取的其实是LoadBalanceFeignClient,用于整合Ribbon的负载均衡
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
Client client = getOptional(context, Client.class)
;获取的是
Feign
的客户端实现:
整合Ribbon逻辑
:进入
LoadBalancerFeignClient
的
execute
方法中,使用
Feign
的负载均衡器向
Ribbon
发请求,已达到整合
Ribbon
的目的!
org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient # execute
@Override
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
//使用Feign的负载均衡器向Ribbon发请求,已达到整合Ribbon的目的!
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
综上所述,负载均衡的逻辑还是在
Ribbon
中,而
Feign
通过整合
Ribbon
实现了带有负载均衡的
RPC
调用!
6. 总结图