How Nacos Leverages UDP for Real‑Time Service Instance Push

This article dissects Nacos 2.0's UDP‑based service‑instance change notification, explaining client‑side listening, UDP port registration, server‑side storage, push logic, acknowledgment handling, and current design limitations, all backed by concrete Java code snippets.

Senior Brother's Insights
Senior Brother's Insights
Senior Brother's Insights
How Nacos Leverages UDP for Real‑Time Service Instance Push

UDP Notification Basic Principle

Nacos uses UDP as a bidirectional channel: both client and server open a UDP listener. When a client subscribes via HTTP, it sends its UDP IP and port to the registry, which stores this information in a PushClient object for later push notifications.

Nacos UDP basic principle
Nacos UDP basic principle

Client‑Side UDP Listening and Processing

When NamingHttpClientProxy is instantiated, it creates a PushReceiver that opens a DatagramSocket (random port if not configured) and runs a single‑thread executor.

public NamingHttpClientProxy(String namespaceId, SecurityProxy securityProxy,
        ServerListManager serverListManager, Properties properties,
        ServiceInfoHolder serviceInfoHolder) {
    // ...
    this.beatReactor = new BeatReactor(this, properties);
    this.pushReceiver = new PushReceiver(serviceInfoHolder);
    // ...
}
PushReceiver

constructor performs four key steps:

Hold a reference to ServiceInfoHolder.

Obtain the UDP port from environment variable push.receiver.udp.port.

Instantiate a DatagramSocket for sending/receiving.

Create a single‑thread ScheduledExecutorService and execute itself.

The run method continuously receives packets, decompresses them, parses the JSON into a PushPacket, processes service info, builds an ACK JSON, and sends the ACK back.

@Override
public void run() {
    while (!closed) {
        try {
            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();
            PushPacket pushPacket = JacksonUtils.toObj(json, PushPacket.class);
            // handle different packet types and build ACK
            udpSocket.send(new DatagramPacket(ack.getBytes(UTF_8), ack.length(),
                    packet.getSocketAddress()));
        } catch (Exception e) {
            if (closed) {
                return;
            }
            // error handling
        }
    }
}

Client Uploading UDP Information

The client adds its UDP port to request parameters via NamingHttpClientProxy.queryInstancesOfService. The subscription interface uses the actual port from pushReceiver.getUdpPort().

@Override
public ServiceInfo subscribe(String serviceName, String groupName, String clusters) throws NacosException {
    return queryInstancesOfService(serviceName, groupName, clusters,
            pushReceiver.getUdpPort(), false);
}

The port value is read from environment variable PropertyKeyConst.PUSH_RECEIVER_UDP_PORT:

public static String getPushReceiverUdpPort() {
    return System.getenv(PropertyKeyConst.PUSH_RECEIVER_UDP_PORT);
}

Server‑Side UDP Storage

When the server receives a request, it extracts the UDP port (default 0) and creates a Subscriber object that includes the client IP and UDP port.

@GetMapping("/list")
public Object list(HttpServletRequest request) throws Exception {
    int udpPort = Integer.parseInt(WebUtils.optional(request, "udpPort", "0"));
    Subscriber subscriber = new Subscriber(clientIP + ":" + udpPort, agent, app,
            clientIP, namespaceId, serviceName, udpPort, clusters);
    return getInstanceOperator().listInstance(namespaceId, serviceName,
            subscriber, clusters, healthyOnly);
}

If udpPort > 0 and the client supports UDP, the server stores the client as a PushClient in a concurrent map.

public void addClient(String namespaceId, String serviceName, String clusters,
        String agent, InetSocketAddress socketAddr, DataSource dataSource,
        String tenant, String app) {
    PushClient client = new PushClient(namespaceId, serviceName, clusters,
            agent, socketAddr, dataSource, tenant, app);
    addClient(client);
}

Server‑Side UDP Push

When a service instance changes, ServiceChangeEvent triggers UdpPushService.onApplicationEvent. It iterates over stored PushClient objects, removes zombie clients, prepares an AckEntry containing the updated service data (compressed if possible), and sends it via UDP.

for (PushClient client : clients.values()) {
    if (client.zombie()) {
        clients.remove(client.toString());
        continue;
    }
    AckEntry ackEntry = prepareAckEntry(client, data, lastRefTime);
    udpPush(ackEntry);
}

Server‑Side UDP Reception

The server runs a static DatagramSocket receiver thread that listens for ACK packets from clients, validates them, and updates internal state.

static {
    try {
        udpSocket = new DatagramSocket();
        Receiver receiver = new Receiver();
        Thread inThread = new Thread(receiver);
        inThread.setDaemon(true);
        inThread.setName("com.alibaba.nacos.naming.push.receiver");
        inThread.start();
    } catch (SocketException e) {
        // log error
    }
}

Design Shortcomings

The documentation does not clearly describe how to enable or configure UDP notifications, leaving users unaware of the feature.

In cloud environments the server’s UDP port is randomly assigned and often blocked by firewalls, making UDP communication unreliable.

Conclusion

The Nacos UDP notification mechanism consists of three steps: (1) the client opens a UDP listener to receive instance‑change pushes; (2) the client registers its UDP endpoint via the subscription API; (3) the server pushes change events to registered clients using UDP. Understanding this flow helps developers decide when UDP is appropriate and highlights potential reliability issues in cloud deployments.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendJavaPush NotificationMicroservicesservice discoveryNacosUDP
Senior Brother's Insights
Written by

Senior Brother's Insights

A public account focused on workplace, career growth, team management, and self-improvement. The author is the writer of books including 'SpringBoot Technology Insider' and 'Drools 8 Rule Engine: Core Technology and Practice'.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.