Dubbo使用Nacos作为注册中心如何感知到服务变更事件?

369 阅读3分钟

原理呢我们得从服务向Nacos注册时的源码开始分析,这里在向nacos发送http请求注册的时候会开启一个线程进行死循环从阻塞队列中获取事件。

1.NacosRegistry

//使用模板模式
private void doSubscribe(final URL url, final NotifyListener listener, final Set<String> serviceNames) {
    execute(namingService -> {
        for (String serviceName : serviceNames) {
            //获取服务实例,这里会向nacos获取对应服务名存在的所有实例
            List<Instance> instances = namingService.getAllInstances(serviceName);
            //唤醒一次监听者,也就是RegistryDirectory类
            notifySubscriber(url, listener, instances);
            //注册订阅事件,后续所有通知监听者都是从这里进行。跟下去
            subscribeEventListener(serviceName, url, listener);
        }
    });
}
private void subscribeEventListener(String serviceName, final URL url, final NotifyListener listener)
        throws NacosException {
    if (!nacosListeners.containsKey(serviceName)) {
        EventListener eventListener = event -> {
            if (event instanceof NamingEvent) {
                NamingEvent e = (NamingEvent) event;
                //这里是感知到服务变化就会执行的逻辑代码
                notifySubscriber(url, listener, e.getInstances());
            }
        };
        //向nacos的服务订阅该事件,跟进去
        namingService.subscribe(serviceName, eventListener);
        //防重
        nacosListeners.put(serviceName, eventListener);
    }
}

2.NacosNamingService

@Override
public void subscribe(String serviceName, String groupName, List<String> clusters, EventListener listener) throws NacosException {
//添加一个事件监听
eventDispatcher.addListener(hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName),
        StringUtils.join(clusters, ",")), StringUtils.join(clusters, ","), listener);
}

EventDispatcher是一个很重要的类,里面的主要逻辑是nacos的事件监听转发功能。

3.EventDispatcher

public EventDispatcher() {
    
    executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r, "com.alibaba.nacos.naming.client.listener");
            thread.setDaemon(true);

            return thread;
        }
    });
    //启动一个守护线程执行任务
    executor.execute(new Notifier());
}
private class Notifier implements Runnable {
    @Override
    public void run() {
        while (true) {
            ServiceInfo serviceInfo = null;
            try {
                //获取阻塞队列中消息的变更,一但阻塞队列中有新的事件,就会获取出来
                //然后进行消费处理
                serviceInfo = changedServices.poll(5, TimeUnit.MINUTES);
            } catch (Exception ignore) {
            }

            if (serviceInfo == null) {
                continue;
            }

            try {
                List<EventListener> listeners = observerMap.get(serviceInfo.getKey());

                if (!CollectionUtils.isEmpty(listeners)) {
                    for (EventListener listener : listeners) {
                        List<Instance> hosts = Collections.unmodifiableList(serviceInfo.getHosts());
                        //这里会执行NacosRegistry中的EventListener的事件
                        listener.onEvent(new NamingEvent(serviceInfo.getName(), serviceInfo.getGroupName(), serviceInfo.getClusters(), hosts));
                    }
                }

            } catch (Exception e) {
                NAMING_LOGGER.error("[NA] notify error for service: "
                    + serviceInfo.getName() + ", clusters: " + serviceInfo.getClusters(), e);
            }
        }
    }
}

上面的changedServices有一个serviceChanged方法,主要是用来向阻塞队列中添加新变化的服务,这里又是谁在调用呢?答案是Nacos的Udp事件,在Nacos中如果发生服务变更的事件包括服务上线和下线,会向所有客户端发送一个Udp请求过来通知服务变更事件。

4.PushReceiver

@Override
public void run() {
    while (true) {
        try {
            // byte[] is initialized with 0 full filled by default
            byte[] buffer = new byte[UDP_MSS];
            DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

            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)) {
                //这里用心跳类来接收udp数据
                hostReactor.processServiceJSON(pushPacket.data);

                // send ack to server
                ack = "{"type": "push-ack""
                    + ", "lastRefTime":"" + pushPacket.lastRefTime
                    + "", "data":" + """}";
            } else if ("dump".equals(pushPacket.type)) {
                // dump data to server
                ack = "{"type": "dump-ack""
                    + ", "lastRefTime": "" + pushPacket.lastRefTime
                    + "", "data":" + """
                    + StringUtils.escapeJavaScript(JSON.toJSONString(hostReactor.getServiceInfoMap()))
                    + ""}";
            } else {
                // do nothing send ack only
                ack = "{"type": "unknown-ack""
                    + ", "lastRefTime":"" + pushPacket.lastRefTime
                    + "", "data":" + """}";
            }

            udpSocket.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);
        }
    }
}

5.HostReactor

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 ignore
        return oldService;
    }

    boolean changed = false;

    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);
            }
        }

        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);
            }

        }

        if (newHosts.size() > 0) {
            changed = true;
            NAMING_LOGGER.info("new ips(" + newHosts.size() + ") service: "
                + serviceInfo.getKey() + " -> " + JSON.toJSONString(newHosts));
        }

        if (remvHosts.size() > 0) {
            changed = true;
            NAMING_LOGGER.info("removed ips(" + remvHosts.size() + ") service: "
                + serviceInfo.getKey() + " -> " + JSON.toJSONString(remvHosts));
        }

        if (modHosts.size() > 0) {
            changed = true;
            NAMING_LOGGER.info("modified ips(" + modHosts.size() + ") service: "
                + serviceInfo.getKey() + " -> " + JSON.toJSONString(modHosts));
        }

        serviceInfo.setJsonFromServer(json);

            if (newHosts.size() > 0 || remvHosts.size() > 0 || modHosts.size() > 0) {
            ////这里就是向阻塞队列中添加服务信息
            eventDispatcher.serviceChanged(serviceInfo);
            DiskCache.write(serviceInfo, cacheDir);
        }

    } else {
        changed = true;
        NAMING_LOGGER.info("init new ips(" + serviceInfo.ipCount() + ") service: " + serviceInfo.getKey() + " -> " + JSON
            .toJSONString(serviceInfo.getHosts()));
        serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);
        //这里就是向阻塞队列中添加服务信息
        eventDispatcher.serviceChanged(serviceInfo);
        serviceInfo.setJsonFromServer(json);
        DiskCache.write(serviceInfo, cacheDir);
    }

    MetricsMonitor.getServiceInfoMapSizeMonitor().set(serviceInfoMap.size());

    if (changed) {
        NAMING_LOGGER.info("current ips:(" + serviceInfo.ipCount() + ") service: " + serviceInfo.getKey() +
            " -> " + JSON.toJSONString(serviceInfo.getHosts()));
    }

    return serviceInfo;
}