目录
心跳任务(MemberInfoReportTask)
处理返回结果
接收集群节点上报请求(NacosClusterController )
处理上报请求(ServerMemberManager)
总结
简介: 前量章节中我们知道了集群列表是如何初始化,已经集群变化的感知是通过发布和监听MembersChangeEvent 完成的,但是集群服务器之间又是如何感知的呢。初始化的集群列表是一个静态的列表。要想实时知道集群服务器的状态就是通过本节要讲解的节点心跳任务主动上报本节点的信息的。
学习目标:了解节点是如何上报当前的节点信息。根据返回上报后的结果如何处理后续操作。及服务器是如何处理请求的。
public class ServerMemberManager implements ApplicationListener {
.....
// Synchronize the metadata information of a node
// A health check of the target node is also attached
class MemberInfoReportTask extends Task {
private final GenericType> reference = new GenericType>() {
};
//从服务器列表中第一个开始请求
private int cursor = 0;
@Override
protected void executeBody() {
List members = ServerMemberManager.this.allMembersWithoutSelf();
if (members.isEmpty()) {
return;
}
//逐个请求除自己之外的集群node 列表
this.cursor = (this.cursor + 1) % members.size();
Member target = members.get(cursor);
Loggers.CLUSTER.debug("report the metadata to the node : {}", target.getAddress());
//构建请求url
//http://ip:port/nacos/v1/core/cluster/report
final String url = HttpUtils
.buildUrl(false, target.getAddress(), EnvUtil.getContextPath(), Commons.NACOS_CORE_CONTEXT, "/cluster/report");
try {
// 构建头部参数 输入内容例如:
// Accept-Charset=UTF-8, Content-Type=application/json;charset=UTF-8,
// Nacos-Server=2.1.0
Header header = Header.newInstance().addParam(Constants.NACOS_SERVER_HEADER, VersionUtils.version);
AuthHeaderUtil.addIdentityToHeader(header);
//底层通过CloseableHttpAsyncClient发送异步请求
//请求方法POST 请求数据体 是 Member 对象
asyncRestTemplate.post(url, header, Query.EMPTY, getSelf(), reference.getType(), new Callback() {
@Override
public void onReceive(RestResult result) {
//这种情况是因为对方版本太低了
//需要清除target的远程连接能力
//清除version 的扩展数据
//目的是把集群降级到低版本
if (result.getCode() == HttpStatus.NOT_IMPLEMENTED.value() || result.getCode()
== HttpStatus.NOT_FOUND.value()) {
Loggers.CLUSTER.warn("{} version is too low, it is recommended to upgrade the version : {}", target, VersionUtils.version);
//拷贝新的memberNew 删除版本属性 重设远程连接的能力属性值false
Member memberNew = null;
if (target.getExtendVal(MemberMetaDataConstants.VERSION) != null) {
memberNew = target.copy();
// Clean up remote version info.
// This value may still stay in extend info when remote server has been downgraded to old version.
memberNew.delExtendVal(MemberMetaDataConstants.VERSION);
memberNew.delExtendVal(MemberMetaDataConstants.READY_TO_UPGRADE);
Loggers.CLUSTER.warn("{} : Clean up version info,"
+ " target has been downgrade to old version.", memberNew);
}
if (target.getAbilities() != null
&& target.getAbilities().getRemoteAbility() != null && target.getAbilities()
.getRemoteAbility().isSupportRemoteConnection()) {
if (memberNew == null) {
memberNew = target.copy();
}
memberNew.getAbilities().getRemoteAbility().setSupportRemoteConnection(false);
Loggers.CLUSTER.warn("{} : Clear support remote connection flag,target may rollback version ",
memberNew);
}
if (memberNew != null) {
//更新节点 发布ServerMemberChangeEvent事件
update(memberNew);
}
return;
}
if (result.ok()) {
//成功 如果之前的状态不是UP, 就改成UP 并发布集群变更事件
MemberUtil.onSuccess(ServerMemberManager.this, target);
} else {
Loggers.CLUSTER.warn("failed to report new info to target node : {}, result : {}",
target.getAddress(), result);
//失败 修改状态为SUSPICIOUS并记录失败次数达到一定的失败次数记录为DOWN状态 然后发布集群变更事件
MemberUtil.onFail(ServerMemberManager.this, target);
}
}
@Override
public void onError(Throwable throwable) {
Loggers.CLUSTER.error("failed to report new info to target node : {}, error : {}",
target.getAddress(), ExceptionUtil.getAllExceptionMsg(throwable));
MemberUtil.onFail(ServerMemberManager.this, target, throwable);
}
@Override
public void onCancel() {
}
});
} catch (Throwable ex) {
Loggers.CLUSTER.error("failed to report new info to target node : {}, error : {}", target.getAddress(),
ExceptionUtil.getAllExceptionMsg(ex));
}
}
//2秒后继续执行心跳上班任务
@Override
protected void after() {
GlobalExecutor.scheduleByCommon(this, 2_000L);
}
}
}
.....
}
public abstract class Task implements Runnable {
protected volatile boolean shutdown = false;
@Override
public void run() {
if (shutdown) {
return;
}
try {
executeBody();
} catch (Throwable t) {
Loggers.CORE.error("this task execute has error : {}", ExceptionUtil.getStackTrace(t));
} finally {
//任务执行结束后2秒后继续执行心跳上班任务
if (!shutdown) {
after();
}
}
}
}
ServerMemberManager在服务启动的时候会启动定时任务每隔2秒发送本节点的信息给集群内其他机器,发送的数据是当前节点的Member 对象序列化后的内容。通过post 的异步回调方式的给集群列表所有机器循环发送。
如果返回码包含NOT_IMPLEMENTED 或者 NOT_FOUND 说明对方的版本比较低,需要整个集群降级,关于自动升降级的内容,当前的教程不详细讲解。这个是涉及到集群的版本升级方面的内容。后面有机会在单独开篇。
其他的就是成功和失败处理异常的情况。分别触发MemberUtil的onSuccess 和 onFail方法
接下来我们看下这2个方法是如何处理的。
public class MemberUtil {
//心跳对方返回成功 恢复失败次数为0 如果之前状态不是UP 修改为UP 并发布变更事件
//注意元数据的变更及能力的变更不是这里触发的
//心跳只返回成功或者失败 不会返回target的信息
//心跳把自己的信息推送给target
//最后如果状态有变更发布变更事件
public static void onSuccess(final ServerMemberManager manager, final Member member) {
final NodeState old = member.getState();
manager.getMemberAddressInfos().add(member.getAddress());
member.setState(NodeState.UP);
member.setFailAccessCnt(0);
if (!Objects.equals(old, member.getState())) {
manager.notifyMemberChange(member);
}
}
//
public static void onFail(final ServerMemberManager manager, final Member member) {
onFail(manager, member, ExceptionUtil.NONE_EXCEPTION);
}
//如果失败次数超过3次 设置状态为DOWN 否则是SUSPICIOUS
//失败次数校验值可以通过配置属性[nacos.core.member.fail-access-cnt]设置
//如果请求的返回移除是connect refused 的关键字说明对方已经挂了直接设置为DOWN
//最后如果状态有变更发布变更事件
public static void onFail(final ServerMemberManager manager, final Member member, Throwable ex) {
//从健康列表中移除该地址
manager.getMemberAddressInfos().remove(member.getAddress());
final NodeState old = member.getState();
member.setState(NodeState.SUSPICIOUS)
member.setFailAccessCnt(member.getFailAccessCnt() + 1);
int maxFailAccessCnt = EnvUtil.getProperty(MEMBER_FAIL_ACCESS_CNT_PROPERTY, Integer.class, DEFAULT_MEMBER_FAIL_ACCESS_CNT);
// If the number of consecutive failures to access the target node reaches
// a maximum, or the link request is rejected, the state is directly down
if (member.getFailAccessCnt() > maxFailAccessCnt || StringUtils
.containsIgnoreCase(ex.getMessage(), TARGET_MEMBER_CONNECT_REFUSE_ERRMSG)) {
member.setState(NodeState.DOWN);
}
if (!Objects.equals(old, member.getState())) {
manager.notifyMemberChange(member);
}
}
....
}
详细的讲解已经注释到代码中。大家结合代码理解。
需要注意一点的是这里只是更新集群的活跃状态 并不会更新集群的元数据信息
服务器收到上报信息后简单校验一下ip 和 port 是否都存在且 port是否是 -1
设置远程节点的状态是UP,整个跟上面的处理返回成功结果的逻辑是一样的。
接下来是更新该节点的其他信息包括远程连接的支持属性等。
public class NacosClusterController {
....
/**
* Other nodes return their own metadata information.
*
* @param node {@link Member}
* @return {@link RestResult}
*/
@PostMapping(value = {"/report"})
public RestResult report(@RequestBody Member node) {
//注意检测 ip port 不为空及 port 不等于 -1
if (!node.check()) {
return RestResultUtils.failedWithMsg(400, "Node information is illegal");
}
//说明节点是获得 状态设置为UP 充值失败次数为0
node.setState(NodeState.UP);
node.setFailAccessCnt(0);
//把远程的节点信息更新本地的节点包括元数据等
boolean result = memberManager.update(node);
return RestResultUtils.success(Boolean.toString(result));
}
....
}
public class ServerMemberManager implements ApplicationListener {
.....
//更新节点信息
public boolean update(Member newMember) {
Loggers.CLUSTER.debug("member information update : {}", newMember);
//如果节点不存在就返回
String address = newMember.getAddress();
if (!serverList.containsKey(address)) {
return false;
}
serverList.computeIfPresent(address, (s, member) -> {
//节点下线从健康地址列表[memberAddressInfos]删除该地址
if (NodeState.DOWN.equals(newMember.getState())) {
memberAddressInfos.remove(newMember.getAddress());
}
//
boolean isPublishChangeEvent = MemberUtil.isBasicInfoChanged(newMember, member);
//LAST_REFRESH_TIME 这个字段目前么有地方使用到
newMember.setExtendVal(MemberMetaDataConstants.LAST_REFRESH_TIME, System.currentTimeMillis());
//更新本地的member对象
MemberUtil.copy(newMember, member);
//信息有变化触发相应的监听器[不记得的同学翻看上一篇]
if (isPublishChangeEvent) {
notifyMemberChange(member);
}
return member;
});
return true;
}
.....
}
public class MemberUtil {
//基本信息变更
public static boolean isBasicInfoChanged(Member actual, Member expected) {
if (null == expected) {
return null == actual;
}
if (!expected.getIp().equals(actual.getIp())) {
return true;
}
if (expected.getPort() != actual.getPort()) {
return true;
}
if (!expected.getAddress().equals(actual.getAddress())) {
return true;
}
if (!expected.getState().equals(actual.getState())) {
return true;
}
if (!expected.getAbilities().equals(actual.getAbilities())) {
return true;
}
return isBasicInfoChangedInExtendInfo(expected, actual);
}
//扩展字段变更
private static boolean isBasicInfoChangedInExtendInfo(Member expected, Member actual) {
for (String each : MemberMetaDataConstants.BASIC_META_KEYS) {
if (expected.getExtendInfo().containsKey(each) != actual.getExtendInfo().containsKey(each)) {
return true;
}
if (!Objects.equals(expected.getExtendVal(each), actual.getExtendVal(each))) {
return true;
}
}
return false;
}
}
首先如果节点状态时DOWN 那么从健康实例列表中移除该节点。否则判断是否有基本信息变更,有变更就发布集群变更事件,同时更新本地的节点信息。
通过本节内容我们知道了集群节点通过信息上报任务的方式根据返回结果更新节点的状态信息,作为请求的接收方及时更新本地集群的节点信息。
从整个集群从节点的初始化 到 实时的节点信息更新 再到监听集群变更事件的处理。服务端集群最核心的功能就讲解完毕了。