nacos:
startup.sh -m standalone 单独启动
客户端注册服务
nacos 集成了 dubbo的话会在 DubboServiceRegistrationNonWebApplicationAutoConfiguration 做服务注册
否则在 NacosServiceRegistryAutoConfiguration -> NacosAutoServiceRegistration -> AbstractAutoServiceRegistration 的 onApplicationEvent 的 bind() -> start() -> register()
进入到 NacosServiceRegistry 的 register 方法
NacosServiceRegistry
Nacos服务端的处理
服务端提供了一个InstanceController类,在这个类中提供了服务注册相关的API,而服务端发起初测时,调用的接口是:[post]: /nacos/v1/ns/instance
serviceName: 代表客户端的项目名称
namespace: nacos 的namespace
InstanceController -> register() -> InstanceOperatorServiceImpl
最后进入 ServiceManager 的 registerInstance 方法
//创建一个空服务,在Nacos控制台服务列表展示的服务信息,实际上是初始化一个serviceMap,它是一个ConcurrentHashMap集合
createEmptyService(namespaceId, serviceName, instance.isEphemeral());
//从serviceMap中,根据namespaceId和serviceName得到一个服务对象
Service service = getService(namespaceId, serviceName);
//调用addInstance创建一个服务实例
addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
把服务实例添加到集合中,然后基于一致性协议进行数据的同步。
消费者的服务查询
服务注册成功之后,消费者就可以从nacos server中获取到服务提供者的地址,然后进行服务的调用。
在服务消费中,有一个核心的类 NacosDiscoveryClient 负责和nacos交互,去获得服务提供者的地址信息。
NacosDiscoveryAutoConfiguration -> NacosDiscoveryClientConfiguration -> NacosDiscoveryClient
NacosDiscoveryClient 中提供了一个 getInstances 方法用来根据服务提供者名称获取服务提供者的url地址的方法.客户端启动获取服务列表
HostReactor.getServiceInfo
updateServiceNow, 立马从Nacos server中去加载服务地址信息
scheduleUpdateIfAbsent 开启定时调度,每10s去查询一次服务地址
HostReactor.scheduleUpdateIfAbsent 在查询服务调用 getServiceInfo 方法的代码中,会开启一个定时任务,这个任务会在默认在1s之后开始执行。而任务的具体实现是一个UpdateTask。
HostReactor.UpdateTask run 方法 执行定时查询服务列表的任务
Raft 选举
RaftCore 里面的 init 方法
//开启定时任务,每500ms执行一次,用来判断是否需要发起leader选举,每500ms发起一次心跳
masterTask = GlobalExecutor.registerMasterElection(new MasterElection());
//开启心跳机制
heartbeatTask = GlobalExecutor.registerHeartbeat(new HeartBeat());
nacos-配置中心
在spring boot项目启动时,有一个prepareContext的方法,它会回调所有实现了ApplicationContextInitializer的实例,来做一些初始化工作。
NacosPropertySourceLocator
//加载共享配置 loadSharedConfiguration(composite);
//加载扩展配置 loadExtConfiguration(composite);
//加载自身配置 loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
loadNacosDataIfPresent 调用 loadNacosPropertySource 加载存在的配置信息。
loadNacosPropertySource -> loadNacosData -> configService.getConfig -> NacosConfigService.getConfigInner
NacosConfigService.getConfigInner
先从本地磁盘中加载配置,因为应用在启动时,会加载远程配置缓存到本地,如果本地文件的内容不为空,直接返回。(在应用启动时ClientWorker会维护两个线程池用来获取配置信息)
如果本地文件的内容为空,则调用worker.getServerConfig加载远程配置
如果出现异常,则调用本地快照文件加载配置
ClientWorker.getServerConfig
通过agent.httpGet发起http请求,获取远程服务的配置。
ClientWorker 可以看到 ClientWorker 除了将 HttpAgent 维持在自己内部,还创建了两个线程池:
第一个线程池是只拥有一个线程用来执行定时任务的 executor,executor 每隔 10ms 就会执行一次 checkConfigInfo() 方法,从方法名上可以知道是每 10 ms 检查一次配置信息。
最终执行 LongPollingRunnable.run
第二个线程池是一个普通的线程池,从 ThreadFactory 的名称可以看到这个线程池是做长轮询的。
ClientWorker.LongPollingRunnable 每个长轮训 LongPullingRunnable 任务默认处理3000个监听配置集。如果超过3000, 则需要启动多个 LongPollingRunnable 去执行。
checkUpdateDataIds 通过长轮训的方式,从远程服务器获得变化的数据进行返回
首先从cacheDatas集合中找到isUseLocalConfigInfo为false的缓存
调用 checkUpdateConfigStr -> agent.httpPost 长轮询
getServerConfig
根据dataId、group、tenant等信息,使用http请求从远程服务器上获得配置信息,读取到数据之后缓存到本地文件中
服务端处理长轮询请求
ConfigController.getListeners 这是客户端的长轮询请求
clientLongPolling 到底做了什么操作。
allSubs.add(this); allSubs是一个队列,队列里面放了 ClientLongPolling 这个对象
这个任务要阻塞29.5s才能执行,因为立马执行没有任何意义,毕竟前面已经执行过一次了如果在29.5s+之内,数据发生变化,需要提前通知。需要有一种监控机制
DataChangeTask.run
也就是服务端数据发生变更的时间,就会执行一个 DataChangeTask 的线程
它里面有一个循环迭代器,从allSubs里面获得 ClientLongPolling 最后通过 clientSub.sendResponse 把数据返回到客户端。所以,这也就能够理解为何数据变化能够实时触发更新了。
AsyncContext介绍
Servlet 3.0的异步处理支持特性,使Servlet 线程不再需要一直阻塞,直到业务处理完毕才能再输出响应,最后才结束该 Servlet 线程。
在接收到请求之后,Servlet 线程可以将耗时的操作委派给另一个线~程来完成,自己在不生成响应的情况下返回至容器。针对业务处理较耗时的情况,这将大大减少服务器资源的占用,并且提高并发处理速度
Servlet 3.0新增了异步处理,可以先释放容器分配给请求的线程与相关资源,减轻系统负担,原先释放了容器所分配线程的请求,其响应将被延后,可以在处理完成(例如长时间运算完成、所需资源已获得)时再对客户端进行响应。