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);
}
認真寫寫博客,寫寫生活點滴