网站首页 > 博客文章 正文
Spring Cloud 系列文章:
?实现Spring Cloud Gateway 动态路由和内置过滤器[1]
?Spring Cloud Gateway和Spring WebFlux的契合点[2]
?Spring Cloud Gateway 过滤器工作流程[3]
引言
在我们上篇Spring Cloud Gateway 过滤器工作流程[4] 我们说了一些关键的过滤器,其中ReactiveLoadBalancerClientFilter就是负责关于客户端负责均衡的一个过滤器处理。
今天我们来详细看看这个过滤器是如何与Loadbalancer这个组件进行交互,从而实现客户端负载均衡
ReactiveLoadBalancerClientFilter源码分析
ReactiveLoadBalancerClientFilter是在GatewayReactiveLoadBalancerClientAutoConfiguration 配置类中进行实例化的,在实例化的时候通过构造函数注入了一个很重要的实例:LoadBalancerClientFactory。
@Bean
@ConditionalOnBean(LoadBalancerClientFactory.class)
@ConditionalOnMissingBean(ReactiveLoadBalancerClientFilter.class)
@ConditionalOnEnabledGlobalFilter
public ReactiveLoadBalancerClientFilter gatewayLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory,
GatewayLoadBalancerProperties properties) {
return new ReactiveLoadBalancerClientFilter(clientFactory, properties);
}
这里简单说下为什么会有LoadBalancerClientFactory,顾名思义,它就是一个客户端负载均衡器的工厂类。网关代理的时候会根据路由配置信息代理到下游服务,每个路由信息都会对应一到多个下游服务,而LoadBalancerClientFactory就是根据路由信息,产生对应的客户端负载均衡实例,即:每个路由信息都会有对应的客户端负载均衡实例,客户端负载均衡实例是多例的
我们回头看下ReactiveLoadBalancerClientFilter#filter方法,根据请求信息拿到请求的uri,根据解析出serviceId。
URI requestUri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
String serviceId = requestUri.getHost();
接着在ReactiveLoadBalancerClientFilter#choose方法内进行服务实例的选择。
private Mono<Response<ServiceInstance>> choose(Request<RequestDataContext> lbRequest, String serviceId,
Set<LoadBalancerLifecycle> supportedLifecycleProcessors) {
// 根据serviceI返回对应的LoadBalancer实例
ReactorLoadBalancer<ServiceInstance> loadBalancer = this.clientFactory.getInstance(serviceId,
ReactorServiceInstanceLoadBalancer.class);
if (loadBalancer == null) {
throw new NotFoundException("No loadbalancer available for " + serviceId);
}
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
// 选择服务实例
return loadBalancer.choose(lbRequest);
}
在通过Loadbalancer实例选择到对应的下游服务实例之后,通过uri的替换,拿到真实的请求地址,最后交给下一个过滤器。
// 负载均衡返回的服务实例
ServiceInstance retrievedInstance = response.getServer();
URI uri = exchange.getRequest().getURI();
// 重写scheme
String overrideScheme = retrievedInstance.isSecure() ? "https" : "http";
if (schemePrefix != null) {
overrideScheme = url.getScheme();
}
DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(retrievedInstance,
overrideScheme);
// 解析真实要代理的URL
URI requestUrl = reconstructURI(serviceInstance, uri);
Loadbalancer - 装配流程
在上一节我们在ReactiveLoadBalancerClientFilter#choose方法上看到了客户端负载均衡实例是通过LoadBalancerClientFactory类获取的,通过打断点知道起默认具体实例是:RoundRobinLoadBalancer,它是通过AnnotationConfigApplicationContext来获取的。代码如下:
public <T> T getInstance(String name, Class<T> type) {
AnnotationConfigApplicationContext context = getContext(name);
try {
return context.getBean(type);
}catch (NoSuchBeanDefinitionException e) {
}
return null;
}
接着,我们翻了下Loadbalancer组件的源码,我们发现了2个配置类:
?LoadBalancerClientConfiguration 客户端负载均衡配置?LoadBalancerAutoConfiguration 负载均衡自动配置
LoadBalancerClientFactory 类就是在LoadBalancerAutoConfiguration配置类中进行实例化的,它还持有一个LoadBalancerClientSpecification 列表,其实现了NamedContextFactory.Specification接口,这里就是为了实现不同路由会有不同的客户端负载均衡实例。
LoadBalancerClientConfiguration 配置类主要配置了:
?ReactorLoadBalancer 客户端负载均衡实例
?ServiceInstanceListSupplier 服务实例列表提供者,用于根据serviceId获取对应的服务实例列表。
代码如下:
@Bean
@ConditionalOnMissingBean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RoundRobinLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
@Bean
@ConditionalOnBean(ReactiveDiscoveryClient.class)
@ConditionalOnMissingBean
@Conditional(DefaultConfigurationCondition.class)
public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder().withDiscoveryClient().withCaching().build(context);
}
Loadbalancer - ReactorLoadBalancer实例化
在上一节,我们找到了ReactorLoadBalancer实例装配的地方,但是只是这么简单吗 ? 很明显不是!
ReactorLoadBalancer实例是多例的,不同的路由一般都会有不同的客户端负载均衡实例。那它是怎么实现的呢?
在LoadBalancerAutoConfiguration类,我们看到了一个注解:LoadBalancerClients,它导入了一个LoadBalancerClientConfigurationRegistrar类,在这类中它注册了LoadBalancerClientSpecification实例。代码如下:
private static void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(LoadBalancerClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(name + ".LoadBalancerClientSpecification", builder.getBeanDefinition());
}
这玩意儿就是LoadBalancerClientFactory的配置信息,默认为空就是了。
这个时候回头看LoadBalancerAutoConfiguration类实例化LoadBalancerClientFactory的时候有个ObjectProvider<List<LoadBalancerClientSpecification>>的实例,就是从LoadBalancerClients注解来的。
至此,LoadBalancerClientFactory实例化好了,而且我们也知道ReactorLoadBalancer是通过LoadBalancerClientFactory#getInstances获取的,这里其实是其父类:NamedContextFactory的方法,获取ReactorLoadBalancer实例的底层代码如下:
protected AnnotationConfigApplicationContext createContext(String name) {
// 创建一个Spring 的子Context
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);
// 添加属性到环境变量,key:"loadbalancer.client.name", value: serviceId
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName,
Collections.<String, Object>singletonMap(this.propertyName, name)));
// 设置其父context
if (this.parent != null) {
context.setParent(this.parent);
context.setClassLoader(this.parent.getClassLoader());
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
这里是根据serviceId创建一个AnnotationConfigApplicationContext,从而实现不同的路由具有不同的Spring子容器,然后再从容器中拿到不同的客户端负载均衡实例。
在这个子容器创建完成,并且从容器中获取ReactorLoadBalancer实例,这个时候代码就走到了我们刚看到的配置类:LoadBalancerClientConfiguration。
这里的配置类使用了注解:@Configuration(proxyBeanMethods = false),也就是说,在第一次获取实例的时候不会从父容器获取,而是直接new一个实例,然后放入到子容器,下次从子容器的缓存获取,最终达到不同路由具有不同的客户端负载均衡实例的目的!
思考题:如果一开始把LoadBalancerClientConfiguration配置到LoadBalancerClientSpecification实例的配置列表,那么每次从子容器获取的实例是否同一个实例?
Loadbalancer - RoundRobinLoadBalancer 源码分析
通过上一节,我们终于搞明白了客户端负载均衡是如何实例化的,并且如何实现不同路由具有不同的客户端负载均衡实例,同时,我们还知道了默认的客户端负载均衡实例是:RoundRobinLoadBalancer,以轮训的方式进行选择实例。
在RoundRobinLoadBalancer#getInstanceResponse方法内,通过维护一个自增的成员变量,然后与实例列表进行求余,从而实现轮训,代码如下:
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + serviceId);
}
return new EmptyResponse();
}
// 轮训
int pos = Math.abs(this.position.incrementAndGet());
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
}
Loadbalancer - 服务发现
在实现客户端负载均衡,那么首先需要从注册中心获取服务实例列表,这里就少不了服务发现组件。那它是如何与Loadbalancer组件结合的呢?
在实例化ReactorLoadBalancer的时候,我们会发现,其通过LoadBalancerClientFactory获取了一个ObjectProvider<ServiceInstanceListSupplier>实例,该实例是在ReactiveSupportConfiguration#discoveryClientServiceInstanceListSupplier方法内进行装配的。代码如下:
@Bean
@ConditionalOnBean(ReactiveDiscoveryClient.class)
@ConditionalOnMissingBean
@Conditional(DefaultConfigurationCondition.class)
public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder().withDiscoveryClient().withCaching().build(context);
}
可以看到通过建造者模式,对于服务提供者加入了服务发现组件和缓存组件,其加入方式都是使用代理模式。服务发现组件处理如下:
public ServiceInstanceListSupplierBuilder withDiscoveryClient() {
if (baseCreator != null && LOG.isWarnEnabled()) {
LOG.warn("Overriding a previously set baseCreator with a ReactiveDiscoveryClient baseCreator.");
}
// 函数式形式,在apply的时候才会返回具体的实例
this.baseCreator = context -> {
ReactiveDiscoveryClient discoveryClient = context.getBean(ReactiveDiscoveryClient.class);
return new DiscoveryClientServiceInstanceListSupplier(discoveryClient, context.getEnvironment());
};
return this;
}
最后
到这里, Loadbalancer 组件的基本工作流程我们已经解读完了,相信你也对Spring Cloud Loadbalancer 有了更深的了解。
其中对于ReactorLoadBalancer实例化会比较难理解,这里需要去看下NamedContextFactory相关作用才能更好的去理解Spring子容器等相关知识。
References
[1] 实现Spring Cloud Gateway 动态路由和内置过滤器: https://juejin.cn/post/7038231474465669157
[2] Spring Cloud Gateway和Spring WebFlux的契合点: https://juejin.cn/post/7041055771768913927
[3] Spring Cloud Gateway 过滤器工作流程: https://juejin.cn/post/7043406906144063518
[4] Spring Cloud Gateway 过滤器工作流程: https://juejin.cn/post/7043406906144063518
猜你喜欢
- 2024-09-20 Spring cloud Ribbon 客户端负载均衡详解(二)负载均衡器
- 2024-09-20 springcloud(十三):注册中心 Consul 使用详解
- 2024-09-20 SpringCloud系列——11Spring Cloud 源码分析之Gateway网关
- 2024-09-20 FeignClient注解配置url属性实现指定服务方
- 2024-09-20 SpringCloud升级之路2020.0.x版-34.验证重试配置正确性(2)
- 2024-09-20 我放弃了okhttp、httpClient,选了这个神仙工具
- 2024-09-20 还没有秃头吗?你真的需要大牛来教你如何深入解析Ribbon源码了
- 2024-09-20 Spring GateWay : 网关的转发细节
- 2024-09-20 深入理解SpringCloud之Gateway,小白都能看懂的保姆级教学
- 2024-09-20 微服务架构进阶:Hystrix 如何解决灾难性雪崩及隔离问题
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- powershellfor (55)
- messagesource (56)
- aspose.pdf破解版 (56)
- promise.race (63)
- 2019cad序列号和密钥激活码 (62)
- window.performance (66)
- qt删除文件夹 (72)
- mysqlcaching_sha2_password (64)
- ubuntu升级gcc (58)
- nacos启动失败 (64)
- ssh-add (70)
- jwt漏洞 (58)
- macos14下载 (58)
- yarnnode (62)
- abstractqueuedsynchronizer (64)
- source~/.bashrc没有那个文件或目录 (65)
- springboot整合activiti工作流 (70)
- jmeter插件下载 (61)
- 抓包分析 (60)
- idea创建mavenweb项目 (65)
- vue回到顶部 (57)
- qcombobox样式表 (68)
- vue数组concat (56)
- tomcatundertow (58)
- pastemac (61)
本文暂时没有评论,来添加一个吧(●'◡'●)