温馨提示:

本文内容基于个人学习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. WordPress安装篇(2):用宝塔面板在Windows上安装WordPress

    上一篇文章介绍了如何使用PHPStudy工具在Windows Server环境安装WordPress,接下来介绍一款更加强大的部署WordPress的集成工具--宝塔面板.宝塔面板不仅提供免费版本,还 ...

  2. 【NX二次开发】NX对象类型及基本操作

    说明:NX中的所有对象都是通过唯一的tag_t值进行标识的,这些对象大致可以分为部件对象.UF对象.表达式.链表对象和属性对象等. 部件对象的操作: 基本操作函数: 1. UF_PART_new()  ...

  3. 大厂面试必问!HashMap 怎样解决hash冲突?

    HashMap冲突解决方法比较考验一个开发者解决问题的能力. 下文给出HashMap冲突的解决方法以及原理分析,无论是在面试问答或者实际使用中,应该都会有所帮助. 在Java编程语言中,最基本的结构就 ...

  4. google protobuf的原理和思路提炼

    之前其实已经用了5篇文章完整地分析了protobuf的原理.回过头去看,感觉一方面篇幅过大,另一方面过于追求细节和源码,对protobuf的初学者并不十分友好,因此这篇文章将会站在"了解.使 ...

  5. 有趣的开源项目集结完毕,HelloGitHub 月刊第 63 期发布啦!

    兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 分享 GitHub 上有趣.入门级的开源项目. 这里有实战项目.入门教程.黑科技.开源书籍.大厂开源项目等,涵盖多种编程语言 Pyt ...

  6. Spring Boot 2.x基础教程:使用Redis的发布订阅功能

    通过前面一篇集中式缓存的使用教程,我们已经了解了Redis的核心功能:作为K.V存储的高性能缓存. 接下来我们会分几篇来继续讲讲Redis的一些其他强大用法!如果你对此感兴趣,一定要关注收藏我哦! 发 ...

  7. 基于SpringBoot 、AOP与自定义注解转义字典值

    一直以来,前端展示字典一般以中文展示为主,若在表中存字典值中文,当字典表更改字典值对应的中文,会造成数据不一致,为此设置冗余字段并非最优方案,若由前端自己写死转义,不够灵活,若在业务代码转义,臃肿也不 ...

  8. excel匹配函数vlookup和lookup

    1.vlookup(查找的条件,查找的区域,满足查找条件后需要返回的值在选中的查找区域的第几列,精确匹配还是近似匹配(精确匹配为0或False表示,反之为1或True)) =VLOOKUP(J2,$G ...

  9. excel函数sum、sumif和sumifs

    1.sum(a1,a2,a2,...a10)或sum(a1:a10)求a1到a10的和 2.sumif(条件区域,指定的求和条件,求和的区域) =SUMIF($F$2:$F$7,J2,$H$2:$H$ ...

  10. Ubuntu 之 Esc and CapsLK

    Ubuntu 更换 CapsLK 和 ESC 内容如下 // vim .xmodmap !! No Caps Lock clear lock !! Make Caps_lock an escape k ...