Deep Dive into Nacos Long‑Polling Mechanism: Client and Server Implementation Details
This article provides a comprehensive analysis of Nacos's long‑polling mechanism, walking through the client‑side ConfigService instantiation, scheduled thread pools, request handling, and the server‑side listener workflow, while highlighting key code snippets and the event‑driven architecture that enables real‑time configuration updates.
For clarity, the Nacos console and registration center are referred to as the Nacos server (the web UI), while the business services we develop are called Nacos clients.
The source‑code analysis is split into two parts; this part focuses on the long‑polling timing mechanism on both client and server sides.
1. Client Long‑Polling Timing Mechanism
Start from the previous article’s NacosPropertySourceLocator.locate() call.
The NacosFactory.createConfigService() method creates a ConfigService instance, which triggers the long‑polling task.
public static ConfigService createConfigService(Properties properties) throws NacosException {
//【Breakpoint】Create ConfigService
return ConfigFactory.createConfigService(properties);
}Inside ConfigFactory.createConfigService(), reflection is used to instantiate NacosConfigService:
public static ConfigService createConfigService(Properties properties) throws NacosException {
try {
Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.config.NacosConfigService");
Constructor constructor = driverImplClass.getConstructor(Properties.class);
ConfigService vendorImpl = (ConfigService) constructor.newInstance(properties);
return vendorImpl;
} catch (Throwable var4) {
throw new NacosException(-400, var4);
}
}The NacosConfigService constructor launches the long‑polling task:
public NacosConfigService(Properties properties) throws NacosException {
// Initialize namespace, HttpAgent, and ClientWorker
this.agent = new MetricsHttpAgent(new ServerHttpAgent(properties));
this.agent.start();
this.worker = new ClientWorker(this.agent, this.configFilterChainManager, properties);
} ClientWorkercreates two scheduled thread pools and starts a periodic task that checks configuration every 10 seconds:
this.executor = Executors.newScheduledThreadPool(1, r -> {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.Worker." + agent.getName());
t.setDaemon(true);
return t;
});
this.executorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), r -> {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.Worker.longPolling." + agent.getName());
t.setDaemon(true);
return t;
});
this.executor.scheduleWithFixedDelay(() -> {
try {
ClientWorker.this.checkConfigInfo();
} catch (Throwable var2) {
ClientWorker.LOGGER.error("[" + agent.getName() + "][sub-check] rotate check error", var2);
}
}, 1L, 10L, TimeUnit.MILLISECONDS);The checkConfigInfo() method splits the listener set into tasks, creates LongPollingRunnable threads when the listener count exceeds 3000, and invokes checkUpdateDataIds() to query the server for changes.
public void checkConfigInfo() {
int listenerSize = ((Map) this.cacheMap.get()).size();
int longingTaskCount = (int) Math.ceil((double) listenerSize / ParamUtil.getPerTaskConfigSize());
if ((double) longingTaskCount > this.currentLongingTaskCount) {
for (int i = (int) this.currentLongingTaskCount; i < longingTaskCount; ++i) {
this.executorService.execute(new ClientWorker.LongPollingRunnable(i));
}
this.currentLongingTaskCount = (double) longingTaskCount;
}
}When a change is detected, LongPollingRunnable.run() reads the updated configuration via ClientWorker.getServerConfig(), which ultimately calls the server’s /v1/cs/configs endpoint.
public String getServerConfig(String dataId, String group, String tenant, long readTimeout) throws NacosException {
if (StringUtils.isBlank(group)) {
group = "DEFAULT_GROUP";
}
HttpResult result = this.agent.httpGet("/v1/cs/configs", null, params, this.agent.getEncode(), readTimeout);
switch (result.code) {
case 200:
LocalConfigInfoProcessor.saveSnapshot(this.agent.getName(), dataId, group, tenant, result.content);
return result.content;
// other status handling omitted for brevity
}
}2. Server Long‑Polling Timing Mechanism
The server receives the client request at ConfigController.listener(), which delegates to LongPollingService.addLongPollingClient() to set up the asynchronous context.
@PostMapping("/listener")
public void listener(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
String probeModify = request.getParameter("Listening-Configs");
// MD5 calculation and async handling omitted for brevity
inner.doPollingConfig(request, response, clientMd5Map, probeModify.length());
} LongPollingService.addLongPollingClient()extracts timeout headers, creates an AsyncContext, and schedules a ClientLongPolling task:
final AsyncContext asyncContext = req.startAsync();
asyncContext.setTimeout(0L);
ConfigExecutor.executeLongPolling(new ClientLongPolling(asyncContext, clientMd5Map, ip, probeRequestSize, timeout, appName, tag));The ClientLongPolling.run() method schedules a delayed task (≈29.5 s). When the timeout expires, it checks for configuration changes; if none, it returns an empty response, otherwise it sends the changed group keys.
asyncTimeoutFuture = ConfigExecutor.scheduleLongPolling(() -> {
// Remove from subscription list, compare MD5, send response
if (changedGroups.size() > 0) {
sendResponse(changedGroups);
} else {
sendResponse(null);
}
}, timeoutTime, TimeUnit.MILLISECONDS);Server‑side configuration changes fire a LocalDataChangeEvent, which is listened to by LongPollingService (pre‑1.3.1) or a registered Subscriber (post‑1.3.2). The listener creates a DataChangeTask that iterates over pending ClientLongPolling instances and immediately notifies matching clients.
public void onEvent(Event event) {
if (event instanceof LocalDataChangeEvent) {
LocalDataChangeEvent evt = (LocalDataChangeEvent) event;
ConfigExecutor.executeLongPolling(new DataChangeTask(evt.groupKey, evt.isBeta, evt.betaIps));
}
}3. Summary of the Source Structure
The client side builds the ConfigService via reflection, creates scheduled executors, periodically checks for updates, and fetches changed configurations through HTTP calls. The server side exposes a /listener endpoint, registers long‑polling clients, schedules timeout tasks, compares MD5 hashes, and pushes change notifications via asynchronous responses.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
