温馨提示:

本文内容基于个人学习Nacos 2.0.1版本代码总结而来,因个人理解差异,不保证完全正确。如有理解错误之处欢迎各位拍砖指正,相互学习;转载请注明出处。

Nacos服务端在处理健康检查和心跳检查任务的时候它是使用拦截器链来执行的。拦截器链内部有多个拦截器,通过获取不同的拦截器链实例,在实例内部指定具体的拦截器类型来组成一组拦截器。这里使用了拦截器模式和模板模式来组织代码。拦截器模式体现在整体拦截机制的实现;模板模式主要体现在对拦截器链的抽象实现上。

拦截器模式有三个要素

  • 拦截器
  • 调度者
  • 业务逻辑

拦截器

定义一个拦截器的基本功能,同时限定了传入的拦截对象类型必须为Interceptable。这里只定义了基本的功能和基本的限定拦截对象。这里将其描述为基本的功能,那就意味着它的实现将会有更高级的功能。

/**
* Nacos naming interceptor.
* 拦截器对象
* @author xiweng.yy
*/
public interface NacosNamingInterceptor<T extends Interceptable> { /**
* Judge whether the input type is intercepted by this Interceptor.
* 此拦截器的实例将会判断传入的对象是否是他需要处理的类型,此方法可以实现不同拦截器处理不同对象的隔离操作
* <p>This method only should judge the object type whether need be do intercept. Not the intercept logic.
* @param type type
* @return true if the input type is intercepted by this Interceptor, otherwise false
*/
boolean isInterceptType(Class<?> type); /**
* Do intercept operation.
* 执行拦截操作
* <p>This method is the actual intercept operation.
* @param object need intercepted object
* @return true if object is intercepted, otherwise false
*/
boolean intercept(T object); /**
* The order of interceptor. The lower the number, the earlier the execution.
* 拦截器排序,数字越低,优先级越高
* @return the order number of interceptor
*/
int order();
}

被拦截的对象

Interceptable 定义了对拦截操作相关的执行方法,passIntercept()在未被拦截的时候需要执行,afterIntercept()在被拦截之后需要执行。被拦截对象的业务逻辑需要由拦截器负责调度。

/**
* Interceptable Interface.
*
* @author xiweng.yy
*/
public interface Interceptable { /**
* If no {@link NacosNamingInterceptor} intercept this object, this method will be called to execute.
*/
void passIntercept(); /**
* If one {@link NacosNamingInterceptor} intercept this object, this method will be called.
*/
void afterIntercept();
}

调度者

调度者主要是用来管理拦截器的组织方式,触发拦截器的拦截操作。下图展示了Naming模块的拦截器链的继承关系。

整体的构成由NacosNamingInterceptorChain定义基本框架,AbstractNamingInterceptorChain实现通用逻辑,HealthCheckInterceptorChainInstanceBeatCheckTaskInterceptorChain则分别服务于健康检查和心跳检查。

NacosNamingInterceptorChain

定义了拦截器链对象应该具有的基本行为:添加拦截器、执行拦截器。

/**
* Nacos naming interceptor chain.
* Nacos Naming模块的拦截器链接口,拦截器链用于存储并管理多个拦截器
* @author xiweng.yy
*/
public interface NacosNamingInterceptorChain<T extends Interceptable> { /**
* Add interceptor.
* 添加指定类型的拦截器对象
* @param interceptor interceptor
*/
void addInterceptor(NacosNamingInterceptor<T> interceptor); /**
* Do intercept by added interceptors.
* 执行拦截的业务操作
* @param object be interceptor object
*/
void doInterceptor(T object);
}

AbstractNamingInterceptorChain

AbstractNamingInterceptorChain实现了NacosNamingInterceptorChain所定义的对NacosNamingInterceptor的操作。在构造方法中提供了具体的拦截器实现类的加载,它这里使用了SPI方式加载。默认可以加载的拦截器必须是NacosNamingInterceptor的实例。在拦截器的执行方法doInterceptor()中会按优先级调用每一个拦截器,首先判断被拦截的对象是否是此拦截器处理,接着调用拦截器的intercept()方法,成功后调用被拦截对象的afterIntercept()方法。若未拦截成功则调用被拦截对象的passIntercept()方法。因此在拦截器中的intercept()方法中可以定义拦截器对被拦截对象的处理逻辑,而被拦截对象则可以在afterIntercept()和passIntercept()方法中定义自身的处理逻辑。从而实现在拦截器中被处理和自身处理任务依赖于拦截器来触发。

/**
* Abstract Naming Interceptor Chain.
* 抽象的命名服务拦截器链,用于定义拦截器链的工作流程
* @author xiweng.yy
*/
public abstract class AbstractNamingInterceptorChain<T extends Interceptable> implements NacosNamingInterceptorChain<T> { // 存储多个拦截器
private final List<NacosNamingInterceptor<T>> interceptors; // 限制使用范围为当前包或者其子类
protected AbstractNamingInterceptorChain(Class<? extends NacosNamingInterceptor<T>> clazz) {
this.interceptors = new LinkedList<>();
// 使用SPI模式加载指定的拦截器类型
// 而且NacosNamingInterceptor内部有判断它需要拦截对象的类型,因此非常灵活
interceptors.addAll(NacosServiceLoader.load(clazz));
// 对拦截器的顺序进行排序
interceptors.sort(Comparator.comparingInt(NacosNamingInterceptor::order));
} /**
* Get all interceptors.
*
* @return interceptors list
*/
protected List<NacosNamingInterceptor<T>> getInterceptors() {
return interceptors;
} @Override
public void addInterceptor(NacosNamingInterceptor<T> interceptor) {
// 若手动添加,则需要再次进行排序
interceptors.add(interceptor);
interceptors.sort(Comparator.comparingInt(NacosNamingInterceptor::order));
} @Override
public void doInterceptor(T object) {
// 因为内部的拦截器已经排序过了,所以直接遍历
for (NacosNamingInterceptor<T> each : interceptors) {
// 若当前拦截的对象不是当前拦截器所要处理的类型则调过
if (!each.isInterceptType(object.getClass())) {
continue;
}
// 执行拦截操作成功之后,继续执行拦截后操作
if (each.intercept(object)) {
object.afterIntercept();
return;
}
}
// 未拦截的操作
object.passIntercept();
}
}

doInterceptor() 方法中使用当前拦截器链内部的所有拦截器对被拦截对象进行处理,并且组织了被拦截对象被拦截之后的方法调用流程。即:拦截之后执行被拦截对象的afterIntercept()方法,未拦截时执行passIntercept()方法。

HealthCheckInterceptorChain

健康检查拦截器链负责加载AbstractHealthCheckInterceptor类型的拦截器。

/**
* Health check interceptor chain.
* @author xiweng.yy
*/
public class HealthCheckInterceptorChain extends AbstractNamingInterceptorChain<NacosHealthCheckTask> { private static final HealthCheckInterceptorChain INSTANCE = new HealthCheckInterceptorChain(); private HealthCheckInterceptorChain() {
super(AbstractHealthCheckInterceptor.class);
} public static HealthCheckInterceptorChain getInstance() {
return INSTANCE;
}
}

InstanceBeatCheckTaskInterceptorChain

实例心跳检查器链负责加载AbstractBeatCheckInterceptor类型的拦截器。

/**
* Instance beat check interceptor chain.
*
* @author xiweng.yy
*/
public class InstanceBeatCheckTaskInterceptorChain extends AbstractNamingInterceptorChain<InstanceBeatCheckTask> { private static final InstanceBeatCheckTaskInterceptorChain INSTANCE = new InstanceBeatCheckTaskInterceptorChain(); private InstanceBeatCheckTaskInterceptorChain() {
super(AbstractBeatCheckInterceptor.class);
} public static InstanceBeatCheckTaskInterceptorChain getInstance() {
return INSTANCE;
}
}

小结

通过模板模式来实现拦截器机制。

  • AbstractNamingInterceptorChain 抽象出连接器链对拦截器加载的通用方法,定义了拦截器对被拦截对象的通用处理流程。
  • AbstractHealthCheckInterceptor 定义了健康检查拦截器被拦截的对象类型
  • AbstractBeatCheckInterceptor 定义了心跳检查拦截器被拦截的对象类型

通过对拦截器链的组织方式梳理可以看到有明显的两条路线,一个是健康检查,一个是心跳检查。分析后续具体的拦截器,以及他们所要处理的任务就很清晰了。

业务逻辑

业务逻辑是被拦截器拦截之后需要进行的操作。

健康检查类的被拦截对象

健康检查的抽象拦截器AbstractHealthCheckInterceptor定义了它的子类将要处理的任务类型为NacosHealthCheckTask

HealthCheckTaskV2

/**
* Health check task for v2.x.
* v2版本的健康检查
* <p>Current health check logic is same as v1.x. TODO refactor health check for v2.x.
*
* @author nacos
*/
public class HealthCheckTaskV2 extends AbstractExecuteTask implements NacosHealthCheckTask { /**
* 一个客户端对象(此客户端代表提供服务用于被应用访问的客户端)
* 从这里可以看出,启动一个健康检查任务是以客户端为维度的
*/
private final IpPortBasedClient client; private final String taskId; private final SwitchDomain switchDomain; private final NamingMetadataManager metadataManager; private long checkRtNormalized = -1;
/**
* 检查最佳响应时间
*/
private long checkRtBest = -1; /**
* 检查最差响应时间
*/
private long checkRtWorst = -1; /**
* 检查上次响应时间
*/
private long checkRtLast = -1; /**
* 检查上上次响应时间
*/
private long checkRtLastLast = -1; /**
* 开始时间
*/
private long startTime; /**
* 任务是否取消
*/
private volatile boolean cancelled = false; public HealthCheckTaskV2(IpPortBasedClient client) {
this.client = client;
this.taskId = client.getResponsibleId();
this.switchDomain = ApplicationUtils.getBean(SwitchDomain.class);
this.metadataManager = ApplicationUtils.getBean(NamingMetadataManager.class);
// 初始化响应时间检查
initCheckRT();
} /**
* 初始化响应时间值
*/
private void initCheckRT() {
// first check time delay
// 2000 + (在5000以内的随机数)
checkRtNormalized =
2000 + RandomUtils.nextInt(0, RandomUtils.nextInt(0, switchDomain.getTcpHealthParams().getMax()));
// 最佳响应时间
checkRtBest = Long.MAX_VALUE;
// 最差响应时间为0
checkRtWorst = 0L;
} public IpPortBasedClient getClient() {
return client;
} @Override
public String getTaskId() {
return taskId;
} /**
* 开始执行健康检查任务
*/
@Override
public void doHealthCheck() {
try {
// 获取当前传入的Client所发布的所有Service
for (Service each : client.getAllPublishedService()) {
// 只有当Service开启了健康检查才执行
if (switchDomain.isHealthCheckEnabled(each.getGroupedServiceName())) {
// 获取Service对应的InstancePublishInfo
InstancePublishInfo instancePublishInfo = client.getInstancePublishInfo(each);
// 获取集群元数据
ClusterMetadata metadata = getClusterMetadata(each, instancePublishInfo);
// 使用Processor代理对象对任务进行处理
ApplicationUtils.getBean(HealthCheckProcessorV2Delegate.class).process(this, each, metadata);
if (Loggers.EVT_LOG.isDebugEnabled()) {
Loggers.EVT_LOG.debug("[HEALTH-CHECK] schedule health check task: {}", client.getClientId());
}
}
}
} catch (Throwable e) {
Loggers.SRV_LOG.error("[HEALTH-CHECK] error while process health check for {}", client.getClientId(), e);
} finally {
// 若任务执行状态为已取消,则再次启动
if (!cancelled) {
HealthCheckReactor.scheduleCheck(this);
// worst == 0 means never checked
if (this.getCheckRtWorst() > 0) {
// TLog doesn't support float so we must convert it into long
long checkRtLastLast = getCheckRtLastLast();
this.setCheckRtLastLast(this.getCheckRtLast());
if (checkRtLastLast > 0) {
long diff = ((this.getCheckRtLast() - this.getCheckRtLastLast()) * 10000) / checkRtLastLast;
if (Loggers.CHECK_RT.isDebugEnabled()) {
Loggers.CHECK_RT.debug("{}->normalized: {}, worst: {}, best: {}, last: {}, diff: {}",
client.getClientId(), this.getCheckRtNormalized(), this.getCheckRtWorst(),
this.getCheckRtBest(), this.getCheckRtLast(), diff);
}
}
}
}
}
} @Override
public void passIntercept() {
doHealthCheck();
} @Override
public void afterIntercept() {
// 若任务执行状态为已取消,则再次启动
if (!cancelled) {
HealthCheckReactor.scheduleCheck(this);
}
} @Override
public void run() {
// 调用健康检查
doHealthCheck();
} /**
* 获取集群元数据
* @param service 服务信息
* @param instancePublishInfo 服务对应的ip等信息
* @return
*/
private ClusterMetadata getClusterMetadata(Service service, InstancePublishInfo instancePublishInfo) {
Optional<ServiceMetadata> serviceMetadata = metadataManager.getServiceMetadata(service);
if (!serviceMetadata.isPresent()) {
return new ClusterMetadata();
}
String cluster = instancePublishInfo.getCluster();
ClusterMetadata result = serviceMetadata.get().getClusters().get(cluster);
return null == result ? new ClusterMetadata() : result;
} public long getCheckRtNormalized() {
return checkRtNormalized;
} public long getCheckRtBest() {
return checkRtBest;
} public long getCheckRtWorst() {
return checkRtWorst;
} public void setCheckRtWorst(long checkRtWorst) {
this.checkRtWorst = checkRtWorst;
} public void setCheckRtBest(long checkRtBest) {
this.checkRtBest = checkRtBest;
} public void setCheckRtNormalized(long checkRtNormalized) {
this.checkRtNormalized = checkRtNormalized;
} public boolean isCancelled() {
return cancelled;
} public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
} public long getStartTime() {
return startTime;
} public void setStartTime(long startTime) {
this.startTime = startTime;
} public long getCheckRtLast() {
return checkRtLast;
} public void setCheckRtLast(long checkRtLast) {
this.checkRtLast = checkRtLast;
} public long getCheckRtLastLast() {
return checkRtLastLast;
} public void setCheckRtLastLast(long checkRtLastLast) {
this.checkRtLastLast = checkRtLastLast;
}
}

心跳检查类的被拦截对象

ClientBeatCheckTaskV2

虽然它继承了NacosHealthCheckTask,但内部只使用了InstanceBeatCheckTaskInterceptorChain,没有使用HealthCheckInterceptorChain, 按理说应该划分到"心跳检查类的被拦截对象" 这个类别的。不知道为何这样设计,已提issues。

/**
* Client beat check task of service for version 2.x.
* @author nkorange
*/
public class ClientBeatCheckTaskV2 extends AbstractExecuteTask implements BeatCheckTask, NacosHealthCheckTask { private final IpPortBasedClient client; private final String taskId; /**
* 使用拦截器链
*/
private final InstanceBeatCheckTaskInterceptorChain interceptorChain; public ClientBeatCheckTaskV2(IpPortBasedClient client) {
this.client = client;
this.taskId = client.getResponsibleId();
this.interceptorChain = InstanceBeatCheckTaskInterceptorChain.getInstance();
} public GlobalConfig getGlobalConfig() {
return ApplicationUtils.getBean(GlobalConfig.class);
} @Override
public String taskKey() {
return KeyBuilder.buildServiceMetaKey(client.getClientId(), String.valueOf(client.isEphemeral()));
} @Override
public String getTaskId() {
return taskId;
} @Override
public void doHealthCheck() { try {
// 获取所有的Service
Collection<Service> services = client.getAllPublishedService();
for (Service each : services) {
logger.info("开始对Service进行拦截操作,{}", each.getName());
// 获取Service对应的InstancePublishInfo
HealthCheckInstancePublishInfo instance = (HealthCheckInstancePublishInfo) client.getInstancePublishInfo(each);
// 创建一个InstanceBeatCheckTask,并交由拦截器链处理
interceptorChain.doInterceptor(new InstanceBeatCheckTask(client, each, instance));
} } catch (Exception e) {
Loggers.SRV_LOG.warn("Exception while processing client beat time out.", e);
}
} @Override
public void run() {
doHealthCheck();
} @Override
public void passIntercept() {
doHealthCheck();
} @Override
public void afterIntercept() {
}
}

InstanceBeatCheckTask

/**
* Instance beat check task.
* Instance心跳检查任务,此处它作为一个可被拦截器拦截的对象使用。
* @author xiweng.yy
*/
public class InstanceBeatCheckTask implements Interceptable { // 心跳检查者列表
private static final List<InstanceBeatChecker> CHECKERS = new LinkedList<>(); // 客户端对象(因为实例就代表的是客户端)
private final IpPortBasedClient client; // 服务对象
private final Service service; // 健康检查信息
private final HealthCheckInstancePublishInfo instancePublishInfo; static {
// 添加不健康实例检查器
CHECKERS.add(new UnhealthyInstanceChecker());
// 添加过期实例检查器
CHECKERS.add(new ExpiredInstanceChecker());
// 添加用户自定义的心跳检查器
CHECKERS.addAll(NacosServiceLoader.load(InstanceBeatChecker.class));
} public InstanceBeatCheckTask(IpPortBasedClient client, Service service, HealthCheckInstancePublishInfo instancePublishInfo) {
this.client = client;
this.service = service;
this.instancePublishInfo = instancePublishInfo;
} @Override
public void passIntercept() {
// 未被拦截的时候执行自身逻辑
for (InstanceBeatChecker each : CHECKERS) {
each.doCheck(client, service, instancePublishInfo);
}
} @Override
public void afterIntercept() {
} public IpPortBasedClient getClient() {
return client;
} public Service getService() {
return service;
} public HealthCheckInstancePublishInfo getInstancePublishInfo() {
return instancePublishInfo;
}
}

总结

  • 拦截器链确定了要加载的拦截器类型
  • 拦截器确定了要拦截的对象类型
  • 被拦截的对象又建立了自己的检查策略

Nacos 2.0源码分析-拦截器机制的更多相关文章

  1. Spring AOP 源码分析 - 拦截器链的执行过程

    1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...

  2. springMVC源码分析--拦截器HandlerExecutionChain(三)

    上一篇博客springMVC源码分析--HandlerInterceptor拦截器调用过程(二)中我们介绍了HandlerInterceptor的执行调用地方,最终HandlerInterceptor ...

  3. Struts2 源码分析——拦截器的机制

    本章简言 上一章讲到关于action代理类的工作.即是如何去找对应的action配置信息,并执行action类的实例.而这一章笔者将讲到在执行action需要用到的拦截器.为什么要讲拦截器呢?可以这样 ...

  4. Struts2 源码分析-----拦截器源码解析 --- ParametersInterceptor

    ParametersInterceptor拦截器其主要功能是把ActionContext中的请求参数设置到ValueStack中,如果栈顶是当前Action则把请求参数设置到了Action中,如果栈顶 ...

  5. (一) Mybatis源码分析-解析器模块

    Mybatis源码分析-解析器模块 原创-转载请说明出处 1. 解析器模块的作用 对XPath进行封装,为mybatis-config.xml配置文件以及映射文件提供支持 为处理动态 SQL 语句中的 ...

  6. Solr5.0源码分析-SolrDispatchFilter

    年初,公司开发法律行业的搜索引擎.当时,我作为整个系统的核心成员,选择solr,并在solr根据我们的要求做了相应的二次开发.但是,对solr的还没有进行认真仔细的研究.最近,事情比较清闲,翻翻sol ...

  7. 3 cocos2dx 3.0 源码分析-mainLoop详细

    简述:   我靠上面图是不是太大了, 有点看不清了.  总结一下过程: 之前说过的appController 之后经过了若干初始化, 最后调用了displayLinker 的定时调用, 这里调用了函数 ...

  8. AFNetWorking3.0源码分析

    分析: AFNetWorking(3.0)源码分析(一)——基本框架 AFNetworking源码解析 AFNetworking2.0源码解析<一> end

  9. Solr4.8.0源码分析(25)之SolrCloud的Split流程

    Solr4.8.0源码分析(25)之SolrCloud的Split流程(一) 题记:昨天有位网友问我SolrCloud的split的机制是如何的,这个还真不知道,所以今天抽空去看了Split的原理,大 ...

随机推荐

  1. Linux CentOS 配置Yaf框架

    简介 Yaf框架想必大家都有所了解,它是一个开源的高性能的PHP框架 官网地址:https://www.php.net/manual/zh/book.yaf.php Yaf开发文档:https://w ...

  2. 【模拟8.01】string(线段树)

    因为题中只有a-z,所以区间中大量字母都是重复的,我们不妨利用桶的性质. 开一棵树,里面维护当前区间内的相同元素,若区间内元素不同,则为零 每次升序操作就先查询一遍区间,用桶将每个区间的a-z元素统计 ...

  3. C#设计模式学习之装饰者模式

    写这个随笔时,其实对该模式理解的并不是十分透彻.在此想到什么写什么,希望对自己对他人有所帮助. 装饰者模式主要是应用继承和组合的思想,极大的实现了程序的多态,使得的程序有了更高的扩展性. 第一个基础例 ...

  4. css--flex弹性布局详解和使用

    前言 前端开发最基础的能力是根据 ui 设计稿迅速还原页面,拿到设计稿不要急于写代码,首先要对页面进行分析,对页面的整体布局有个大概的了解,然后先实现一个整体的布局,再把布局拆分成逐个小模块,逐个去实 ...

  5. Python基础之:Python的数据结构

    目录 简介 列表 列表作为栈使用 列表作为队列使用 列表推导式 del 元组 集合 字典 循环 简介 不管是做科学计算还是编写应用程序,都需要使用到一些基本的数据结构,比如列表,元组,字典等. 本文将 ...

  6. 快速了解ARP

    目录 前言 一.MAC 1.MAC地址三种帧 二.ARP 1.五种ARP 三.ARP老化 四.什么时候会发送免费ARP 五.代理ARP 六.ARP欺骗 总结 前言 分别介绍MAC地址和五种ARP报文 ...

  7. 44、wget和curl的常用参数

    1.wget: wget是文件下载的工具: 不加任何参数是直接下载该文件: (1)-O: 将下载的文件指定为特定的文件名: wget -O baidu.html www.baidu.com --201 ...

  8. 【PC桌面软件的末日,手机移动端App称王】写在windows11支持安卓,macOS支持ios,龙芯支持x86和arm指令翻译

    面对这场突如其来的变革,作为软件开发者,应该如何选择自己今后的发展方向?桌面软件开发领域还有前景吗? 起源 自从苹果发布m1处理器,让自家Mac支持IOS移动端app运行之后,彻底打破了移动端app和 ...

  9. POJ 2299 Ultra-QuickSort 求逆序数 线段树或树状数组 离散化

    我用的线段树写的. num数组表示已插入的数值的个数. 由于a[i]数值很大,但是n不是很大,所以要离散化处理 9 1 0 5 4 离散化后 4 1 0 3 2 这样保证最大值不会超过n #inclu ...

  10. 学会这些CSS技巧让你写样式更加丝滑

    目录 1,前言 1,calc() 2,min() 3,max() 4,clamp() 5,gap 6,writing-mode 1,前言 记录一些很好用的css属性 1,calc() calc()函数 ...