目录
一、RegistryDirectory
Consumer端启动时,Dubbo的RegistryDirectory中向Nacos发起订阅,并把自己做为回调通知的Listenering执行器:
当Nacos中注册的Provider实例有发生变化时,会及时的收到回调通知,RegistryDirectory.notify(List<URL> urls)方法会被调用,用于处理接收到的新的Provider实例:
二、NacosRegistry
RegistryDirectory的subscribe(URL url)方法通过NacosRegistry中的doSubscribe(...)方法,往Nacos服务端发起注册及获取Provider信息:
方法doSubscribe(URL, NotifyListener)中根据Consumer端的url生成对应的Provider服务名称,如此时consumer的url如下:
consumer://192.168.22.198/cn.raysonblog.shopserviceprovider.service.RpcShopService?application=shop-service-consumer&category=providers,configurators,routers&dubbo=2.0.2&generic=false&interface=cn.raysonblog.shopserviceprovider.service.RpcShopService&lazy=false&logger=log4j2&methods=sayHello,getConfigServiceName&pid=397&qos.enable=false&release=2.7.3&side=consumer&sticky=false×tamp=1629807903321
此时生成的Provider服务名称serviceNames为:
providers:cn.raysonblog.shopserviceprovider.service.RpcShopService::
三、NacosNamingService
在方法doSubscribe(URL, NotifyListener, Set<String>)中通过namingService.getAllInstances(serviceName)获取当前Consumer对应服务的所有Provider节点,namingService代表的类为NacosNamingService ,在该类中会通过getAllInstances(...)方法调用HostReactor的getServiceInfo方法获取ServiceInfo对象:
四、HostReactor
getServiceInfo(...)方法会判断本地是否已经有Provider节点信息,如果没有则通过updateServiceNow(...)立即从Nacos服务获取:
以下将立即获取更新及将更新加到定时任务中分别说明。
1、立即获取更新
updateServiceNow(...)方法中是通过HTTP请求向Nacos获取最新的配置信息:
此处的serverProxy对象是NamingProxy类的实例,queryList(...)方法:
会调用Nacose服务的接口“/nacos/v1/ns/instance/list”获取数据,传入的参数:
- namespaceId:指定获取配置的命名空间,dubbo可以通过配置项dubbo.registry.address中的nacos地址中增加namespace参数进行指定,如下所示:
- serviceName:当前Consumer应用的要获取的Provider的服务名称,Consumer自动生成,类似如下所示:
DEFAULT_GROUP@@providers:cn.raysonblog.shopserviceprovider.service.RpcShopService::
- clusters:暂时不清楚,代码中也没有注释,默认情况下是new了一个空的ArrayList<String>,到这里时该参数的值ArrayList中的值按英文逗号分隔后的拼接:StringUtils.join(clusters, ","),因而其值还是空字符串"";
- udpPort:用于接收Nacos的UDP连接的端口,Nacos服务端往当前应用发送推送消息时,就往该端口推送,接收UDP的实现为PushReceiver,后面单独介绍该实现;
- clientIp:当前应用所在服务器的IP地址,用于接收Nacos服务的UDP推送等;
- healthOnly:用于指定是否只获取健康的Provider示例,默认值为false,跟了一下queryList方法的调用,共有三个地方且都在HostReactor中,传入的值都是false,因而目前还没有发现可以通过配置修改该值;
调用Nacos服务获取Provider信息的接口,可能成功也可能失败,如果失败则不更新Provider的信息;如果成功则会比较本地与从服务端获取的进行刷新时间的比较,以便于确认其是否为过期服务等验证,验证通过后才会使用新的Provider信息更新本地的Provider信息,如下HostReactor.processServiceJSON(String)方法所示:
2、定时更新配置
定时更新是方法scheduleUpdateIfAbsent(serviceName, clusters)调用其中的addTask方法增加到定时任务中:
public synchronized ScheduledFuture<?> addTask(UpdateTask task) {
return executor.schedule(task, DEFAULT_DELAY, TimeUnit.MILLISECONDS);
}
初次执行周期延迟为DEFAULT_DELAY,默认值为1000,即1秒。
其中UpdateTask为真正执行的线程,检查是否有新的Provider节点信息,实现如下:
通过UpdateTask的线程实现可以得知,Nacos作为注册中心时,Consumer会通过HostReactor中的UpdateTask线程间隔10秒去服务端拉取最新的Provider信息实现的,作为对监听即时回调的补充,避免由于监听回调用处理失败导致不能够获取到完整的Provider实例的问题。
五、EventDispatcher
HostReactor.processServiceJSON(String)方法会调用EventDispatcher.serviceChanged(ServiceInfo)方法,将变化的ServiceInfo加入到名为changedServices的LinkedBlockingQueue队列中,然后会通过EventDispatcher中的线程Notifier进行处理:
EventListener会调用Dubbo服务提供者变更通知NotifyListener的实现类RegistryDirectory,通知Dubbo Consumer端发起对这些可用Provider的连接,如果原来有连接也会重新发起连接,后面会有操作步骤及日志展示。
六、PushReceiver
PushReceiver为Consumer接收Nacos服务端UDP通知的监听器,以单独的线程进行监听,分为三种消息类型:dom、service及dump,dom及service消息类型都是服务端给Consumer发送消息,如Provider节点变更消息,然后通过hostReactor.processServiceJSON(...)处理这些信息,如下图红枉中的代码所示;dump消息类型为服务端希望获取Consumer的中现在的Provider信息:
但是不论我是增加新的Provider还是断掉Provider,该线程就从来没有读取到数据,说明Nacos服务端没有通过UDP消息的方式往客户端推送消息,那问题来了,Nacos服务端会在什么时候触发UDP通知呢?
七、获取最新Provider实例的三种方式
综上所述,Nacos做为Dubbo注册中心时,获取到最新的Provider实例有以下三种方式:
1、通过注册的回调处理的org.apache.dubbo.registry.integration.RegistryDirectory.notfity()方法,即时的接收实例变更的回调用;
2、通过线程com.alibaba.nacos.client.naming.core.HostReactor.UpdateTask定期的去查询当前应用的Provider实例,避免由于监听回调用处理失败导致不能够获取到完整的Provider实例的问题;
3、通过UDP服务com.alibaba.nacos.client.naming.core.PushReceiver线程,接收Provider实例变化的通知,虽然在本次调试中没有触发这样的请求,但是其应该是有触发机制的。
八、新增加Provider的日志分析
现在已经启动一台Provider以及一台Consumer,现在观察一下新增加Provider时,Consumer在获取到新的Provider,除了对新节点进行重连以下,是否会重新对已经存在Provider再次发起连接。
启动第二台Provider 2
先看一下启动第二个Provider节点的启动日志:
55分19秒启完成,55分20秒显示成功注册到Nacos。
查看Consumer此时的日志变化:
Consumer在55分21秒的时候显示收到Nacos的通知,并在同一秒的时间成功与Provider连接。
通过上面的日志可以看到,Provider在启动成功后的一秒钟之内,Consumer收到通知并成功与Provider进行连接。
与此同时Provider 1的日志控制台有重新注册到Nacos的日志:
启动第三台Provider 3
先看一下启动第三个Provider节点的启动日志:
56分17秒启完成,56分18秒显示成功注册到Nacos。
查看Consumer此时的日志变化:
Consumer在56分21秒的时候显示收到Nacos的通知,并在同一秒的时间成功与Provider连接。
通过上面的日志可以看到,Provider在启动成功后的四秒钟之内,Consumer收到通知并成功与Provider进行连接。
与此同时Provider 1的日志控制台有重新注册到Nacos的日志:
与此同时Provider 2的日志控制台有重新注册到Nacos的日志:
通过以上几个Provider的加入及日志分析,说明Consumer有重新往原来已经建立了连接的Provider发起了建立连接的请求。