当你想在你的代码中找到一个错误时,这很难;当你认为你的代码是不会有错误时,这就更难了。
–> 返回Netflix OSS套件专栏汇总 <–
代码下载地址:https://github.com/f641385712/netflix-learning
Ribbon不仅仅是负载均衡,负载均衡只是它的一个最核心、最出名的模块而已。在聊ribbon-core
的时候我们知道它有个核心API是IClient
,它表示发送一个请求得到一个响应,不规定发送方式、协议等。
因为Ribbon最核心的功能就是负载均衡,因此本文我们将了解到它这个具有负载均衡能力的客户端:AbstractLoadBalancerAwareClient
,它所在的jar是:ribbon-loadbalancer
。
因为我们不太可能把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
是必不可少的喽。
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
去完成定制化~
它不是接口方法:因为接口方法不具备负载均衡的能力。但是它是更为重要的方法:包装了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整合的相关章节中。
既然Client如此重要,为了快速、方便的得到一个Client实例,Ribbon提供了ClientFactory
这个工厂类。该类用于快速创建IClient
客户端、ILoadBalancer
、IClientConfig
等的工厂。并且它内部还维护了全局的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下的实战。有了强大的理论做支撑,实战讲解将非常的快。
Author | A哥(YourBatman) |
---|---|
个人站点 | www.yourbatman.cn |
[email protected] | |
微 信 | fsx641385712 |
活跃平台 |
|
公众号 | BAT的乌托邦(ID:BAT-utopia) |
知识星球 | BAT的乌托邦 |
每日文章推荐 | 每日文章推荐 |