Ribbon之LoadBalancerClient、ZoneAwareLoadBalancer 、ZoneAvoidanceRule默认三剑客

1.RibbonLoadBalancerClient之承上

在ribbon中LoadBalanceClient 只有一个子类,这个子类就是RibbonLoadBalancerClient。它从LoadBalancerInterceptor接过请求,进入真正的负载均衡流程。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BnU5dnzx-1623398061677)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/5b961a52-9c29-48b6-8c00-bcd7a4c31316/Untitled.png)]



2.RibbonLoadBalancerClient做了些啥?

  1. 获取负载均衡器ILoadBalancer
  2. 根据负载均衡器和规则选择服务(chooseServer
  3. 记录每个serviceId的状态 RibbonStatsRecorder
  4. 执行request请求并返回结果 T returnVal = request.apply(serviceInstance);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8kTVlXnc-1623398061685)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/ef575409-0135-44a7-86c0-6223a55a119d/Untitled.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ROpARyHN-1623398061686)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/1905e091-527a-4103-a3f1-eb73892a95a7/Untitled.png)]



3.获取负载均衡器getLoadBalancer(serviceId)

这里我们看到是通过serviceId进行负载均衡器的获取,那说明其实每个服务(包含多个服务实例)应该拥有一个自己的负载均衡器,其中应该存在一些机制来维护服务列表,还有对应的负载均衡策略,以及负载均衡策略所需的数据维护。

通过debug,我发现是通过AnnotationConfigApplicationContext来维护的,然后从里面根据service类型获取对应的实例信息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uQqAsGOh-1623398061689)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/2a7aaf24-7c44-463d-b993-dcd8c52da688/Untitled.png)]

在这里看到默认的负载均衡器为ZoneAwareLoadBalancer

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-blwuvhR7-1623398061692)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/5df29129-fb27-41a3-9b80-83a683c4e989/Untitled.png)]

那这个负载均衡器在哪初始化的呢?这个我后面会单独开一篇来讲,这里就先跳过了。



4.ZoneAwareLoadBalancer 如何进行服务选择

public Server chooseServer(Object key) {
				// 我们知道每个服务都有一个负载均衡器,这里是针对每个服务的负载均衡统计器
				// 看看进行了多少次请求负载均衡
        if (counter == null) {
            counter = createCounter();
        }
        counter.increment();
        if (rule == null) {
            return null;
        } else {
            try {
								// 诶,这里进入了规则选择
                return rule.choose(key);
            } catch (Exception e) {
                logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
                return null;
            }
        }
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iZ2ONgg6-1623398061693)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/79c1399d-122f-4f41-b7cd-1b334aab8c50/Untitled.png)]
这里我们可以看到默认的负载均衡策略为ZoneAvoidanceRule



5.ZoneAvoidanceRule如何选择服务的呢?

public Server choose(Object key) {
        ILoadBalancer lb = getLoadBalancer();
				// 这里就完成了服务的选择
				// 而且我们可以看到,这里的lb.getAllServers 说明ILoadBalancer直接存储或者间接存储了服务列表
        Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
        if (server.isPresent()) {
            return server.get();
        } else {
            return null;
        }       
    }

从上面可以看到chooseRoundRobinAfterFiltering 这个方法的意思就是在过滤之后,选择轮询的负载均衡方式。

lb.getAllServers是获取该服务的所有服务实例。

由此可见chooseRoundRobinAfterFiltering就是选择的关键点了。

public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
        // 过滤掉不复合条件的服务实例
				List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
        if (eligible.size() == 0) {
            return Optional.absent();
        }
				// incrementAndGetModulo 这个就是轮询的关键计算
        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;
						// 重置next指针
            if (nextIndex.compareAndSet(current, next) && current < modulo)
                return current;
        }
    }

其实轮询方式的负载均衡就是在n个服务器中按照顺序进行访问,通过取模来实现nextIndex永远不会大于modulo的情况。

在这里选择完使用哪个服务实例Server后,如何将原链接中的ServiceProvider服务名替换为Server中的ip和端口呢?



6.服务名到ip+端口的过程

1.将拿到的服务实例封装成RibbonServerServiceInstance的子类)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U5EXjyjK-1623398061695)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/d6b1ad59-c41b-430c-9c64-2b47408976f5/Untitled.png)]

2.LoadBalancerRequest.apply将将服务实例封装到HttpRequest的子类ServiceRequestWrapper

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xYEZA4Jf-1623398061701)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/695b7d94-7028-4b20-b5c4-2874f37f8596/Untitled.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fuqQtAy4-1623398061702)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/fc045da1-be11-4ee1-983d-19a37cbb032c/Untitled.png)]

3.拿着ServiceRequestWrapper 回到InterceptionClientHttpRequest中继续往下执行,在getURI方法中完成了服务名称到ip和端口的替换过程。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j6j9Sfk8-1623398061704)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/107f463c-5001-49a9-bd6a-66d90c1c9d5e/Untitled.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5ZyaPo1t-1623398061706)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/b027fbc0-c101-4173-a1f5-31eab08f20c5/Untitled.png)]



7.替换的细节

@Override
	public URI reconstructURI(ServiceInstance instance, URI original) {
		// 拿到实例ID
		Assert.notNull(instance, "instance can not be null");
		String serviceId = instance.getServiceId();
		// 获取负载均衡器的上下文
		RibbonLoadBalancerContext context = this.clientFactory
				.getLoadBalancerContext(serviceId);

		URI uri;
		Server server;
		if (instance instanceof RibbonServer) {
			RibbonServer ribbonServer = (RibbonServer) instance;
			server = ribbonServer.getServer();
			// 判断是否要走安全连接
			uri = updateToSecureConnectionIfNeeded(original, ribbonServer);
		} else {
			server = new Server(instance.getScheme(), instance.getHost(), instance.getPort());
			IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
			ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
			uri = updateToSecureConnectionIfNeeded(original, clientConfig,
					serverIntrospector, server);
		}
		// 拿着服务实例信息和带服务名的uri去进行替换并返回
		return context.reconstructURIWithServer(server, uri);
	}

开始替换

public URI reconstructURIWithServer(Server server, URI original) {
        String host = server.getHost();
        int port = server.getPort();
        String scheme = server.getScheme();
        // 校验url和服务实例内的信息是否一致
        if (host.equals(original.getHost()) 
                && port == original.getPort()
                && scheme == original.getScheme()) {
            return original;
        }
				// 获取请求协议前缀
        if (scheme == null) {
            scheme = original.getScheme();
        }
        if (scheme == null) {
            scheme = deriveSchemeAndPortFromPartialUri(original).first();
        }

        try {
            StringBuilder sb = new StringBuilder();
						// 协议拼接
            sb.append(scheme).append("://");
            if (!Strings.isNullOrEmpty(original.getRawUserInfo())) {
                sb.append(original.getRawUserInfo()).append("@");
            }
						// 域名或ip拼接
            sb.append(host);
            //端口整起来
						if (port >= 0) {
                sb.append(":").append(port);
            }
						// 拼参数
            sb.append(original.getRawPath());
            if (!Strings.isNullOrEmpty(original.getRawQuery())) {
                sb.append("?").append(original.getRawQuery());
            }
            if (!Strings.isNullOrEmpty(original.getRawFragment())) {
                sb.append("#").append(original.getRawFragment());
            }
            URI newURI = new URI(sb.toString());
            return newURI;            
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

这样就拼接出了一个全新的链接去发送http请求了。



8.小结

这里其实又将之前的整个ribbon拦截restTemplate的过程进行详细梳理了一遍。后面可能研究一下,服务列表怎么获取的,又是怎么更新和维护的。还有不同的负载均衡策略和不同的负载均衡器是怎么玩的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值