Nacos服务端代码分析

InstanceController

进入InstanceController类,可以看到一个register方法,就是服务注册的方法了:

点击查看代码
    @CanDistro
    @PostMapping
    @Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
    public String register(HttpServletRequest request) throws Exception {
        //获取namespaceId
        final String namespaceId = WebUtils
                .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
        //尝试获取serviceName,其格式为 group_name@@service_name
        final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
        NamingUtils.checkServiceNameFormat(serviceName);
        // 解析出实例信息,封装为Instance对象
        final Instance instance = parseInstance(request);
        // 注册实例
        serviceManager.registerInstance(namespaceId, serviceName, instance);
        return "ok";
    }
这里,进入到了serviceManager.registerInstance()方法中。 ## ServiceManager ServiceManager就是Nacos中管理服务、实例信息的核心API,其中就包含Nacos的服务注册表:


而其中的registerInstance方法就是注册服务实例的方法:

我们可以继续跟进createEmptyService这个方法:

再继续跟进createServiceIfAbsent方法如果缺席就创建,也就是说不存在就创建,第一次来就创建。

进来createServiceIfAbsent以后,首先尝试从注册表中获取一次服务,跟进去

如果不为null则代表有,也就是组Map有,通过这个chooseServiceMap方法得到组map,跟进来:

其实就是从服务列表里面根据命名空间id获取Map。接着通过组的服务名获取Service:

如果为空,说明该服务是第一次注册,创建新的Service:

这个groupName其实是serviceName的一部分是两个@符号隔开的,我们再第一次创建服务的时候传进去的cluster是null,随意上图中的cluster肯定是null,里面的代码也即不会执行。
记下来就是putServiceAndInit,服务创建好就要把其put进来。继续跟进:

第一个方法putService,继续:
首先是判断一下serviceMap注册表是否有这个服务,这里其实是为了提高性能(单例模式就有这种判断),如果没有就设置一把锁,出于线程安全考虑,当我在创建的时候别人就不要创建了,否则出现并发写修改的安全问题,然后在这个同步代码块里面又判断一次有没有,其实就是为了保证线程安全,如果确实没有就往注册表里面put:

但这个ConcurrentSkipListMap并不是在同步代码块里填的,而是出来这个同步代码块,在这个:
serviceMap.get(service.getNamespaceId()).putIfAbsent(service.getName(), service);
这句代码填入的真正的服务,这样确保我们的服务一定能写进去。
其中的putIfAbsent表示:如果不存在才put,如果并发情况下有其他人put进来了,就不往里面put了。这就是保证put动作的有效性。
然后service已经有了,我们取一次,并进行初始化,其实是进行一个健康检测:

接下来进行一致性服务的监听:consistencyService
监听服务的变更:

到这里,putServiceAndInit这个服务注册和初始化就做完了:

但是要记得putServiceAndInit方法里面的两个监听器。后续分析会用到
继续往下执行createEmptyService后,我们需要添加实例到service中addInstance这个方法,跟进:

addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);记住这些参数。
比较重要的是上图的同步代码块

点击查看代码
synchronized (service) {
          // 拷贝注册表中旧的实例列表,然后结合新注册的实例,得到最终的实例列表
          List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);

          //封装实例列表到instances
          Instances instances = new Instances();
          instances.setInstanceList(instanceList);

          //更新注册表(更新本地注册表、同步给nacos集群中的其他节点)
          consistencyService.put(key, instances);
      }
也就是说同一个服务只能有一个线程完成服务注册的动作。因为微服务集群中,假如有成百上千的服务,而这些服务同时来作注册,那一个服务里面往往有多个实例,假如这个服务将来有100个实例,那这一百个实例就有可能并行来执行,如果大家都来修改就有可能造成一些脏写的问题,所以这里要加锁,就是为了在多线程同时操作一个服务资源时为了保证线程安全就要加上同步锁,所以同一个服务的多个实例只能串行注册,避免并发修改。 从上面的同步代码块总共三步: 1.拷贝注册表中旧的实例列表,然后结合新注册的实例,得到最终的实例列表 2.封装实例列表到instances 3.更新注册表(更新本地注册表、同步给nacos集群中的其他节点) 整个过程第三步最耗时:consistencyService.put(key, instances);Ctrl+Alt+鼠标左键put这个方法,可以发现其有很多的实现:


因为ConsistencyService本身是个接口,它有很多的实现,Ctrl+h可以看到如下图:

可以发现其默认注册的就是delegate这个,意思就是委托:


它并不是一种真实的实现,是一种装饰模式。我们第一次注册进来的就是它:consistencyDelegate


所以我们调用的put就是DelegateConsistencyServiceImpl的put方法:

这个key就是前面传过来的服务标识serviceId,它会根据key来得到哪个service:

临时实例采用的协议是Distro协议,默认就是临时协议实现类的这个put方法:


上图主要的操作是:
1.更新本地注册表
2.同步给Nacos集群中的其他节点
继续跟进这个onput方法:


我们关注下这个dataStore:


它其实里面维护了一个dataMap存放serviceId和Datum的
private Map<String, Datum> dataMap = new ConcurrentHashMap<>(1024);
我们再跟进addTask这个方法:

它将那些serviceId和实例数据包存放到了任务里等待DistroConsistencyServiceImpl初始化后利用线程池来执行里面的具体的中注册。

找到这个类的构造函数:


发现它会利用一个全局的线程池来执行notifier任务,继续跟如notifier这个run方法


可以发现里面有个for循环它会从阻塞队列中获取任务来执行,阻塞队列BlockingQueue是指:当队列中有元素时这个take会返回,当无元素时就会take会waite阻塞,线程释放cpu执行权在这等着被唤醒。所以这个一只循环的for无所谓,它不是盲等,它有等待唤醒机制的。因此,只要你往这个队列里面投放一个任务,take才会醒过来取这个任务,然后handle执行。而这个取的动作是用线程池异步执行的,也就是说这个活跟服务注册就没关系了,是一个单独的任务。
相当于在我们执行onPut操作的时候,先更新本地注册表,其实就是将任务放到队列里面就结束了,然后不管你有没有执行完,我接着执行同步nacos集群的操作,这就是异步更新的作用,这样就提高了注册的效率,性能。
回到put这个方法这:

distroProtocol是发行版协议的意思,sync就是同步的意思,进入sync:


可以发现,将数据同步到nacos集群的其他节点也是异步的。

所以,Nacos内部接受到注册的请求时,不会立即写数据,而是将服务注册的任务放入一个阻塞队列里面立即响应给客户端。然后利用线程池
读取阻塞队列中的任务,异步来完成本地实例和集群中其他实例的更新,从而提高并发能力。这个阻塞队列大小为1024。

posted @ 2023-06-30 19:53  xycccode  阅读(112)  评论(0编辑  收藏  举报