温馨提示:

本文内容基于个人学习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. 1.5w字 + 24张图肝翻 TCP。

    TCP 是一种面向连接的单播协议,在 TCP 中,并不存在多播.广播的这种行为,因为 TCP 报文段中能明确发送方和接受方的 IP 地址. 在发送数据前,相互通信的双方(即发送方和接受方)需要建立一条 ...

  2. HR面试:过五关斩六将后,小心阴沟翻船!(史上最全、避坑宝典)

    文章很长,建议收藏起来,慢慢读! 疯狂创客圈为小伙伴奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : <Netty Zookeeper Redis 高并发实战> 面试必备 + 大厂必备 ...

  3. CMake 两种变量原理

    目录 [TOC] 1.两种变量的定义参考 2.两种变量的作用域原理及使用 1.Normal Variables (1).包含 add_subdirectory().function().(本质是值拷贝 ...

  4. 深入理解Linux文件系统与日志分析

    一.inode和bolck概述 二.链接文件 三.inode节点耗尽故障处理 四.EXT类型文件恢复 五.xfs文件备份和恢复 六.日志文件 一.inode和bolck概述 1.定义 文件数据 文件数 ...

  5. k8s通过ceph-csi接入存储的概要分析

    kubernetes ceph-csi分析目录导航 概述 下面的分析是k8s通过ceph-csi(csi plugin)接入ceph存储(csi相关组件的分析以rbd为例进行分析),对csi系统结构. ...

  6. 『心善渊』Selenium3.0基础 — 15、Selenium对多窗口的操作

    目录 1.多标签/多窗口之间的切换 2.句柄练习 1.多标签/多窗口之间的切换 (1)多标签/多窗口场景: 在页面操作过程中有时候点击某个链接会弹出新的窗口,这时就需要切换到新打开的窗口上进行操作,如 ...

  7. 用 .SqlSugar ORM 来实现报表功能 .NET CORE /.NET

    架框介绍 SqlSugar是一款.NET老牌ORM 并且也是 新手基数比较多的ORM(因为上手容易),SqlSugar之所以能一直更新到现在,还是要感谢SqlSugar的忠实用户,随着我的技术越来越好 ...

  8. InterlliJ Debug启动提示:Method breakpoints may dramatically slow down debugging

  9. 信息论估计工具jidt基本使用

    JIDT基本介绍 JIDT是 Java Information Dynamics Toolkit的简称,用于研究复杂系统中信息论相关度量的计算,它是一个基于java的开源工具库,也可以在Matlab. ...

  10. macos 安装telnet命令

    在10.12及以下版本,都内置了telnet命令,但是在10.13中,已经取消了 接下来给大家介绍下如何安装telnet命令 打开"终端",输入: /usr/bin/ruby -e ...