How Long Polling Powers Real‑Time Config Updates in Nacos & Apollo

This article explains the principles of long polling used by configuration centers like Nacos and Apollo, compares it with traditional polling and push models, and provides a complete Java demo showing client and server implementations for dynamic configuration updates.

Alibaba Cloud Developer
Alibaba Cloud Developer
Alibaba Cloud Developer
How Long Polling Powers Real‑Time Config Updates in Nacos & Apollo

1 Preface

Traditional static configuration requires restarting an application to change a setting, which is cumbersome for runtime‑aware flags. A configuration center solves this by providing dynamic push, especially in micro‑service architectures.

The core capability is dynamic push. Popular centers such as Nacos and Apollo use long polling rather than persistent connections. This article explains long polling, its differences from classic polling, and provides a simple demo.

2 Data Interaction Modes

There are two data interaction modes: Push and Pull.

Push: the server holds a long connection and pushes data immediately when it changes. It is timely but may cause client overload.

Pull: the client periodically requests data. It avoids overload but may be less timely.

3 Long Polling vs Polling

Polling repeatedly requests data at fixed intervals, causing delay and server load.

Push delay: up to the interval.

Server pressure: frequent requests.

Trade‑off cannot be balanced.

Long polling holds the request until data changes or a timeout occurs, eliminating the above problems.

Push delay: response returned immediately after change.

Server pressure: long intervals (e.g., 30‑60 s) and low resource usage.

Example Nacos long‑polling flow:

4 Configuration Center Long‑Polling Design

Client initiates a HTTP request with dataId; the server starts async processing.

Server maintains a mapping of dataId to async contexts. If data changes, the server writes the new config and completes the async request; otherwise a 304 response is sent after a timeout.

304 means “Not Modified” and is used to indicate no configuration change.

Implementation uses Servlet 3.0 AsyncContext to avoid blocking Tomcat threads.

5 Implementation

Client

@Slf4j
public class ConfigClient {
    private CloseableHttpClient httpClient;
    private RequestConfig requestConfig;

    public ConfigClient() {
        this.httpClient = HttpClientBuilder.create().build();
        // client timeout must be larger than long‑polling timeout
        this.requestConfig = RequestConfig.custom().setSocketTimeout(40000).build();
    }

    @SneakyThrows
    public void longPolling(String url, String dataId) {
        String endpoint = url + "?dataId=" + dataId;
        HttpGet request = new HttpGet(endpoint);
        CloseableHttpResponse response = httpClient.execute(request);
        switch (response.getStatusLine().getStatusCode()) {
            case 200: {
                BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
                StringBuilder result = new StringBuilder();
                String line;
                while ((line = rd.readLine()) != null) {
                    result.append(line);
                }
                response.close();
                String configInfo = result.toString();
                log.info("dataId: [{}] changed, receive configInfo: {}", dataId, configInfo);
                longPolling(url, dataId);
                break;
            }
            case 304: {
                log.info("longPolling dataId: [{}] once finished, configInfo is unchanged, longPolling again", dataId);
                longPolling(url, dataId);
                break;
            }
            default: {
                throw new RuntimeException("unExcepted HTTP status code");
            }
        }
    }

    public static void main(String[] args) {
        // suppress httpclient debug logs
        Logger logger = (Logger) LoggerFactory.getLogger("org.apache.http");
        logger.setLevel(Level.INFO);
        logger.setAdditive(false);

        ConfigClient configClient = new ConfigClient();
        // listen to dataId:user
        configClient.longPolling("http://127.0.0.1:8080/listener", "user");
    }
}

Server

@RestController
@Slf4j
@SpringBootApplication
public class ConfigServer {

    @Data
    private static class AsyncTask {
        // async context containing request and response
        private AsyncContext asyncContext;
        // timeout flag
        private boolean timeout;
        public AsyncTask(AsyncContext asyncContext, boolean timeout) {
            this.asyncContext = asyncContext;
            this.timeout = timeout;
        }
    }

    // multimap of dataId -> async tasks
    private Multimap<String, AsyncTask> dataIdContext = Multimaps.synchronizedSetMultimap(HashMultimap.create());

    private ThreadFactory threadFactory = new ThreadFactoryBuilder()
        .setNameFormat("longPolling-timeout-checker-%d")
        .build();
    private ScheduledExecutorService timeoutChecker = new ScheduledThreadPoolExecutor(1, threadFactory);

    @RequestMapping("/listener")
    public void addListener(HttpServletRequest request, HttpServletResponse response) {
        String dataId = request.getParameter("dataId");
        AsyncContext asyncContext = request.startAsync(request, response);
        AsyncTask asyncTask = new AsyncTask(asyncContext, true);
        dataIdContext.put(dataId, asyncTask);
        timeoutChecker.schedule(() -> {
            if (asyncTask.isTimeout()) {
                dataIdContext.remove(dataId, asyncTask);
                response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                asyncContext.complete();
            }
        }, 30000, TimeUnit.MILLISECONDS);
    }

    @RequestMapping("/publishConfig")
    @SneakyThrows
    public String publishConfig(String dataId, String configInfo) {
        log.info("publish configInfo dataId: [{}], configInfo: {}", dataId, configInfo);
        Collection<AsyncTask> asyncTasks = dataIdContext.removeAll(dataId);
        for (AsyncTask asyncTask : asyncTasks) {
            asyncTask.setTimeout(false);
            HttpServletResponse resp = (HttpServletResponse) asyncTask.getAsyncContext().getResponse();
            resp.setStatus(HttpServletResponse.SC_OK);
            resp.getWriter().println(configInfo);
            asyncTask.getAsyncContext().complete();
        }
        return "success";
    }

    public static void main(String[] args) {
        SpringApplication.run(ConfigServer.class, args);
    }
}

6 Design Considerations

Why a server‑side timer returns 304: it separates client timeout from server timeout and uses normal flow instead of exceptions.

Clients must set a timeout longer than the server’s long‑polling timeout (e.g., 40 s vs 30 s).

In production, many dataIds are batched into a single long‑polling request (e.g., 3000 dataIds per request in Nacos).

7 Long Polling vs Long Connection

Although long connections seem more modern, long polling is easier to implement, works over plain HTTP, and is language‑agnostic, which explains its adoption in Nacos and Apollo.

8 Summary

The article compared polling, long polling, and long connections, explained why major configuration centers use long polling, and provided a runnable demo (available at https://github.com/lexburner/longPolling-demo).

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.

JavaBackend DevelopmentNacosApolloConfiguration Centerlong polling
Alibaba Cloud Developer
Written by

Alibaba Cloud Developer

Alibaba's official tech channel, featuring all of its technology innovations.

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.