Nacos注册中心(1.4.1)源码解读心得

一丶Nacos介绍

  Nacos是阿里巴巴推出的一款新开源项目,是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它致力于帮助您发现、配置和管理微服务,提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。Nacos是构建以“服务”为中心的现代应用架构(例如微服务范式、云原生范式)的服务基础设施。

Nacos支持多种核心特性,包括:

  1. 服务发现:支持DNS与RPC服务发现,也提供原生SDK、OpenAPI等多种服务注册方式和DNS、HTTP与API等多种服务发现方式。
  2. 服务健康监测:提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求。
  3. 动态配置服务:提供配置统一管理功能,能够帮助我们将配置以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。
  4. 动态DNS服务:支持动态DNS服务权重路由,能够很容易地实现中间层负载均衡、更灵活的路由策略、流量控制以及数据中心内网的简单DNS解析服务。
  5. 服务及其元数据管理:支持从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的SLA以及最首要的metrics统计数据。

  Nacos可以与Spring、Spring Boot、Spring Cloud集成,并能代替Spring Cloud Eureka和Spring Cloud Config。通过Nacos Server和spring-cloud-starter-alibaba-nacos-config实现配置的动态变更。它提供了一个简洁易用的UI(控制台样例Demo)帮助您管理所有的服务和应用的配置。同时,它也提供了一些简单的DNS APIs TODO帮助您管理服务的关联域名和可用的IP:PORT列表。

二丶客户端注册流程

  • 在SpringCloudAlibaba这一套微服务组件中,Nacos作为注册中心向其他的微服务提供信息,业务服务通过从Nacos中拉取所需要的服务信息,再通过Ribbon在本地做负载均衡之后通过Feign组件发起接口调用。例如在电商系统中的下单服务,库存服务,支付服务等。完成一个下单过程中,下单服务需要调用库存服务减库存,调用支付服务完成支付等,而库存服务,支付服务的信息都会存储在注册中心即Nacos服务中。服务之间的调用只需要通过注册中心获取,不再需要每个服务都去存储需要调用的服务信息了,完成了解耦。
  • 客户端想要注册到注册中心去就要先引入Nacos的客户端依赖spring-cloud-starter-alibaba-nacos-discovery,并在配置文件中配上Nacos的服务地址和命名空间等信息。想要知道Nacos客户端的注册流程就得从引入的依赖入手,从maven依赖库中找到nacos的jar包,下面有一个META-INF文件夹,里面的spring.factory文件就是springboot自动装配过程中会装配的类:

    可以看到,红框标注的类就是跟服务发现自动装配相关性比较大的类文件了,直接在项目中搜索这个类可以看到以下文件:

    @Configuration
    @EnableConfigurationProperties
    @ConditionalOnNacosDiscoveryEnabled
    @ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
    @AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
    AutoServiceRegistrationAutoConfiguration.class })
    public class NacosDiscoveryAutoConfiguration { @Bean
    public NacosServiceRegistry nacosServiceRegistry(
    NacosDiscoveryProperties nacosDiscoveryProperties) {
    return new NacosServiceRegistry(nacosDiscoveryProperties);
    } @Bean
    @ConditionalOnBean(AutoServiceRegistrationProperties.class)
    public NacosRegistration nacosRegistration(
    NacosDiscoveryProperties nacosDiscoveryProperties,
    ApplicationContext context) {
    return new NacosRegistration(nacosDiscoveryProperties, context);
    } @Bean
    @ConditionalOnBean(AutoServiceRegistrationProperties.class)
    public NacosAutoServiceRegistration nacosAutoServiceRegistration(
    NacosServiceRegistry registry,
    AutoServiceRegistrationProperties autoServiceRegistrationProperties,
    NacosRegistration registration) {
    return new NacosAutoServiceRegistration(registry,
    autoServiceRegistrationProperties, registration);
    }
    }

    在这个类中有三个@Bean注解,仔细观察前两个bean都在第三个bean的参数中,所以第三个bean就是比较重要的Bean了。

  • 直接搜索NacosAutoServiceRegistry这个类可以看到其中有一个方法名为register(),通过类名和方法名大概能猜到这就是注册的主逻辑了。
    @Override
    protected void register() {
    if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) {
    log.debug("Registration disabled.");
    return;
    }
    if (this.registration.getPort() < 0) {
    this.registration.setPort(getPort().get());
    }
    super.register();
    }
  • 直接点到注册方法中去是NacosAutoServiceRegistry的抽象父类,抽象父类中有一个对象serviceRegistry,注册方法就是这个对象的register()方法,继续跟到这个register()方法中去:
    @Override
    public void register(Registration registration) { if (StringUtils.isEmpty(registration.getServiceId())) {
    log.warn("No service to register for nacos client...");
    return;
    } String serviceId = registration.getServiceId(); Instance instance = getNacosInstanceFromRegistration(registration); try {
    namingService.registerInstance(serviceId, instance);
    log.info("nacos registry, {} {}:{} register finished", serviceId,
    instance.getIp(), instance.getPort());
    }
    catch (Exception e) {
    log.error("nacos registry, {} register failed...{},", serviceId,
    registration.toString(), e);
    }
    }

    可以看到通过getNacosInstanceFromRegistration()方法将Registration 转换成为了Instance 对象,后续通过namingService.registerInstance(serviceId, instance);进行了注册的动作,而这个Instance 对象其实就是我们Nacos服务端所存储的微服务相关的一些信息。

  • 继续跟namingService.registerInstance(serviceId, instance);方法:
     @Override
    public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException { if (instance.isEphemeral()) {
    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);
    long instanceInterval = instance.getInstanceHeartBeatInterval();
    beatInfo.setPeriod(instanceInterval == 0 ? DEFAULT_HEART_BEAT_INTERVAL : instanceInterval); beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
    } serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
    }

    这是NamingService的实现类NacosNamingService中的实现,可以看到进行了一个if判断,这个其实是判断是否是一个临时的实例,如果是临时实例做一些处理,最后的注册请求是在serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);方法中的:

    public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
    
            NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}",
    namespaceId, serviceName, instance); final Map<String, String> params = new HashMap<String, String>(9);
    params.put(CommonParams.NAMESPACE_ID, namespaceId);
    params.put(CommonParams.SERVICE_NAME, serviceName);
    params.put(CommonParams.GROUP_NAME, groupName);
    params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
    params.put("ip", instance.getIp());
    params.put("port", String.valueOf(instance.getPort()));
    params.put("weight", String.valueOf(instance.getWeight()));
    params.put("enable", String.valueOf(instance.isEnabled()));
    params.put("healthy", String.valueOf(instance.isHealthy()));
    params.put("ephemeral", String.valueOf(instance.isEphemeral()));
    params.put("metadata", JSON.toJSONString(instance.getMetadata())); reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, HttpMethod.POST); }

    组装了一些参数最后发起了POST请求.其中UtilAndComs.NACOS_URL_INSTANCE这个常量最后拼接出来是/nacos/v1/ns/instance

  • 得出结论:客户端通过将自己服务的信息包括ip端口,命名空间,服务名等信息组装好,向Nacos服务发起POST请求注册到注册中心去。

三丶服务端存储客户端注册的服务信息

  •  在上面客户端注册流程最后我们得到了一个URI:/nacos/v1/ns/instance  通过这个URI我们可以去服务端的源码中搜索这个接口:

     @CanDistro
    @PostMapping
    @Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
    public String register(HttpServletRequest request) throws Exception { final String namespaceId = WebUtils
    .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
    final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
    NamingUtils.checkServiceNameFormat(serviceName); final Instance instance = parseInstance(request); serviceManager.registerInstance(namespaceId, serviceName, instance);
    return "ok";
    }

    以上是服务端的注册实例的接口,主要完成了三个动作:检查服务名信息,将请求中的参数转换为Instance实例对象,注册实例,继续跟 serviceManager.registerInstance(namespaceId, serviceName, instance)注册的方法:

    public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
    
            createEmptyService(namespaceId, serviceName, instance.isEphemeral());
    
            Service service = getService(namespaceId, serviceName);
    
            if (service == null) {
    throw new NacosException(NacosException.INVALID_PARAM,
    "service not found, namespace: " + namespaceId + ", service: " + serviceName);
    } addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
    }

    这个方法中主要动作有:创建空的Service对象,获取Service对象,注册实例等动作。

    createEmptyService(namespaceId, serviceName, instance.isEphemeral())方法:
     public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
    throws NacosException {
    Service service = getService(namespaceId, serviceName);
    if (service == null) { Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName);
    service = new Service();
    service.setName(serviceName);
    service.setNamespaceId(namespaceId);
    service.setGroupName(NamingUtils.getGroupName(serviceName));
    // now validate the service. if failed, exception will be thrown
    service.setLastModifiedMillis(System.currentTimeMillis());
    service.recalculateChecksum();
    if (cluster != null) {
    cluster.setService(service);
    service.getClusterMap().put(cluster.getName(), cluster);
    }
    service.validate(); putServiceAndInit(service);
    if (!local) {
    addOrReplaceService(service);
    }
    }
    
    

    先调用了getService方法:

    public Service getService(String namespaceId, String serviceName) {
    if (serviceMap.get(namespaceId) == null) {
    return null;
    }
    return chooseServiceMap(namespaceId).get(serviceName);
    }

    其中的serviceMap结构是

    private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();

    这个map其实就是注册中心保存所有实例的map,最外层的key一般为namespace,里层的key一般为定义的group名,根据业务需要定义。

    public class Service extends com.alibaba.nacos.api.naming.pojo.Service implements Record, RecordListener<Instances> {
    ...private Map<String, Cluster> clusterMap = new HashMap<>();
    ...
    }

    map中的Service对象其中又有一个clusterMap,而Cluster对象的结构如下

    public class Cluster extends com.alibaba.nacos.api.naming.pojo.Cluster implements Cloneable {
    ... @JsonIgnore
    private Set<Instance> persistentInstances = new HashSet<>(); @JsonIgnore
    private Set<Instance> ephemeralInstances = new HashSet<>(); @JsonIgnore
    private Service service;
    ...
    }

    看到这里的两个HashSet中的Instance对象是否有些眼熟?他就是客户端注册到注册中心的服务实例信息

  • 所以整体来看,服务端存储各个微服务的结构如图所示:
  • 那么再回到上述的getService方法中,通过命名空间就可以得到同一组下面的服务,而chooseServiceMap(namespaceId).get(serviceName)又通过服务名来获取Service,其实得到的结构就是一个个的service其中还有一层Cluster;

  • 那么此时我的微服务还并未完成注册获取到的Service肯定是Null,继续往下走就会新构建一个Service,经过前面的赋值校验方法,会走到putServiceAndInit(service)方法中去:
    private void putServiceAndInit(Service service) throws NacosException {
    putService(service);
    service.init();
    consistencyService
    .listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), service);
    consistencyService
    .listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false), service);
    Loggers.SRV_LOG.info("[NEW-SERVICE] {}", service.toJson());
    }

    这个方法中做了两步很重要的操作,其中putService()方法:

    public void putService(Service service) {
    if (!serviceMap.containsKey(service.getNamespaceId())) {
    synchronized (putServiceLock) {
    if (!serviceMap.containsKey(service.getNamespaceId())) {
    serviceMap.put(service.getNamespaceId(), new ConcurrentSkipListMap<>());
    }
    }
    }
    serviceMap.get(service.getNamespaceId()).put(service.getName(), service);
    }

    就是将我们的service放到serviceMap中,源码中就用到了双检锁。

  • 而在service.init()方法中:
     public void init() {
    HealthCheckReactor.scheduleCheck(clientBeatCheckTask);
    for (Map.Entry<String, Cluster> entry : clusterMap.entrySet()) {
    entry.getValue().setService(this);
    entry.getValue().init();
    }
    }

    第一行的 HealthCheckReactor.scheduleCheck(clientBeatCheckTask);看类名是健康检查相关的,那么可以想到注册实例肯定要把自己的健康信息更新到注册中心去,再看参数:clientBeatCheckTask 服务心跳检查任务,点到这个类中:

    public class ClientBeatCheckTask implements Runnable {
    
        private Service service;
    
        @Override
    public void run() {
    try {
    if (!getDistroMapper().responsible(service.getName())) {
    return;
    } if (!getSwitchDomain().isHealthCheckEnabled()) {
    return;
    } List<Instance> instances = service.allIPs(true); // first set health status of instances:
    for (Instance instance : instances) {
    if (System.currentTimeMillis() - instance.getLastBeat() > instance.getInstanceHeartBeatTimeOut()) {
    if (!instance.isMarked()) {
    if (instance.isHealthy()) {
    instance.setHealthy(false);
    Loggers.EVT_LOG
    .info("{POS} {IP-DISABLED} valid: {}:{}@{}@{}, region: {}, msg: client timeout after {}, last beat: {}",
    instance.getIp(), instance.getPort(), instance.getClusterName(),
    service.getName(), UtilsAndCommons.LOCALHOST_SITE,
    instance.getInstanceHeartBeatTimeOut(), instance.getLastBeat());
    getPushService().serviceChanged(service);
    ApplicationUtils.publishEvent(new InstanceHeartbeatTimeoutEvent(this, instance));
    }
    }
    }
    } if (!getGlobalConfig().isExpireInstance()) {
    return;
    } // then remove obsolete instances:
    for (Instance instance : instances) { if (instance.isMarked()) {
    continue;
    } if (System.currentTimeMillis() - instance.getLastBeat() > instance.getIpDeleteTimeout()) {
    // delete instance
    Loggers.SRV_LOG.info("[AUTO-DELETE-IP] service: {}, ip: {}", service.getName(),
    JacksonUtils.toJson(instance));
    deleteIp(instance);
    }
    } } catch (Exception e) {
    Loggers.SRV_LOG.warn("Exception while processing client beat time out.", e);
    } }
    }

    实现了Runnable接口说明是一个线程,直接看run方法

     List<Instance> instances = service.allIPs(true);
    public List<Instance> allIPs(boolean ephemeral) {
    List<Instance> result = new ArrayList<>();
    for (Map.Entry<String, Cluster> entry : clusterMap.entrySet()) {
    result.addAll(entry.getValue().allIPs(ephemeral));
    } return result;
    }
    public List<Instance> allIPs(boolean ephemeral) {
    return ephemeral ? new ArrayList<>(ephemeralInstances) : new ArrayList<>(persistentInstances);
    }

    获取了service中的所有Instance,然后做一系列的心跳检查,发布事件等。到此就做完了实例初始化的动作。

  • 上述是createEmptyService(namespaceId, serviceName, instance.isEphemeral())方法:
  • 那么接下来
     Service service = getService(namespaceId, serviceName);

    肯定可以获取到service,直接看最后的addInstance(namespaceId, serviceName, instance.isEphemeral(), instance)方法:

    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); synchronized (service) {
    List<Instance> instanceList = addIpAddresses(service, ephemeral, ips); Instances instances = new Instances();
    instances.setInstanceList(instanceList); consistencyService.put(key, instances);
    }
    }
  • String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral)这个方法通过nameSpaceId,服务名信息组建了一个key:
    public static String buildInstanceListKey(String namespaceId, String serviceName, boolean ephemeral) {
    return ephemeral ? buildEphemeralInstanceListKey(namespaceId, serviceName)
    : buildPersistentInstanceListKey(namespaceId, serviceName);
    }
    可以看到是根据ephemeral来判断的,这个值是控制是否临时实例的,Instance中的默认值是true表示默认新建的就是临时实例,那么构建出来的字符串:"com.alibaba.nacos.naming.iplist.ephemeral."+ namespaceId +"##"+serviceName
    中间是有ephemeral.的;下面又将初始化了的Instance获取到加锁执行注册逻辑:
    public List<Instance> updateIpAddresses(Service service, String action, boolean ephemeral, Instance... ips)
    throws NacosException { Datum datum = consistencyService
    .get(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), ephemeral)); List<Instance> currentIPs = service.allIPs(ephemeral);
    Map<String, Instance> currentInstances = new HashMap<>(currentIPs.size());
    Set<String> currentInstanceIds = Sets.newHashSet(); for (Instance instance : currentIPs) {
    currentInstances.put(instance.toIpAddr(), instance);
    currentInstanceIds.add(instance.getInstanceId());
    } Map<String, Instance> instanceMap;
    if (datum != null && null != datum.value) {
    instanceMap = setValid(((Instances) datum.value).getInstanceList(), currentInstances);
    } else {
    instanceMap = new HashMap<>(ips.length);
    } for (Instance instance : ips) {
    if (!service.getClusterMap().containsKey(instance.getClusterName())) {
    Cluster cluster = new Cluster(instance.getClusterName(), service);
    cluster.init();
    service.getClusterMap().put(instance.getClusterName(), cluster);
    Loggers.SRV_LOG
    .warn("cluster: {} not found, ip: {}, will create new cluster with default configuration.",
    instance.getClusterName(), instance.toJson());
    } if (UtilsAndCommons.UPDATE_INSTANCE_ACTION_REMOVE.equals(action)) {
    instanceMap.remove(instance.getDatumKey());
    } else {
    Instance oldInstance = instanceMap.get(instance.getDatumKey());
    if (oldInstance != null) {
    instance.setInstanceId(oldInstance.getInstanceId());
    } else {
    instance.setInstanceId(instance.generateInstanceId(currentInstanceIds));
    }
    instanceMap.put(instance.getDatumKey(), instance);
    } } if (instanceMap.size() <= 0 && UtilsAndCommons.UPDATE_INSTANCE_ACTION_ADD.equals(action)) {
    throw new IllegalArgumentException(
    "ip list can not be empty, service: " + service.getName() + ", ip list: " + JacksonUtils
    .toJson(instanceMap.values()));
    } return new ArrayList<>(instanceMap.values());
    }

    这个方法返回值是一个List<Instance>直接看返回值是instanceMap.values(),这个map的来源是通过第一个

    Datum datum = consistencyService .get(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), ephemeral))

    的结果判断的,流程是先判断这个实例是否已经注册到注册中心了,没有的话新增一个instanceMap,有将其中的旧元素都放到新的instanceMap中,再对传过来的instance做一系列的检查注册操作,返回现有的instanceMap中的元素集合。

  • 通过返回的List<Instance>构建一个Instances对象,这个对象里面的结构:
    public class Instances implements Record {
    
        private static final long serialVersionUID = 5500823673993740145L;
    
        private List<Instance> instanceList = new ArrayList<>();
    }

    最后一步consistencyService.put(key, instances)方法:

    public void put(String key, Record value) throws NacosException {
    onPut(key, value);
    distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE,
    globalConfig.getTaskDispatchPeriod() / 2);
    }

    onPut方法就是注册的核心逻辑了:

     public void onPut(String key, Record value) {
    
            if (KeyBuilder.matchEphemeralInstanceListKey(key)) {
    Datum<Instances> datum = new Datum<>();
    datum.value = (Instances) value;
    datum.key = key;
    datum.timestamp.incrementAndGet();
    dataStore.put(key, datum);
    } if (!listeners.containsKey(key)) {
    return;
    } notifier.addTask(key, DataOperation.CHANGE);
    }

    其中notifier的结构:

     public class Notifier implements Runnable {
    
            private ConcurrentHashMap<String, String> services = new ConcurrentHashMap<>(10 * 1024);
    
            private BlockingQueue<Pair<String, DataOperation>> tasks = new ArrayBlockingQueue<>(1024 * 1024);
    }

    说明又是一个线程类,成员变量有一个tasks的阻塞队列。

     public void addTask(String datumKey, DataOperation action) {
    
                if (services.containsKey(datumKey) && action == DataOperation.CHANGE) {
    return;
    }
    if (action == DataOperation.CHANGE) {
    services.put(datumKey, StringUtils.EMPTY);
    }
    tasks.offer(Pair.with(datumKey, action));
    }

    通过观察addTask方法可以看出向上面提到的阻塞队列tasks中添加了一个对象:

    public static <A, B> Pair<A, B> with(A value0, B value1) {
    return new Pair(value0, value1);
    }

    这里的Pair.with()可以不用管,只需要知道返回的一个Pair对象包含了前面生成的key和action,action代表了操作类型是新增或者编辑删除之类的。

那么这个阻塞队列里面就包含了我们的服务信息,服务名,nameSpaceId,是否是临时实例等。再回去看notifier的run方法:

public void run() {
Loggers.DISTRO.info("distro notifier started"); for (; ; ) {
try {
Pair<String, DataOperation> pair = tasks.take();
handle(pair);
} catch (Throwable e) {
Loggers.DISTRO.error("[NACOS-DISTRO] Error while handling notifying task", e);
}
}
}

可以看出是一个线程从阻塞队列中循环拿出实例的信息到后续的handle方法:

private void handle(Pair<String, DataOperation> pair) {
try {
String datumKey = pair.getValue0();
DataOperation action = pair.getValue1(); services.remove(datumKey); int count = 0; if (!listeners.containsKey(datumKey)) {
return;
} for (RecordListener listener : listeners.get(datumKey)) { count++; try {
if (action == DataOperation.CHANGE) {
listener.onChange(datumKey, dataStore.get(datumKey).value);
continue;
} if (action == DataOperation.DELETE) {
listener.onDelete(datumKey);
continue;
}
} catch (Throwable e) {
Loggers.DISTRO.error("[NACOS-DISTRO] error while notifying listener of key: {}", datumKey, e);
}
} if (Loggers.DISTRO.isDebugEnabled()) {
Loggers.DISTRO
.debug("[NACOS-DISTRO] datum change notified, key: {}, listener count: {}, action: {}",
datumKey, count, action.name());
}
} catch (Throwable e) {
Loggers.DISTRO.error("[NACOS-DISTRO] Error while handling notifying task", e);
}
}
 @Override
public void onChange(String key, Instances value) throws Exception { Loggers.SRV_LOG.info("[NACOS-RAFT] datum is changed, key: {}, value: {}", key, value); for (Instance instance : value.getInstanceList()) { if (instance == null) {
// Reject this abnormal instance list:
throw new RuntimeException("got null instance " + key);
} if (instance.getWeight() > 10000.0D) {
instance.setWeight(10000.0D);
} if (instance.getWeight() < 0.01D && instance.getWeight() > 0.0D) {
instance.setWeight(0.01D);
}
} updateIPs(value.getInstanceList(), KeyBuilder.matchEphemeralInstanceListKey(key)); recalculateChecksum();
}

在这里拿出所有的Instance实例对象进行权重默认值的设置,之后 updateIPs(value.getInstanceList(), KeyBuilder.matchEphemeralInstanceListKey(key))方法:

public void updateIPs(Collection<Instance> instances, boolean ephemeral) {
Map<String, List<Instance>> ipMap = new HashMap<>(clusterMap.size());
for (String clusterName : clusterMap.keySet()) {
ipMap.put(clusterName, new ArrayList<>());
} for (Instance instance : instances) {
try {
if (instance == null) {
Loggers.SRV_LOG.error("[NACOS-DOM] received malformed ip: null");
continue;
} if (StringUtils.isEmpty(instance.getClusterName())) {
instance.setClusterName(UtilsAndCommons.DEFAULT_CLUSTER_NAME);
} if (!clusterMap.containsKey(instance.getClusterName())) {
Loggers.SRV_LOG
.warn("cluster: {} not found, ip: {}, will create new cluster with default configuration.",
instance.getClusterName(), instance.toJson());
Cluster cluster = new Cluster(instance.getClusterName(), this);
cluster.init();
getClusterMap().put(instance.getClusterName(), cluster);
} List<Instance> clusterIPs = ipMap.get(instance.getClusterName());
if (clusterIPs == null) {
clusterIPs = new LinkedList<>();
ipMap.put(instance.getClusterName(), clusterIPs);
} clusterIPs.add(instance);
} catch (Exception e) {
Loggers.SRV_LOG.error("[NACOS-DOM] failed to process ip: " + instance, e);
}
} for (Map.Entry<String, List<Instance>> entry : ipMap.entrySet()) {
//make every ip mine
List<Instance> entryIPs = entry.getValue();
clusterMap.get(entry.getKey()).updateIps(entryIPs, ephemeral);
} setLastModifiedMillis(System.currentTimeMillis());
getPushService().serviceChanged(this);
StringBuilder stringBuilder = new StringBuilder(); for (Instance instance : allIPs()) {
stringBuilder.append(instance.toIpAddr()).append("_").append(instance.isHealthy()).append(",");
} Loggers.EVT_LOG.info("[IP-UPDATED] namespace: {}, service: {}, ips: {}", getNamespaceId(), getName(),
stringBuilder.toString()); }

其中将Instance实例放入Map中的逻辑:

if (!clusterMap.containsKey(instance.getClusterName())) {
Loggers.SRV_LOG
.warn("cluster: {} not found, ip: {}, will create new cluster with default configuration.",
instance.getClusterName(), instance.toJson());
Cluster cluster = new Cluster(instance.getClusterName(), this);
cluster.init();
getClusterMap().put(instance.getClusterName(), cluster);
}

可以得出结论:Nacos中有一个线程从阻塞队列notifier中拿出新注册的Instance初始化处理之后根据是否是临时实例放到对应的HashSet中,即保存了新注册的实例信息。而每次注册的结果就是将注册信息放入到阻塞队列中去;这种异步处理的方式使得Nacos的TPS可以达到1w3+,因为其保存的服务信息对实时性要求并不高,这种场景下使用异步处理是合适的。而Nacos在更新实例信息时采用了写时复制的思想,保证了服务上下线修改Map时的效率。由于其写时复制复制的其实是Cluster中的内容,不是复制整个Map,所以它的效率也是很高的。

附一张图灵学院诸葛老师总结的流程图:

Nacos源码阅读心得的更多相关文章

  1. ThinkPhp 源码阅读心得

    php 中header 函数 我可能见多了,只要用来跳转.今天在阅读TP源码的时候发现,header函数有第三个参数.有些困惑所以找到手册查阅下,发现 void header ( string $st ...

  2. breeze源码阅读心得

            在阅读Spark ML源码的过程中,发现很多机器学习中的优化问题,都是直接调用breeze库解决的,因此拿来breeze源码想一探究竟.整体来看,breeze是一个用scala实现的基 ...

  3. mybatis源码阅读心得

    第一天阅读源码及创建时序图.(第一次用prosson画时序图,挺丑..) 1.  调用 SqlSessionFactoryBuilder 对象的 build(inputStream) 方法: 2.   ...

  4. commons-io源码阅读心得

    FileCleanTracker: 开启一个守护线程在后台默默的删除文件. /* * Licensed to the Apache Software Foundation (ASF) under on ...

  5. 源码阅读之mongoengine(0)

    最近工作上用到了mongodb,之前只是草草了解了一下.对于NoSQL的了解也不是太多.所以想趁机多学习一下. 工作的项目直接用了pymongo来操作直接操作mongodb.对于用惯了Djongo O ...

  6. 【安卓本卓】Android系统源码篇之(一)源码获取、源码目录结构及源码阅读工具简介

    前言        古人常说,“熟读唐诗三百首,不会作诗也会吟”,说明了大量阅读诗歌名篇对学习作诗有非常大的帮助.做开发也一样,Android源码是全世界最优秀的Android工程师编写的代码,也是A ...

  7. Redis源码阅读(一)事件机制

    Redis源码阅读(一)事件机制 Redis作为一款NoSQL非关系内存数据库,具有很高的读写性能,且原生支持的数据类型丰富,被广泛的作为缓存.分布式数据库.消息队列等应用.此外Redis还有许多高可 ...

  8. Nacos2.X源码阅读总结

    前言 Nacos是一个Alibaba出品的高性能微服务时代产出的组件,集注册和配置中心为一体.那么Nacos为什么这么高性能呢?总结以下几点: 1:基于阿里自研的distro协议进行Nacos把不同节 ...

  9. Nacos源码系列—关于服务注册的那些事

    点赞再看,养成习惯,微信搜索[牧小农]关注我获取更多资讯,风里雨里,小农等你,很高兴能够成为你的朋友. 项目源码地址:公众号回复 nacos,即可免费获取源码 简介 首先我们在看Nacos源码之前,要 ...

  10. 【原】FMDB源码阅读(三)

    [原]FMDB源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 FMDB比较优秀的地方就在于对多线程的处理.所以这一篇主要是研究FMDB的多线程处理的实现.而 ...

随机推荐

  1. SpringIoc容器之Aware

    1 前言 Aware是Spring提供的一个标记超接口,指示bean有资格通过回调样式的方法由Spring容器通知特定的框架对象,以获取到容器中特有对象的实例的方法之一.实际的方法签名由各个子接口确定 ...

  2. 向量数据库Faiss的搭建与使用

    向量数据库Faiss是Facebook AI研究院开发的一种高效的相似性搜索和聚类的库.它能够快速处理大规模数据,并且支持在高维空间中进行相似性搜索.本文将介绍如何搭建Faiss环境并提供一个简单的使 ...

  3. CF1580C Train Maintenance题解

    我们以 \(\sqrt m\) 为分界点来进行平衡. 设当前在进行第 \(k\) 次操作,询问 \(i\). 对于 \(x_i + y_i \leq \sqrt m\),可以在 \(last_{x_i ...

  4. 高效运营新纪元:智能化华为云Astro低代码重塑组装式交付

    摘要:程序员不再需要盲目编码,填补单调乏味的任务空白,他们可以专注于设计和创新:企业不必困惑于复杂的开发过程,可以更好地满足客户需求以及业务策略迭代. 本文分享自华为云社区<高效运营新纪元:智能 ...

  5. [最长回文字符串]manacher马拉车

    manacher马拉车 https://www.luogu.com.cn/problem/P3805 闲言一下:花了一个中午终于把 manacher 给搞懂了.本文将以一个蒟蒻的身份来,来写写马拉车算 ...

  6. 【技术实战】Vue技术实战【三】

    需求实战一 效果展示 代码展示 <template> <div style="display: flex;"> <div style="di ...

  7. 重温C#中的值类型和引用类型

    在C#中,数据类型分为值类型和引用类型两种. 引用类型变量存储的是数据的引用,数据存储在数据堆中,而值类型变量直接存储数据.对于引用类型,两个变量可以引用同一个对象.因此,对一个变量的操作可能会影响另 ...

  8. [golang]使用tail追踪文件变更

    简介 借助 github.com/hpcloud/tail ,可以实时追踪文件变更,达到类似shell命令tail -f的效果. 示例代码 以下示例代码用于实时读取nginx的access.log日志 ...

  9. 解锁Spring组件扫描的新视角

    本文分享自华为云社区<Spring高手之路10--解锁Spring组件扫描的新视角>,作者: 砖业洋__. 首先,我们将探讨一些Spring框架中IOC(Inversion of Cont ...

  10. maven系列:基本命令(创建类、构建打包类、IDEA中操作)

    目录 一.创建类命令 创建普通Maven项目 创建Web Maven项目 发布第三方Jar到本地库中 二.构建打包类命令 编译源代码 编译测试代码 编译测试代码 打包项目 清除打包的项目 清除历史打包 ...