1: 简介
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。那么哪些路由配置会走负载均衡器呢?答案是配置具有lb://服务名,这样的配置会走,因为LoadBalancerClientFilter会进行判断url的前缀是否含有lb
if (url == null||
(!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
return chain.filter(exchange);
}
2: 查看自动配置文件
spring.factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration
3: 分析RibbonAutoConfiguration
第一步:分析SpringClientFactory
配置类实例化了工厂配置SpringClientFactory,而SpringClientFacory实现了spring cloud context的NamedContextFactory,NamedContextFactory是创建客户端、负载平衡器和客户端配置实例的工厂,为每个实现者createContext创建一个SpringApplicationContext(AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();),并提取所需要的信息。也就是说可以在SpringClientFactory里获取配置 lb 的具体实例信息,在这个过程中,会调用该容器的refresh()刷新当前的容器实例信息,这个时候会触发RibbonClientConfiguration配置,实例化负载均衡器ZoneAwareLoadBalancer、负载均衡规则 ZoneAvoidanceRule,并将ZoneAvoidanceRule注入到ZoneAwareLoadBalancer里,默认通过ZoneAwareLoadBalancer的choose找到server信息
@Bean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
通过SpringClientFactory获取负载均衡器
public ILoadBalancer getLoadBalancer(String name) {
return getInstance(name, ILoadBalancer.class);
}
getLoadBalancer里的name指的是负载均衡lb的名字,如下面中的 opencloud-ram,负载均衡器是IloadBalancer,通过SpringClientFactory中的容器AnnotationConfigApplicationContext,调用getBean获取具体的bean实例,即ZoneAwareLoadBalancer。
- id: ram_route
uri: lb://opencloud-ram
predicates:
- Path=/v1/ram/**
第二步:分析RibbonLoadBalancerClient
RibbonAutoConfiguration也实例化了RibbonLoadBalancerClient客户端,使其根据lb获取负载均衡实例,
public ServiceInstance choose(String serviceId, Object hint) {
Server server = getServer(getLoadBalancer(serviceId), hint);
if (server == null) {
return null;
}
return new RibbonServer(serviceId, server, isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
}
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
if (loadBalancer == null) {
return null;
}
// Use 'default' on a null hint, or just pass it on?
// 在这里我们看到,hint作为负载均衡策略,如果不设置,则为null,会使用默认的default侧露
// 也就是轮询策略
return loadBalancer.chooseServer(hint != null ? hint : "default");
}
此时程序执行到关键的步骤,使用ZoneAwareLoadBalancer负载均衡策略获取server
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
// 这的服务信息,将有配置中心获取,所用的配置中心是Nacos
List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
if (eligible.size() == 0) {
return Optional.absent();
}
return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
}
其中算法如下,一个轮询的算法
private final AtomicInteger nextIndex = new AtomicInteger();
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextIndex.get();
int next = (current + 1) % modulo;
if (nextIndex.compareAndSet(current, next) && current < modulo)
return current;
}
}
4: 配置分析完了之后,要分析下如何应用
LoadBalancerClientFilter作为负载均衡过滤器,所有有效的请求会经过这里的filter,然后根据负载策略选择服务
@Override
@SuppressWarnings("Duplicates")
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
if (url == null
|| (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
return chain.filter(exchange);
}
// preserve the original url
addOriginalRequestUrl(exchange, url);
log.trace("LoadBalancerClientFilter url before: " + url);
final ServiceInstance instance = choose(exchange);
if (instance == null) {
throw NotFoundException.create(properties.isUse404(),
"Unable to find instance for " + url.getHost());
}
URI uri = exchange.getRequest().getURI();
// if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
// if the loadbalancer doesn't provide one.
String overrideScheme = instance.isSecure() ? "https" : "http";
if (schemePrefix != null) {
overrideScheme = url.getScheme();
}
URI requestUrl = loadBalancer.reconstructURI(
new DelegatingServiceInstance(instance, overrideScheme), uri);
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
return chain.filter(exchange);
}
看了下Spring Cloud Gateway官网的原理图,知道了filter应该是经由webhandler做的轮询处理,找找代码,看看是不是这么回事
果不其然,列表中有个过滤器就是LoadBalancerClientFilter,而这里的Mono.defer表示创建反应流,如果使用Mono.just会在声明阶段构造对象,只创建一次,但是Mono.defer却是在subscribe阶段才会创建对应的对象,每次调用subscribe方法都会创建对象。
@Override
public Mono<Void> filter(ServerWebExchange exchange) {
return Mono.defer(() -> {
if (this.index < filters.size()) {
GatewayFilter filter = filters.get(this.index);
DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(this,
this.index + 1);
return filter.filter(exchange, chain);
}
else {
return Mono.empty(); // complete
}
});
}
那为什么使用defer每次subscribe被调用的时候会执行创建呢?因为defer传入了匿名类给Mono并赋值给了Supplier,Supplier什么作用呢,只有调用Supplier里的get方法,才会真正构建对象,而MonoDefer正是在调用subscribe时候调用supplier.get()方法,此时也就解释了只有调用subscribe时候才会构建对象的说法,代码如下:
public void subscribe(CoreSubscriber<? super T> actual) {
Mono<? extends T> p;
try {
p = Objects.requireNonNull(supplier.get(),
"The Mono returned by the supplier is null");
}
catch (Throwable e) {
Operators.error(actual, Operators.onOperatorError(e, actual.currentContext()));
return;
}
p.subscribe(actual);
}
认真写写博客,写写生活点滴