探究Spring Cloud Ribbon默认负载均衡策略实现的过程

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);
	}

 

认真写写博客,写写生活点滴

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值