版本:1.3.1
前端节点列表通过接口/nacos/v1/core/cluster/nodes获取集群数据进行展示,但是获取到的数据是错误的,他是从ServerMemberManager.serverList这一快照中获取数据,定时任务ServerInfoUpdater每2s对一个节点的数据进行更新,除了自身节点(当然这部分可能也有一部分问题),ServerInfoUpdater通过调用如下代码进行更新:
memberManager.update(target);
```java
public boolean update(Member newMember) {
...
serverList.computeIfPresent(address, (s, member) -> {
if (!MemberUtils.fullEquals(newMember, member)) {
newMember.setExtendVal(MemberMetaDataConstants.LAST_REFRESH_TIME, System.currentTimeMillis());
MemberUtils.copy(newMember, member);
// member data changes and all listeners need to be notified
NotifyCenter.publishEvent(MembersChangeEvent.builder().members(allMembers()).build());
}
...
}
该方法先判断旧数据是否与新数据一致,如果不一致,则进行更新,否则不做任何操作,考察`MemberUtils.fullEquals(Member, Member)`方法:
```java
public static boolean fullEquals(Member actual, Member expected) {
...
return equalsExtendInfo(expected, actual);
}
public static final String[] META_KEY_LIST_WITHOUT_LAST_REFRESH_TIME = new String[] {SITE_KEY, AD_WEIGHT, RAFT_PORT,
WEIGHT, VERSION};
private static boolean equalsExtendInfo(Member expected, Member actual) {
for (String each : MemberMetaDataConstants.META_KEY_LIST_WITHOUT_LAST_REFRESH_TIME) {
...
}
return true;
}
扩展字段并没有完全得到判断,只判断了基础字段,而raft的相关的状态信息,存在了扩展字段的naming字段中。
@Math312 所以这里的MemberUtils.fullEquals(Member, Member),其实还是需要根据Map中的"naming"key对应的value来进行是否equals的判断的。我打印了下naming作为扩展字段对应的value如下:
"naming":
{"voteFor":"172.20.21.132:8848",
"ip":"172.20.21.132:8848",
"heartbeatDueMs":5000,
"term":3,
"leaderDueMs":17376,
"state":"LEADER"},
个人理解,应该只是选择上面"naming"key对应的部分value值进行判断的。 @KomachiSion @Math312 @chuntaojun
这块其实还需要好好的规划下,比如Member的equals 和 hashcode是不是能够替代这个工具类,Raft的元数据是否也可以生产POJO然后在POJO里的equals和hashcode去做文章。
否则这个工具类随着服务节点原数据越来越多,可能会变得很复杂很耦合
@KomachiSion 嗯嗯,同意这个想法,找时间可以规划 +讨论下;
agree
个人觉得,这种情况不应该专"款"专用么,每个逻辑处理自己对对应字段,也避免了冲突,例如这个部分使用的是naming,我们就专门处理这一个字段的校验等等,做一个ServerUpdateInfoProcessor。我们只是单纯的通过数据进行解耦,并且使用自己规定的规则保证数据不冲突。
而且有个问题,如果后来的功能中,有很多业务数据都在里面,我觉得不应该这么详细的排查,如果这部分数据有n层,那么逐个排查就很吓人,不如考虑一些小的算法,对其进行验证,尽管有错误判断,但是小的容错率是可以接受的
那你的建议或者是想法是什么? @Math312
我的想法是,如果还有此类的Raft元数据判断是否修改的业务,可以针对其做如下类似接口:
public interface XXXProcessor {
boolean supports(Object);
boolean equals(Object,Object);
Object process(Object);
}
然后通过工厂模式做一个解耦。例如本例,我们就可以通过工厂模式取出对应的Processor(可以使用support方法,不过效率有点慢哈),然后通过equals进行比较,如果不同,则执行proceed操作。作出合理的解耦。而且对于equals操作,我们可以尝试使用sha-XXX此族算法对原有数据和现有数据进行处理(当然,需要比较一下遍历与使用算法加密后比较的代价)然后比较。
这种方法是相对比较保守的。如果只是针对当前问题,其实感觉简单比较term字段也未尝不可。
采用这种方法,一方面,为大量元数据做了解耦,一个Processor只处理对应的数据,你甚至可以做一条ProcessorChain,前提是你真的需要。另一方面,对于同一业务含义的数据,我们可以尝试使用其余的算法(md5\sha-XXX)处理后比较,感觉hashcode()这东西的自定义还是需要自己想,equals()原生的应该不满足需要吧。我感觉我们不应该做一个大而全的东西,而是应该作出一个个小的东西,然后如果需要拼接成一个大的东西。
元数据带有各种业务语义的处理当然可以像您这个方案,但是对于member本身,扩展字段的元数据就是无语义的对象,仅以比对和确认修改的角度来看,您这个处理方式会导致无法解耦扩展字段和member本身,不是很好。因为member或者memberManager需要从元数据来源处获取大量Processor,或者理解扩展字段语义。
我还是觉得对于扩展字段的元数据,就把它当一个POJO看待,在equals和hashCode里去判断是否变化可能更解耦一些。
元数据带有各种业务语义的处理当然可以像您这个方案,但是对于member本身,扩展字段的元数据就是无语义的对象,仅以比对和确认修改的角度来看,您这个处理方式会导致无法解耦扩展字段和member本身,不是很好。因为member或者memberManager需要从元数据来源处获取大量Processor,或者理解扩展字段语义。
我还是觉得对于扩展字段的元数据,就把它当一个POJO看待,在equals和hashCode里去判断是否变化可能更解耦一些。
我觉得,成员自己去处理equlas以及hashCode的判断
感觉我们把问题想复杂了,Member信息其实分成了基础信息和拓展信息,基础信息比如ip、状态、权重和版本号等信息变更后应该要触发Member变更事件,触发子模块的一些操作。
至于拓展信息,其实不需要考虑这些问题,每次心跳的时候直接更新就好了。
这样就不需要考虑比较的问题了。
Most helpful comment
元数据带有各种业务语义的处理当然可以像您这个方案,但是对于member本身,扩展字段的元数据就是无语义的对象,仅以比对和确认修改的角度来看,您这个处理方式会导致无法解耦扩展字段和member本身,不是很好。因为member或者memberManager需要从元数据来源处获取大量Processor,或者理解扩展字段语义。
我还是觉得对于扩展字段的元数据,就把它当一个POJO看待,在equals和hashCode里去判断是否变化可能更解耦一些。