目录

一、使用

1. 服务提供方

2. 服务消费方

二、服务发现与注册

1. 消费方服务发现

(1)主动拉取服务

NacosNamingService.getAllInstances()

NacosNamingService.getServiceInfo(final String serviceName, final String clusters)

NacosNamingService.updateServiceNow(String serviceName, String clusters)

NacosNamingService.scheduleUpdateIfAbsent(String serviceName, String clusters)

UpdateTask.run()

(2)接收推送服务

PushReceiver构造方法

PushReceiver.run()

HostReactor.processServiceJSON(String json)

2. 提供方服务注册

NacosNamingService.registerInstance(String serviceName, String groupName, Instance instance) throws NacosException

NacosNamingService.addBeatInfo(String serviceName, BeatInfo beatInfo)

心跳任务:BeatTask

3. 注册中心

ServiceManager.registerInstance(String namespaceId, String serviceName, Instance instance);

ServiceManager.addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips) throws NacosException

DelegateConsistencyServiceImpl.put(String key, Record value)

RaftConsistencyServiceImpl.put(String key, Record value)

RaftConsistencyServiceImpl.signalPublish(String key, Record value)

RaftCore.onPublish(Datum datum, RaftPeer source)


命名服务是指通过指定的名字来获取资源或者服务的地址,提供者的信息。

名字服务需要提供服务的订阅与注册功能,dubbo默认基于zk实现,nacos有自己的实现方式,数据存储主要基于raft。

提供分布式系统中所有对象(Object)、实体(Entity)的“名字”到关联的元数据之间的映射管理服务,例如 ServiceName -> Endpoints Info, Distributed Lock Name -> Lock Owner/Status Info, DNS Domain Name -> IP List, 服务发现和 DNS 就是名字服务的2大场景。

一、使用

1. 服务提供方

@NacosInjected // 使用nacos client的NacosInjected注解注入服务
private NamingService namingService;@RequestMapping(value = "/set", method = GET)
@ResponseBody
public String set(@RequestParam String serviceName) {try {namingService.registerInstance(serviceName, "10.26.15.125", 8848); // 注册中心的地址return "OK";} catch (NacosException e) {e.printStackTrace();return "ERROR";}
}

调用http://localhost:8084/discovery/set?serviceName=example,注册服务name=example

2. 服务消费方

@NacosInjected
private NamingService namingService;@RequestMapping(value = "/get", method = GET)
@ResponseBody
public List<Instance> get(@RequestParam(defaultValue = "") String serviceName) throws NacosException {return namingService.getAllInstances(serviceName);
}

调用http://localhost:8084/discovery/get?serviceName=example,获取服务,下面是返回结果:

[{instanceId: "10.26.15.125#8848#DEFAULT#DEFAULT_GROUP@@examplee",ip: "10.26.15.125",port: 8848,weight: 1,healthy: true,enabled: true,ephemeral: true,clusterName: "DEFAULT",serviceName: "DEFAULT_GROUP@@example",metadata: {}}
]

二、服务发现与注册

1. 消费方服务发现

(1)主动拉取服务

  • NacosNamingService.getAllInstances()

public List<Instance> getAllInstances(String serviceName, String groupName, List<String> clusters, boolean subscribe) throws NacosException {ServiceInfo serviceInfo;if (subscribe) { // 是否订阅了服务// 从缓存或注册中心获取服务信息serviceInfo = hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));} else {// 直接从注册中心获取服务// getServiceInfoDirectlyFromServer方法很简单,直接调用serverProxy.queryList(下面会讲)获取服务,serviceInfo = hostReactor.getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));}List<Instance> list;if (serviceInfo == null || CollectionUtils.isEmpty(list = serviceInfo.getHosts())) {return new ArrayList<Instance>();}return list;
}
  • NacosNamingService.getServiceInfo(final String serviceName, final String clusters)

public ServiceInfo getServiceInfo(final String serviceName, final String clusters) {NAMING_LOGGER.debug("failover-mode: " + failoverReactor.isFailoverSwitch());String key = ServiceInfo.getKey(serviceName, clusters);// 注册中心与服务提供方失去联系,会把该服务置成Failover状态,下面的代码会直接缓存取服务信息,或者取不到返回空服务。if (failoverReactor.isFailoverSwitch()) {return failoverReactor.getService(key);}// 从本地缓存map中取ServiceInfo serviceObj = getSerivceInfo0(serviceName, clusters);// 如果本地缓存没有,则请求注册中心,并更新本地缓存。if (null == serviceObj) {serviceObj = new ServiceInfo(serviceName, clusters);serviceInfoMap.put(serviceObj.getKey(), serviceObj);updatingMap.put(serviceName, new Object());// 请求注册中心,并更新本地缓存。updateServiceNow(serviceName, clusters);updatingMap.remove(serviceName);} else if (updatingMap.containsKey(serviceName)) {if (updateHoldInterval > 0) {// hold a moment waiting for update finishsynchronized (serviceObj) {try {serviceObj.wait(updateHoldInterval);} catch (InterruptedException e) {NAMING_LOGGER.error("[getServiceInfo] serviceName:" + serviceName + ", clusters:" + clusters, e);}}}}// 生成定时任务,等一段时间再执行更新操作scheduleUpdateIfAbsent(serviceName, clusters);return serviceInfoMap.get(serviceObj.getKey());
}
  • NacosNamingService.updateServiceNow(String serviceName, String clusters)


public void updateServiceNow(String serviceName, String clusters) {// 先取本地缓存ServiceInfo oldService = getSerivceInfo0(serviceName, clusters);try {// 从配置中心拉取String result = serverProxy.queryList(serviceName, clusters, pushReceiver.getUDPPort(), false);if (StringUtils.isNotEmpty(result)) {processServiceJSON(result);}} catch (Exception e) {NAMING_LOGGER.error("[NA] failed to update serviceName: " + serviceName, e);} finally {if (oldService != null) {synchronized (oldService) {oldService.notifyAll();}}}
}
  • NacosNamingService.scheduleUpdateIfAbsent(String serviceName, String clusters)

// 缓存中存在,直接返回。
if (futureMap.get(ServiceInfo.getKey(serviceName, clusters)) != null) {return;
}synchronized (futureMap) {if (futureMap.get(ServiceInfo.getKey(serviceName, clusters)) != null) {return;}// 缓存没有,生成一个任务,定时延迟执行, 该任务是循环回调自己的任务ScheduledFuture<?> future = addTask(new UpdateTask(serviceName, clusters));futureMap.put(ServiceInfo.getKey(serviceName, clusters), future);
}
  • UpdateTask.run()

@Override
public void run() {try {ServiceInfo serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));if (serviceObj == null) {updateServiceNow(serviceName, clusters);executor.schedule(this, DEFAULT_DELAY, TimeUnit.MILLISECONDS);return;}if (serviceObj.getLastRefTime() <= lastRefTime) {// 请求注册中心updateServiceNow(serviceName, clusters);serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));} else {// if serviceName already updated by push, we should not override it// since the push data may be different from pull through force push// 只调用serverProxy.queryList接口,传递数据,不更新数据refreshOnly(serviceName, clusters);}// 循环回调自身executor.schedule(this, serviceObj.getCacheMillis(), TimeUnit.MILLISECONDS);lastRefTime = serviceObj.getLastRefTime();} catch (Throwable e) {NAMING_LOGGER.warn("[NA] failed to update serviceName: " + serviceName, e);}
}

(2)接收推送服务

接收推送的逻辑在HostReactor类中实现
  • PushReceiver构造方法

public PushReceiver(HostReactor hostReactor) {try {this.hostReactor = hostReactor;// 开启udp服务udpSocket = new DatagramSocket();executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {@Overridepublic Thread newThread(Runnable r) {Thread thread = new Thread(r);thread.setDaemon(true);thread.setName("com.alibaba.nacos.naming.push.receiver");return thread;}});// 定时接受数据executorService.execute(this);} catch (Exception e) {NAMING_LOGGER.error("[NA] init udp socket failed", e);}
}
  • PushReceiver.run()

public void run() {while (true) {try {// byte[] is initialized with 0 full filled by defaultbyte[] buffer = new byte[UDP_MSS];DatagramPacket packet = new DatagramPacket(buffer, buffer.length);// 接受udp数据包udpSocket.receive(packet);String json = new String(IoUtils.tryDecompress(packet.getData()), "UTF-8").trim();NAMING_LOGGER.info("received push data: " + json + " from " + packet.getAddress().toString());PushPacket pushPacket = JSON.parseObject(json, PushPacket.class);String ack;if ("dom".equals(pushPacket.type) || "service".equals(pushPacket.type)) {// 处理数据,并同步到其他节点hostReactor.processServiceJSON(pushPacket.data);// send ack to serverack = "{\"type\": \"push-ack\""+ ", \"lastRefTime\":\"" + pushPacket.lastRefTime+ "\", \"data\":" + "\"\"}";} else if ("dump".equals(pushPacket.type)) {// dump data to serverack = "{\"type\": \"dump-ack\""+ ", \"lastRefTime\": \"" + pushPacket.lastRefTime+ "\", \"data\":" + "\""+ StringUtils.escapeJavaScript(JSON.toJSONString(hostReactor.getServiceInfoMap()))+ "\"}";} else {// do nothing send ack onlyack = "{\"type\": \"unknown-ack\""+ ", \"lastRefTime\":\"" + pushPacket.lastRefTime+ "\", \"data\":" + "\"\"}";}// ackudpSocket.send(new DatagramPacket(ack.getBytes(Charset.forName("UTF-8")),ack.getBytes(Charset.forName("UTF-8")).length, packet.getSocketAddress()));} catch (Exception e) {NAMING_LOGGER.error("[NA] error while receiving push data", e);}}
}
  • HostReactor.processServiceJSON(String json)

public ServiceInfo processServiceJSON(String json) {ServiceInfo serviceInfo = JSON.parseObject(json, ServiceInfo.class);ServiceInfo oldService = serviceInfoMap.get(serviceInfo.getKey());if (serviceInfo.getHosts() == null || !serviceInfo.validate()) {//empty or error push, just ignorereturn oldService;}if (oldService != null) {if (oldService.getLastRefTime() > serviceInfo.getLastRefTime()) {NAMING_LOGGER.warn("out of date data received, old-t: " + oldService.getLastRefTime()+ ", new-t: " + serviceInfo.getLastRefTime());}// 共享内存serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);Map<String, Instance> oldHostMap = new HashMap<String, Instance>(oldService.getHosts().size());for (Instance host : oldService.getHosts()) {oldHostMap.put(host.toInetAddr(), host);}Map<String, Instance> newHostMap = new HashMap<String, Instance>(serviceInfo.getHosts().size());for (Instance host : serviceInfo.getHosts()) {newHostMap.put(host.toInetAddr(), host);}Set<Instance> modHosts = new HashSet<Instance>();Set<Instance> newHosts = new HashSet<Instance>();Set<Instance> remvHosts = new HashSet<Instance>();List<Map.Entry<String, Instance>> newServiceHosts = new ArrayList<Map.Entry<String, Instance>>(newHostMap.entrySet());for (Map.Entry<String, Instance> entry : newServiceHosts) {Instance host = entry.getValue();String key = entry.getKey();if (oldHostMap.containsKey(key) && !StringUtils.equals(host.toString(),oldHostMap.get(key).toString())) {modHosts.add(host);continue;}if (!oldHostMap.containsKey(key)) {newHosts.add(host);continue;}}for (Map.Entry<String, Instance> entry : oldHostMap.entrySet()) {Instance host = entry.getValue();String key = entry.getKey();if (newHostMap.containsKey(key)) {continue;}if (!newHostMap.containsKey(key)) {remvHosts.add(host);continue;}}if (newHosts.size() > 0) {NAMING_LOGGER.info("new ips(" + newHosts.size() + ") service: "+ serviceInfo.getName() + " -> " + JSON.toJSONString(newHosts));}if (remvHosts.size() > 0) {NAMING_LOGGER.info("removed ips(" + remvHosts.size() + ") service: "+ serviceInfo.getName() + " -> " + JSON.toJSONString(remvHosts));}if (modHosts.size() > 0) {NAMING_LOGGER.info("modified ips(" + modHosts.size() + ") service: "+ serviceInfo.getName() + " -> " + JSON.toJSONString(modHosts));}serviceInfo.setJsonFromServer(json);if (newHosts.size() > 0 || remvHosts.size() > 0 || modHosts.size() > 0) {// 事件更新,做业务拓展eventDispatcher.serviceChanged(serviceInfo);DiskCache.write(serviceInfo, cacheDir);}} else {NAMING_LOGGER.info("new ips(" + serviceInfo.ipCount() + ") service: " + serviceInfo.getName() + " -> " + JSON.toJSONString(serviceInfo.getHosts()));serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);eventDispatcher.serviceChanged(serviceInfo);serviceInfo.setJsonFromServer(json);// 写入到磁盘DiskCache.write(serviceInfo, cacheDir);}// 使用prometheus.Gauge 监控服务数量MetricsMonitor.getServiceInfoMapSizeMonitor().set(serviceInfoMap.size());NAMING_LOGGER.info("current ips:(" + serviceInfo.ipCount() + ") service: " + serviceInfo.getName() +" -> " + JSON.toJSONString(serviceInfo.getHosts()));return serviceInfo;
}

2. 提供方服务注册

  • NacosNamingService.registerInstance(String serviceName, String groupName, Instance instance) throws NacosException

public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {BeatInfo beatInfo = new BeatInfo();beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));beatInfo.setIp(instance.getIp());beatInfo.setPort(instance.getPort());beatInfo.setCluster(instance.getClusterName());beatInfo.setWeight(instance.getWeight());beatInfo.setMetadata(instance.getMetadata());beatInfo.setScheduled(false);// 添加心跳任务,后续向注册中心发送心跳beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);// 向注册中心注册服务serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
}
  • NacosNamingService.addBeatInfo(String serviceName, BeatInfo beatInfo)

public void addBeatInfo(String serviceName, BeatInfo beatInfo) {NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);// 加入dom2Beat(map),后续定时任务发送心跳dom2Beat.put(buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort()), beatInfo);// 使用Prometheus.Gauge监控心跳服务数量,任务类:BeatTaskMetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}
  • 心跳任务:BeatTask

class BeatTask implements Runnable {BeatInfo beatInfo;public BeatTask(BeatInfo beatInfo) {this.beatInfo = beatInfo;}@Overridepublic void run() {// 注册中心代理发送心跳信息long result = serverProxy.sendBeat(beatInfo);beatInfo.setScheduled(false);if (result > 0) {clientBeatInterval = result;}}
}

3. 注册中心

下面是注册中心收到服务注册请求后的逻辑

  • ServiceManager.registerInstance(String namespaceId, String serviceName, Instance instance);

public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {if (ServerMode.AP.name().equals(switchDomain.getServerMode())) {createEmptyService(namespaceId, serviceName);}// 检查是否已经注册,重复注册会抛出异常Service service = getService(namespaceId, serviceName);if (service == null) {throw new NacosException(NacosException.INVALID_PARAM,"service not found, namespace: " + namespaceId + ", service: " + serviceName);}if (service.allIPs().contains(instance)) {throw new NacosException(NacosException.INVALID_PARAM, "instance already exist: " + instance);}addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}
  • ServiceManager.addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips) throws NacosException

public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips) throws NacosException {String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);Service service = getService(namespaceId, serviceName);// 获取该服务的所有实例List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);Instances instances = new Instances();instances.setInstanceList(instanceList);consistencyService.put(key, instances);
}
  • DelegateConsistencyServiceImpl.put(String key, Record value)

public void put(String key, Record value) throws NacosException {mapConsistencyService(key).put(key, value);
}
// CAP定理,
// AP模式下,默认返回 EphemeralConsistencyService实例,数据保存到内存当中
// CP模式下,默认返回 PersistentConsistencyService实例,数据保存到硬盘中
private ConsistencyService mapConsistencyService(String key) {return KeyBuilder.matchEphemeralKey(key) ? ephemeralConsistencyService : persistentConsistencyService;
}

由于线上集群模式都是CP模式,数据都会保存到磁盘当中,所有下面看看PersistentConsistencyService保存实例的实现逻辑,其实现类是:RaftConsistencyServiceImpl

  • RaftConsistencyServiceImpl.put(String key, Record value)

public void put(String key, Record value) throws NacosException {try {// 触发发布服务信号raftCore.signalPublish(key, value);} catch (Exception e) {Loggers.RAFT.error("Raft put failed.", e);throw new NacosException(NacosException.SERVER_ERROR, "Raft put failed, key:" + key + ", value:" + value);}
}
  • RaftConsistencyServiceImpl.signalPublish(String key, Record value)

public void signalPublish(String key, Record value) throws Exception {// 如果自己不是leader节点,请求将会转发到leader节点if (!isLeader()) {JSONObject params = new JSONObject();params.put("key", key);params.put("value", value);Map<String, String> parameters = new HashMap<>(1);parameters.put("key", key);// 内部使用的是http协议转发请求raftProxy.proxyPostLarge(getLeader().ip, API_PUB, params.toJSONString(), parameters);return;}try {OPERATE_LOCK.lock();long start = System.currentTimeMillis();final Datum datum = new Datum();datum.key = key;datum.value = value;// timestamp记录的是服务被操作的次数,相当于版本号if (getDatum(key) == null) {datum.timestamp.set(1L);} else {// 操作一次版本号+1datum.timestamp.set(getDatum(key).timestamp.incrementAndGet());}JSONObject json = new JSONObject();json.put("datum", datum);json.put("source", peers.local());// leader节点发布服务onPublish(datum, peers.local());final String content = JSON.toJSONString(json);final CountDownLatch latch = new CountDownLatch(peers.majorityCount());// 发布服务后,同步到所有非leader节点for (final String server : peers.allServersIncludeMyself()) {if (isLeader(server)) {latch.countDown();continue;}final String url = buildURL(server, API_ON_PUB);HttpClient.asyncHttpPostLarge(url, Arrays.asList("key=" + key), content, new AsyncCompletionHandler<Integer>() {@Overridepublic Integer onCompleted(Response response) throws Exception {if (response.getStatusCode() != HttpURLConnection.HTTP_OK) {Loggers.RAFT.warn("[RAFT] failed to publish data to peer, datumId={}, peer={}, http code={}",datum.key, server, response.getStatusCode());return 1;}latch.countDown();return 0;}@Overridepublic STATE onContentWriteCompleted() {return STATE.CONTINUE;}});}// 等待所有从节点更新服务成功,超时抛异常if (!latch.await(UtilsAndCommons.RAFT_PUBLISH_TIMEOUT, TimeUnit.MILLISECONDS)) {// only majority servers return success can we consider this update successLoggers.RAFT.info("data publish failed, caused failed to notify majority, key={}", key);throw new IllegalStateException("data publish failed, caused failed to notify majority, key=" + key);}long end = System.currentTimeMillis();Loggers.RAFT.info("signalPublish cost {} ms, key: {}", (end - start), key);} finally {OPERATE_LOCK.unlock();}
}
  • RaftCore.onPublish(Datum datum, RaftPeer source)

public void onPublish(Datum datum, RaftPeer source) throws Exception {// 校验逻辑RaftPeer local = peers.local();if (datum.value == null) {Loggers.RAFT.warn("received empty datum");throw new IllegalStateException("received empty datum");}if (!peers.isLeader(source.ip)) {Loggers.RAFT.warn("peer {} tried to publish data but wasn't leader, leader: {}",JSON.toJSONString(source), JSON.toJSONString(getLeader()));throw new IllegalStateException("peer(" + source.ip + ") tried to publish " +"data but wasn't leader");}if (source.term.get() < local.term.get()) {Loggers.RAFT.warn("out of date publish, pub-term: {}, cur-term: {}",JSON.toJSONString(source), JSON.toJSONString(local));throw new IllegalStateException("out of date publish, pub-term:"+ source.term.get() + ", cur-term: " + local.term.get());}// 重新设置leader持续的时间local.resetLeaderDue();// if data should be persistent, usually this is always true:if (KeyBuilder.matchPersistentKey(datum.key)) {// 数据持久化到本地磁盘raftStore.write(datum);}datums.put(datum.key, datum);if (isLeader()) {local.term.addAndGet(PUBLISH_TERM_INCREASE_COUNT);} else {if (local.term.get() + PUBLISH_TERM_INCREASE_COUNT > source.term.get()) {//set leader term:getLeader().term.set(source.term.get());local.term.set(getLeader().term.get());} else {local.term.addAndGet(PUBLISH_TERM_INCREASE_COUNT);}}// 更新leader的term数 +1raftStore.updateTerm(local.term.get());// 发布事件通知,注意这个作用是作为业务逻辑的拓展,不是主从同步, 如果没有注册业务实现的listener,将不做任何事情。notifier.addTask(datum, ApplyAction.CHANGE);Loggers.RAFT.info("data added/updated, key={}, term={}", datum.key, local.term);
}

Nacos名字服务(Naming Service)相关推荐

  1. Nacos微服务注册发现、配置和管理微服务

    目录 Nacos介绍 什么是 Nacos? Nacos 地图 Nacos 生态图 Nacos 概念 地域 可用区 接入点 命名空间 配置 配置管理 配置项 配置集 配置集 ID 配置分组 配置快照 服 ...

  2. Distributed System: Naming Service (命名服务)

    About Naming Service: Name Space (命名空间):在一个特定空间内,某一资源(文件.网页.音乐.图像等)具有全局唯一的标识符URI(名字).例如,目前的互联网(Inter ...

  3. nacos名字的由来

    nacos名字由来: 前四个字母为naming和configuration的前两个字母,最后的s为service. 什么是nacos: 一个更易于构建云原生应用的动态服务发现,配置管理和服务管理平台, ...

  4. 如何用 Nacos 构建服务网格生态

    Nacos 简介 Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称.目标是构建一个更易于构建云原生应用的动态服务发现.配 ...

  5. 如何用 Nacos 构建服务网格生态?

    简介:Nacos 在阿里巴巴起源于 2008 年五彩石项目(该项目完成微服务拆分和业务中台建设),成长于十年的阿里双十一峰值考验,这一阶段主要帮助业务解决微服务的扩展性和高可用问题,解决了百万实例扩展 ...

  6. 芭比扣了!Nacos中服务删除不了,肿么办?

    作者 | 磊哥 来源 | Java中文社群(ID:javacn666) 转载请联系授权(微信ID:GG_Stone) 前两天遇到了一个问题,Nacos 中的永久服务删除不了,折腾了一番,最后还是顺利解 ...

  7. SpringCloud - Spring Cloud Alibaba 之 Nacos Discovery服务注册发现(三)

    阅读本文可先参考博文 https://blog.csdn.net/MinggeQingchun/article/details/125613600 https://blog.csdn.net/Ming ...

  8. Nacos中服务注册中心AP、CP模式实现,AP、CP模式切换

    本文分析Nacos基于Nacos 2.0 Nacos中服务注册中心默认是AP模式,如果设置为CP模式 那么客户端设置 spring.cloud.nacos.discovery.ephemeral=fa ...

  9. Nacos的服务注册表结构是怎样的?

    问题说明:考察对Nacos数据分级结构的了解,以及Nacos源码的掌握情况 难易程度:一般 参考话术: Nacos采用了数据的分级存储模型,最外层是Namespace,用来隔离环境.然后是Group, ...

最新文章

  1. 团队-科学计算器-成员简介及分工
  2. Go游戏服务器开发的一些思考(九):Docker桥接网络及固定IP (二)
  3. EventBus设计与实现分析——事件的发布
  4. 多项式乘积求导 c语言,c语言实现多项式求导.docx
  5. sqlserver 中怎样查看一个数据库中表的关系
  6. 基于Yolov5目标检测的物体分类识别及定位 -- 全过程总结
  7. springcloud之eureka集群搭建
  8. 鹏芯U盘(UDK2008)意外断电后修复 1
  9. 电脑一直显示服务器不兼容,原神PC版提示版本不兼容怎么办 PC版常见问题介绍...
  10. 简述软件黑盒测试的方法,简述什么是黑盒测试方法
  11. 贪心算法(贪婪算法)
  12. sqlmap命令详解(最全版本)
  13. WPF 特殊符号集合
  14. Android 使用高德SDK编写周边搜索定位
  15. 国外程序员真实生活曝光,谷歌的工资竟这么高
  16. LTE传输模式(TM1 - TM9)
  17. 【爱情】小事件の车祸
  18. 谈谈阿里与谷歌的Java开发规范
  19. 华三交换机配置access命令_h3c交换机配置命令
  20. java爬虫框架 httpclient_Java爬虫框架简介

热门文章

  1. 如何将知识内化为能力
  2. 同一个Maven项目移机出错解决办法
  3. stm32f103rbt6_5
  4. 基于R语言的seasonal包使用手册_05.final\original\trend\irregular\residuals
  5. 怎么避免邮件进入垃圾邮箱?
  6. 网口调试基础之一网口phy驱动
  7. vivo图像算法工程师双非研究生可以吗_计算机学院研究生会就业经验交流分享会...
  8. 小霸王被申请破产:谈感情真的很伤钱!
  9. GAVH39,PM1132,供电220v转5v芯片,SOT23-3,AC-DC小功率应用方案
  10. disp()函数的用法