[享学Netflix] 六十、Ribbon具有负载均衡能力的客户端:AbstractLoadBalancerAwareClient

当你想在你的代码中找到一个错误时,这很难;当你认为你的代码是不会有错误时,这就更难了。

–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/netflix-learning

前言

Ribbon不仅仅是负载均衡,负载均衡只是它的一个最核心、最出名的模块而已。在聊ribbon-core的时候我们知道它有个核心API是IClient,它表示发送一个请求得到一个响应,不规定发送方式、协议等。

因为Ribbon最核心的功能就是负载均衡,因此本文我们将了解到它这个具有负载均衡能力的客户端:AbstractLoadBalancerAwareClient,它所在的jar是:ribbon-loadbalancer


正文

AbstractLoadBalancerAwareClient

因为我们不太可能把Ribbon当其它用,而只用作负载均衡,因此你可以简单粗暴理解为:AbstractLoadBalancerAwareClient是所有的客户端实现的顶级父类,而实际上也确实如此。

需要注意的是,该抽象类不仅实现了接口IClient,并且还继承自LoadBalancerContext,所以它自己不仅仅是个Client,还拥有着负载均衡上下文。

该抽象类无任何成员属性,提供了一些方法:


初始化方法

它提供两个构造器用于初始化。

public abstract class AbstractLoadBalancerAwareClient<S extends ClientRequest, T extends IResponse> extends LoadBalancerContext implements IClient<S, T>, IClientConfigAware {

    public AbstractLoadBalancerAwareClient(ILoadBalancer lb) {
        super(lb);
    }
    public AbstractLoadBalancerAwareClient(ILoadBalancer lb, IClientConfig clientConfig) {
        super(lb, clientConfig);        
    }

}

作为一个和负载均衡相关的Client,负载均衡器ILoadBalancer是必不可少的喽。


buildLoadBalancerCommand() 构建负载均衡命令

Ribbon执行所有的请求都是基于命令模式去执行的,因此均需要包装成一个LoadBalancerCommand命令:

AbstractLoadBalancerAwareClient:

	// 抽象方法:提供一个RequestSpecificRetryHandler重试处理器
	// 因为重试方案父类定不了:有些是超时重试,有些是异常重试,因此交给子类去决定为好
	// 但请保证是RequestSpecificRetryHandler的子类:因为它已经帮你实现了写基本逻辑
	// 一般使用包装器模式,给RequestSpecificRetryHandler.fallback赋值了就好
	public abstract RequestSpecificRetryHandler getRequestSpecificRetryHandler(S request, IClientConfig requestConfig);
	// 毕竟LoadBalancerCommand的属性众多,默认只给其设置必要的属性,其它的交给调用者去个性化吧
	// 比如常用的:增加监听器来监听必要的执行过程
	protected void customizeLoadBalancerCommandBuilder(S request, IClientConfig config, LoadBalancerCommand.Builder<T> builder) {
		// 空实现,交给子类去定制
	}

	// request请求对象,提供URI(注意不是URL,因为不一定是网络请求)
    protected LoadBalancerCommand<T> buildLoadBalancerCommand(S request, IClientConfig config) {
    	// 得到重试处理器:因为重试处理器对LoadBalancerCommand的行为特别重要
		RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, config);
		LoadBalancerCommand.Builder<T> builder = LoadBalancerCommand.<T>builder()
				.withLoadBalancerContext(this)
				.withRetryHandler(handler)
				.withLoadBalancerURI(request.getUri());
		customizeLoadBalancerCommandBuilder(request, config, builder);
		return builder.build();
	}

它留下一个抽象方法和一个钩子方法给AbstractLoadBalancerAwareClient去完成定制化~


executeWithLoadBalancer() 带有负载均衡能力的执行

它不是接口方法:因为接口方法不具备负载均衡的能力。但是它是更为重要的方法:包装了execute()接口方法,放在LoadBalancerCommand里执行从而就具有负载均衡的能力了。

AbstractLoadBalancerAwareClient:

	// 注意:接口方法只有execute,这是在外层套了一个负载均衡器,均由负载均衡的能力
    public T executeWithLoadBalancer(S request) throws ClientException {
        return executeWithLoadBalancer(request, null);
    }

    public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
    	// 构建一个执行命令:command
    	LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);


		// 提交目标操作/目标请求  -> 执行目标方法
    	return command.submit(server -> {
    		// 根据LB选中的Server,构建出一个最终的URI
    		// 因为你的URI可能没有host、port等是不完整的
    		URI finalUri = reconstructURIWithServer(server, request.getUri());
    		// 给request重新制定一个新的URI
    		S requestForServer = (S) request.replaceUri(finalUri);
    		
    		// execute执行目标方法:一般是发送http请求,当然这不是一定的~
    		return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
    	})
    	// 阻塞的,顺序执行 使用RxJava是为了编程方便、优美
		.toBlocking()
		.single();
    }
}

说明:为了代码结构清晰,内嵌的很多try…catch均省略了

该方法的核心要义是:使用LoadBalancerCommand包装execute目标方法,从而使得其具有了负载均衡的能力。因此在实际应用中:请勿直接调用execute方法,而是使用更加上层、功能更强的executeWithLoadBalancer()方法。


如果仅是在ribbon-loadbalancer这个jar内,IClient体系有且仅有这一个子类AbstractLoadBalancerAwareClient,并且它还是抽象类。由于负载均衡器它并不限定具体协议,比如http、tcp、udp等都是可以做负载均衡的,所以在此jar内并无任何具体的Client实现类

但是在Spring Cloud环境下,一片繁荣:

在这里插入图片描述
在这里插入图片描述
本文并不会介绍Spring Cloud里对它的实现,而是把它放在和Spring Cloud整合的相关章节中。


ClientFactory

既然Client如此重要,为了快速、方便的得到一个Client实例,Ribbon提供了ClientFactory这个工厂类。该类用于快速创建IClient客户端、ILoadBalancerIClientConfig等的工厂。并且它内部还维护了全局的Map,缓存来提高获取效率。

public class ClientFactory {

	// key是ClientName  value是Client实例
	private static Map<String, IClient<?,?>> simpleClientMap = new ConcurrentHashMap<>();
	// key是Lb的名称  value是LB实例
	private static Map<String, ILoadBalancer> namedLBMap = new ConcurrentHashMap<>();
	// key是ClientName  value是该client对应的配置(含默认配置)
	private static ConcurrentHashMap<String, IClientConfig> namedConfig = new ConcurrentHashMap<>();

	// ==========工具方法们=========
	
	// 反射创建一个实例,并且调用其initWithNiwsConfig()方法把config传递给它
	public static Object instantiateInstanceWithClientConfig(String className, IClientConfig clientConfig) {
		...
		IClientConfigAware obj = (IClientConfigAware) clazz.newInstance();
		obj.initWithNiwsConfig(clientConfig);
		return obj;
	}

	// 反射创建一个clientConfigClass类型的配置。IClientConfig接口的自带实现仅有DefaultClientConfigImpl
	// 注意config.loadProperties(name)方法会被调用哦(配置会被加载进来)
	// 最后放进缓存(缓存里就返回缓存里的)
	public static IClientConfig getNamedConfig(String name, Class<? extends IClientConfig> clientConfigClass) {
        IClientConfig config = namedConfig.get(name);
        if (config != null) {
            return config;
        } 
        config = (IClientConfig) clientConfigClass.newInstance();
        config.loadProperties(name);
        ...
        return config;
	}
    public static IClientConfig getNamedConfig(String name) {
        return 	getNamedConfig(name, DefaultClientConfigImpl.class);
    }

	... // 创建`ILoadBalancer`实例的方法几乎一模一样,略

	// 提供名称和客户端配置的实用程序方法来创建客户端和负载均衡器(如果在客户端配置中启用)
	// InitializeNFLoadBalancer默认配置值是true,开启负载均衡器的
	public static synchronized IClient<?, ?> registerClientFromProperties(String restClientName, IClientConfig clientConfig) throws ClientException {
    	IClient<?, ?> client = null;
    	ILoadBalancer loadBalancer = null;
    	// 如果同名的Client已经创建过了,在调用此方法就抛错,而并非把缓存里的返回给你
    	if (simpleClientMap.get(restClientName) != null) {
    		throw new ClientException(ClientException.ErrorType.GENERAL, "A Rest Client with this name is already registered. Please use a different name");
    	}
    	... // 反射创建Client、LB的实例
		simpleClientMap.put(restClientName, client);
		return client;
	}

	// 它木有传入配置:所以全部使用外部化配置
    public static synchronized IClient getNamedClient(String name) {
        return getNamedClient(name, DefaultClientConfigImpl.class);
    }
    public static synchronized IClient getNamedClient(String name, Class<? extends IClientConfig> configClass) {
    	if (simpleClientMap.get(name) != null) {
    	    return simpleClientMap.get(name);
    	}
    	return createNamedClient(name, configClass);
    }
}

其中ClientFactory.instantiateInstanceWithClientConfig()方法是最为通用的:它可以实例化帮你实例化任何实例,包括五大核心组件等。它的优点是初始化完成后自动帮你调用initWithNiwsConfig()方法完成属性赋值~


总结

关于Ribbon具有负载均衡能力的客户端:AbstractLoadBalancerAwareClient就先介绍到这,虽然本文木有提及具体实现类和给出代码示例,但是只要理解了它(其实核心是LoadBalancerCommand)你会觉得其它都是小儿科,这在后面讲解整合时将会继续说明。

至此,关于Ribbon最最最核心部分(包含core和loadbalancer)就全部介绍完了,虽然此项目目前已经停更,但停更不停用,目前仍然是主流的(甚至是唯一的)客户端负载均衡器。学习了它不仅可以用在Spring Cloud体系下运用自如,对于理解dubbo等框架负载均衡机制都易如反掌,另外据我了解深入了解Ribbon,以及有能力使用它来实现多区域部署的人并不多,因此若你掌握了这不就是你升职加薪的砝码了麽~

当然,这不是Ribbon系列的全部,后面还要讲整合、在Spring Cloud下的实战。有了强大的理论做支撑,实战讲解将非常的快。
分隔线

声明

原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。
往期精选

发布了362 篇原创文章 · 获赞 531 · 访问量 48万+

猜你喜欢

转载自blog.csdn.net/f641385712/article/details/104998870