[享学Netflix] 五十八、Ribbon负载均衡命令:LoadBalancerCommand(一)基础类打点

软件在能够复用前必须先能用。

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

前言

如果你已经了解过Netflix Hystrix,那么你对Command命令模式并不陌生。同样的,Ribbon也采用了基于RxJava的命令模式来控制请求的执行过程,这便是本文将要讲解的核心内容:LoadBalancerCommand


正文

基于RxJava的命令模式,LoadBalancerCommandb表示负载均衡命令,用于中转请求到负载均衡器ILoadBalancer。它的相关类并不多,如下截图:

在这里插入图片描述

其中最重要的当属LoadBalancerCommand这个API,在这之前,先对其基础类进行打点。


ServerOperation

本接口非常简单,就是一个函数式接口Function而已:“吃”进去一个Server,吐出来一个Observable<T>。提供基于rxjava的Observable支持

// 仅仅继承自Func1,暴露出结果泛型T
public interface ServerOperation<T> extends Func1<Server, Observable<T>> {}

它作为一个动作,作为入参传入LoadBalancerCommand#submit()提交给命令处理。


ExecutionInfo

一个POJO,代表执行时信息。

public class ExecutionInfo {

    private final Server server;
    // 在该Server上已经尝试过的总次数
    private final int numberOfPastAttemptsOnServer;
    // 已经尝试过的Server总个数
    private final int numberOfPastServersAttempted;

	// 构造器是私有化的。该静态方法用于创建实例
    public static ExecutionInfo create(Server server, int numberOfPastAttemptsOnServer, int numberOfPastServersAttempted) {
        return new ExecutionInfo(server, numberOfPastAttemptsOnServer, numberOfPastServersAttempted);
    }
}

它主要用于作为参数传递给ExecutionListener,从而使得调用者可以窥探到执行是的信息。


ExecutionInfoContext

它是LoadBalancerCommand的一个内部类,记录执行时的上下文info信息。

LoadBalancerCommand:

	class ExecutionInfoContext {
		// 当前Server,每次调用set方法,此值就会变哦~~~~
		// serverAttemptCount记录了当前请求已经换了几次Server了~~~
        Server      server;
        // 已经重试的Server总数(每次换台Server就+1)
        int         serverAttemptCount = 0; 
        // 这次请求的重试总数(包括相同Server,以及换Server,总之是个总数)
        int         attemptCount = 0;

        public ExecutionInfo toExecutionInfo() {
            return ExecutionInfo.create(server, attemptCount-1, serverAttemptCount-1);
        }
        public ExecutionInfo toFinalExecutionInfo() {
            return ExecutionInfo.create(server, attemptCount, serverAttemptCount-1);
        }
	}

该上下文主要记录了一次请求执行时的上下文重试次数,最终生成一个ExecutionInfo实例交给监听器,使用者可以完成个性化判断(比如限制一把:重试过多就不要再重试了)。


ExecutionContext (重要)

在每个负载均衡器执行开始时创建的上下文对象,包含负载均衡器的某些元数据和的可变状态数据。

public class ExecutionContext<T> {
	
	// 存储元数据的Map
	private final Map<String, Object> context;
	// ChildContext继承自ExecutionContext
	private final ConcurrentHashMap<Object, ChildContext<T>> subContexts;

	// 请求。requestConfig的优先级比clientConfig的高哦,具有“覆盖”效果
	private final T request;
    private final IClientConfig requestConfig;
    private final RetryHandler retryHandler;
    private final IClientConfig clientConfig;
	
	// 子上下文:它增加了一个属性ExecutionContext<T> parent;
	// 这样便可以让两个ExecutionContext关联起来
    private static class ChildContext<T> extends ExecutionContext<T> {
        private final ExecutionContext<T> parent;
        ChildContext(ExecutionContext<T> parent) {
            super(parent.request, parent.requestConfig, parent.clientConfig, parent.retryHandler, null);
            this.parent = parent;
        }
        @Override
        public ExecutionContext<T> getGlobalContext() {
            return parent;
        }
    }
	... // 省略构造器赋值
	
	// default访问权限的方法:获取子执行上下文
	// 没有就会new一个然后放在ConcurrentHashMap缓存起来。key就是obj
	ExecutionContext<T> getChildContext(Object obj) { ... }
	...
	// 获得指定name的value。一个简单的k-v而已
    public void put(String name, Object value) {
        context.put(name, value);
    }
    public Object get(String name) {
        return context.get(name);
    }
    // 先去requestConfig里找,若没找再去clientConfig里找
    public <S> S getClientProperty(IClientConfigKey<S> key) { ... }

	// 注意:子上下文ChildContext的该方法返回的是parent,所以就打通了
    public ExecutionContext<T> getGlobalContext() {
        return this;
    }
}

通过父、子上下文的设计,能保证每个listener有它自己的ExecutionContext,但是你也可以通过ExecutionContext#getGlobalContext()来得到所有listener全局共享的上下文。


ExecutionListener

负载均衡器在不同执行阶段调用的监听器。

// I:input类型,给ExecutionContext用。这样ExecutionContext#getRequest()就能拿到这个请求对象
// O:负载均衡器执行的output输出
public interface ExecutionListener<I, O> {

	// 在执行即将开始时调用。
	public void onExecutionStart(ExecutionContext<I> context) throws AbortExecutionException;
	// 当服务器被选中,请求**将在服务器上执行时**调用。
	public void onStartWithServer(ExecutionContext<I> context, ExecutionInfo info) throws AbortExecutionException;

	// 当在服务器上执行请求时接收到异常时调用(这时木有Response,只有异常信息)
	public void onExceptionWithServer(ExecutionContext<I> context, Throwable exception,  ExecutionInfo info);
	// 执行正常,有response
	public void onExecutionSuccess(ExecutionContext<I> context, O response,  ExecutionInfo info);
	// 在**所有重试**后认为请求失败时调用
	// finalException:最终的异常。有可能包装着重试时的各种异常
	public void onExecutionFailed(ExecutionContext<I> context, Throwable finalException, ExecutionInfo info);
}

对于该接口并么有任何内置实现,它是留给使用者的一个钩子。


ExecutionContextListenerInvoker

可以认为它是一个工具类:用于在执行上下文ExecutionContext上调用各种ExecutionListener们。功能上特别像大家熟悉的Spring Boot里的SpringApplicationRunListeners用于执行SpringApplicationRunListener监听器。

public class ExecutionContextListenerInvoker<I, O> {

	// 执行上下文
	private final ExecutionContext<I> context;
	// 执行时的监听器们
	private final List<ExecutionListener<I, O>> listeners;
	
	private final IClientConfig clientConfig;
	// key表示的是ExecutionListener实现类的全类名
	// value:IClientConfigKey。它的值是"listener." + key+ ".disabled"
	// 因为CommonClientConfigKey.valueOf()比较耗时,所以这里用了缓存
	private final ConcurrentHashMap<String, IClientConfigKey> classConfigKeyMap;
	
	... // 省略构造器
	
	
    public void onExecutionStart() {
        onExecutionStart(this.context);
    }
	public void onExecutionStart(ExecutionContext<I> context) {
		for (ExecutionListener<I, O> listener : listeners) {
			// 若这个Listener没有被禁用,那就执行它
			if (!isListenerDisabled(listener)) {
				// 请注意:这里的上下文使用的是子上下文哦
				// 所以保证了每个监听器均有一个自己的上下文,各有一份自己的数据,线程安全
				listener.onExecutionStart(context.getChildContext(listener));
			}
		}
	}
	
	... // 执行监听器其它方法的逻辑一毛一样,略

	// 判断一个监听器是否被禁用。可通过动态配置来开/关
	// "listener." + className + ".disabled":禁用
    private boolean isListenerDisabled(ExecutionListener<?, ?> listener) {
        if (clientConfig == null) {
            return false;
        } else {
            String className = listener.getClass().getName();
            IClientConfigKey key = classConfigKeyMap.get(className);
            if (key == null) {
                key = CommonClientConfigKey.valueOf("listener." + className + ".disabled");
                IClientConfigKey old = classConfigKeyMap.putIfAbsent(className, key);
                if (old != null) {
                    key = old;
                }
            }
            return clientConfig.getPropertyAsBoolean(key, false);
        }
    }
}
  1. 监听器是Ribbon留给开发者介入到负载均衡执行过程的钩子,比较重要(无任何内置实现)
  2. 每个Listener执行均由一个自己的上下文,线程安全
  3. 可通过外部化配置:"listener." + className + ".disabled"的方式禁用执行的监听器,设计上满足一定弹性

总结

关于Ribbon负载均衡命令:LoadBalancerCommand(一)基础类打点部分就先介绍到这。本文内容相对简单,主要为下文服务,敬请关注。
分隔线

声明

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

发布了377 篇原创文章 · 获赞 572 · 访问量 51万+
App 阅读领勋章
微信扫码 下载APP
阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: Age of Ai 设计师: meimeiellie

分享到微信朋友圈

×

扫一扫,手机浏览