本地服务注册不上nacos_nacos注册服务异常无法删除

基于官方的 Nacos Sync 做任务分片和集群高可用,目标是为了支持大规模的注册集群迁移,并保障在节点宕机时,其它节点能快速响应,转移故障。技术点如下,文中只列出部分源码或者以伪代码表示:

**详细代码,请参考:

https://github.com/zhangmen-tech/nacos**

服务一致性 Hash 分片路由:

根据如图1多集群部署,为每个节点设置可配置的虚拟节点数,使其在 Hash 环上能均匀分布。

// 虚拟节点配置

sync.consistent.hash.replicas = 1000;

// 存储虚拟节点

SortedMap circle = new TreeMap();

// 循环添加所有节点到容器,构建Hash环

replicas for loop {

// 为每个物理节点设置虚拟节点 String nodeStr = node.toString().concat("##").concat(Integer.toString(replica)); // 根据算法计算出虚拟节点的Hash值 int hashcode = getHash(nodeStr); // 将虚拟节点放入Hash环中 circle.put(hashcode, node);

// 异步监听节点存活状态

etcdManager.watchEtcdKeyAsync(REGISTER_WORKER_PATH, true, response -> {

for (WatchEvent event : response.getEvents()) { // 删除事件,从内存中剔除此节点及Hash中虚拟节点 if (event.getEventType().equals(WatchEvent.EventType.DELETE)) { String key = Optional.ofNullable(event.getKeyValue().getKey()).map(bs -> bs.toString(Charsets.UTF_8)).orElse(StringUtils.EMPTY); //获取Etcd中心跳丢失的节点 String[] ks = key.split(SLASH); log.info("{} lost heart beat", ks[3]); // 自身节点不做判断 if (!IPUtils.getIpAddress().equalsIgnoreCase(ks[3])) { // 监听心跳丢失,更显存货节点缓存,删除Hash环上节点 nodeCaches.remove(ks[3]); try { // 心跳丢失,清除etcd上该节点的处理任务 manager.deleteEtcdValueByKey(PER_WORKER_PROCESS_SERVICE.concat(SLASH).concat(ks[3]), true); } catch (InterruptedException e) { log.error("clear {} process service failed,{}", ks[3], e); } catch (ExecutionException e) { log.error("clear {} process service failed,{}", ks[3], e); } } }

}

根据业务服务名的 FNV1_32_HASH 算法计算每个业务服务的哈希值,计算该 Hash 值顺时针最近的节点,将任务代理到该节点。

// 计算任务的Hash值

int hash = getHash(key.toString());

if (!circle.containsKey(hash)) {

SortedMap tailMap = circle.tailMap(hash); // 找到顺势针最近节点 hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();

}

// 得到Hash环中的节点位置

circle.get(hash);

// 判断任务是否自己的处理节点

if (syncShardingProxy.isProcessNode(taskDO.getServiceName())) {

//如果任务属于该节点,则进行心跳同步处理 processTask(Task);

}

// 删除心跳同步任务

if (TaskStatusEnum.DELETE.getCode().equals(taskUpdateRequest.getTaskStatus())) {

// 通过Etcd存活节点的一致性Hash算法,获取此任务所在的处理节点 Node processNode = syncShardingProxy.fetchProcessNode(Task); if (processNode.isMyself()) { // 如果是自己的同步任务,发布删除心跳事件 eventBus.post(new DeleteTaskEvent(taskDO)); } else { // 如果是其他节点,则通过Http代理到此节点处理 httpClientProxy.deleteTask(targetUrl,task); }

}

同步节点宕机故障转移:

节点监听。监听其它节点存活状态,配置 Etcd 集群租约 TTL , TTL 内至少发送 5 个续约心跳以保证一旦出现网络波动避免造成节点丢失。

// 心跳TTL配置

sync.etcd.register.ttl = 30;

// 获取租约TTL配置

String ttls = environment.getProperty(ETCD_BEAT_TTL);

long ttl = NumberUtils.toLong(ttls);

// 获取租约ID

long leaseId = client.getLeaseClient().grant(ttl).get().getID();

PutOption option = PutOption.newBuilder().withLeaseId(leaseId).withPrevKV().build();

client.getKVClient().put(ByteSequence.from(key, UTF_8), ByteSequence.from(value, UTF_8), option).get();

long delay = ttl / 6;

// 定时续约

scheduledExecutorService.schedule(new BeatTask(leaseId, delay), delay, TimeUnit.SECONDS);

// 续约任务

private class BeatTask implements Runnable {

long leaseId; long delay; public BeatTask(long leaseId, long delay) { this.leaseId = leaseId; this.delay = delay; } public void run() { client.getLeaseClient().keepAliveOnce(leaseId); scheduledExecutorService.schedule(new BeatTask(this.leaseId, this.delay), delay, TimeUnit.SECONDS); }

}

节点宕机。其中某个节点宕机,其任务转移到其它节点,因为有虚拟节点的缘故,所以此节点的任务会均衡 ReSharding 到其它节点,那么,集群在任何时候,任务处理都是分片均衡的,如图2中, B 节点宕机, ##1 、 ##2 虚拟节点的任务会分别转移到 C 和 A 节点,这样避免一个节点承担宕机节点的所有任务造成剩余节点连续雪崩。

节点恢复。如图3,节点的虚拟节点重新添加到 Hash 环中, Sharding 规则变更,恢复的节点会根据新的 Hash 环规则承担其它节点的一部分任务。心跳任务一旦在节点产生都不会自动消失,这时需要清理其它节点的多余任务(即重新分配给复苏节点的任务),给其它节点减负(这一步非常关键,不然也可能会引发集群的连续雪崩),保障集群恢复到最初正常任务同步状态。

// 找到此节点处理的心跳同步任务

Map finishedTaskMap = skyWalkerCacheServices.getFinishedTaskMap();

// 存储非此节点处理任务

Map unBelongTaskMap = Maps.newHashMap();

// 找到集群复苏后,Rehash后不是此节点处理的任务

if (!shardingEtcdProxy.isProcessNode(taskDO.getServiceName()) && TaskStatusEnum.SYNC.getCode().equals(taskDO.getTaskStatus())) {

unBelongTaskMap.put(operationId, entry.getValue());

}

unBelongTaskMap for loop {

// 删除多余的节点同步 specialSyncEventBus.unsubscribe(taskDO); // 删除多余的节点处理任务数 proxy.deleteEtcdValueByKey(PER_WORKER_PROCESS_SERVICE.concat(SLASH).concat(IPUtils.getIpAddress()).concat(SLASH).concat(taskDO.getServiceName()), false); // 根据不同的同步类型,删除多余的节点心跳 if (ClusterTypeEnum.EUREKA.getCode().equalsIgnoreCase(clusterDO.getClusterType())) { syncToNacosService.deleteHeartBeat(taskDO); } if (ClusterTypeEnum.NACOS.getCode().equalsIgnoreCase(clusterDO.getClusterType())) { syncToEurekaService.deleteHeartBeat(taskDO); } // 删除多余的finish任务 finishedTaskMap.remove(val.getKey());

}

节点容灾。如果 Etcd 集群连接不上,则存活节点从配置文件中获取,集群正常运作,但是会失去容灾能力。

// 配置所有处理节点的机器IP,用于构建Hash环

sync.worker.address = ip1, ip2, ip3;

// 从配置文件获取所有处理任务节点IP

List ips = getWorkerIps();

ConsistentHash consistentHash = new ConsistentHash(replicas, ips);

// 如果从Etcd中获取不到当前处理节点,则构建Hash环用配置文件中的IP列表,且列表不会动态变化

if (CollectionUtils.isNotEmpty(nodeCaches)) {

consistentHash = new ConsistentHash(replicas, nodeCaches);

}

return consistentHash;

Nacos Eureka Sync 保障手段

Nacos Eureka Sync 同步界面

从如下界面可以保证,从 Eureka 或者 Nacos 上,新上线或者下线一个业务服务(非实例),都能让 Nacos Eureka Sync 实时感知。但我们做了更进一层的智能化和自动化:

1、新增同步。结合 DevOps 发布平台,当一个业务服务(非实例)新上线的时候,智能判断它是从哪个注册中心上线的,然后回调 Nacos Eureka Sync 接口,自动添加同步接口,例如,A 业务服务注册到 Eureka 上,DevOps 发布平台会自动添加它的 Eureka -> Nacos 的同步任务,反之亦然。当然从如下界面的操作也可实现该功能。

2、删除同步。由于 DevOps 发布平台无法判断一个业务服务(非实例)下线,或者已经迁移到另一个注册中心,已经全部完毕(有同学会反问,可以判断的,即查看那个业务服务的实例数是否是零为标准,但我们应该考虑,实例数为零在网络故障的时候也会发生,即心跳全部丢失,所以这个判断依据是不严谨的),交由业务人员来判断,同时配合钉钉机器人告警提醒,由基础架构部同学从如下界面的操作实现该功能。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值