Before reading this article, you should be familiar with the features of NACOS, and it is recommended to finish Spring Cloud Alibaba NACOS.
For the function, there is a purpose to find the corresponding source code, to further understand how the function is implemented.
This article for a certain source code reading experience of the crowd, will not go into too many details, but also requires readers to open source tracking, understand.
One, the introduction
Go to the corresponding page of GitHub and clone the NACOS project. Directories and files can seem tedious, but there’s not much that’s really helpful for looking at the source code.
With these three maps, we can successfully find a breakthrough, the core content is concentrated in nacos-console, nacos-naming, nacos-config, follow the path, we can see a lot of content.
If you still don’t know where to start, go to Nacos-Example, which has the call entry for the main business.
2. Configure services
First of all, from a factory class about: com. Alibaba. Nacos. API. NacosFactory.
The static method is used to create ConfigService and NamingService. The code is similar.
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 e) {
throw new NacosException(-400, e.getMessage()); }}Copy the code
There is no complicated logic and basic reflection is used. The construction parameters are passed in properties, which can be specified in bootstrap.yml, corresponding to NacosConfigProperties.
It’s the part of the constructor that initializes the namespace that needs to be looked at.
private void initNamespace(Properties properties) {
String namespaceTmp = null;
String isUseCloudNamespaceParsing =
properties.getProperty(PropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING,
System.getProperty(SystemPropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING,
String.valueOf(Constants.DEFAULT_USE_CLOUD_NAMESPACE_PARSING)));
if (Boolean.valueOf(isUseCloudNamespaceParsing)) {
namespaceTmp = TemplateUtils.stringBlankAndThenExecute(namespaceTmp, new Callable<String>() {
@Override
public String call(a) {
returnTenantUtil.getUserTenantForAcm(); }}); namespaceTmp = TemplateUtils.stringBlankAndThenExecute(namespaceTmp,new Callable<String>() {
@Override
public String call(a) {
String namespace = System.getenv(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_NAMESPACE);
returnStringUtils.isNotBlank(namespace) ? namespace : EMPTY; }}); }if (StringUtils.isBlank(namespaceTmp)) {
namespaceTmp = properties.getProperty(PropertyKeyConst.NAMESPACE);
}
namespace = StringUtils.isNotBlank(namespaceTmp) ? namespaceTmp.trim() : EMPTY;
properties.put(PropertyKeyConst.NAMESPACE, namespace);
}
Copy the code
The passed properties will specify whether the namespace parameter in the cloud environment is resolved, if so, to read the system variable of the Ali cloud environment; If not, the namespace specified in properties is read; otherwise, an empty string is parsed. From the code, retrieving the namespace of the cloud environment is made asynchronous, but the current version still uses synchronous calls.
Following ConfigService, which defines a series of interface methods, is what we want to look at.
Each business implementation ultimately boils down to Http requests, that is, configured serverAddr, and multiple addresses will be used in turn, of course, within a certain timeout period, in turn requests are unsuccessful, it will throw an exception.
The requester is nacos-client, the receiver ends up on the Nacos-config service, and the JdbcTemplate is used for data persistence.
This section of the code is easy to read. Publish configuration, get configuration, and delete configuration are all represented, so I won’t elaborate.
Focus on parsing the source code of the configuration listening part.
First focus on the com. Alibaba. Nacos. Client. Config. Impl. CacheData on this data structure, is a typical model of congestion is mainly ACTS as the role of the listener managers, it seems, the name of the class is not so friendly.
In fact, you can see that CacheData aggregates configuration information (namespace, content) and listeners together. You can think of a configuration as attaching more than one Listener to it (because the Listener interface may have multiple implementations). Only one instance of each listener is attached to the configuration.
public void addListener(Listener listener) {
if (null == listener) {
throw new IllegalArgumentException("listener is null");
}
ManagerListenerWrap wrap = new ManagerListenerWrap(listener);
if (listeners.addIfAbsent(wrap)) {
LOGGER.info("[{}] [add-listener] ok, tenant={}, dataId={}, group={}, cnt={}", name, tenant, dataId, group, listeners.size()); }}Copy the code
Use the CopyOnWriteArrayList addIfAbsent method, this method is the most important is the equals method, ManagerListenerWrap is another form of the listener’s package, it has realized the equals method:
@Override
public boolean equals(Object obj) {
if (null== obj || obj.getClass() ! = getClass()) {return false;
}
if (obj == this) {
return true;
}
ManagerListenerWrap other = (ManagerListenerWrap) obj;
return listener.equals(other.listener);
}
Copy the code
Going over again, you can find more senior management to the listener API: com. Alibaba. Nacos. Client. Config. The impl. ClientWorker.
CacheMap is a key component of listener management, defined as follows:
private final AtomicReference<Map<String, CacheData>> cacheMap = new AtomicReference<Map<String, CacheData>>()
Copy the code
An AtomicReference with its atomicoperations feature avoids concurrency inconsistencies by enclosing a HashMap, value is a CacheData object, and keys are generated by rules found in the GroupKey class:
static public String getKeyTenant(String dataId, String group, String tenant) {
StringBuilder sb = new StringBuilder();
urlEncode(dataId, sb);
sb.append('+');
urlEncode(group, sb);
if (StringUtils.isNotEmpty(tenant)) {
sb.append('+');
urlEncode(tenant, sb);
}
return sb.toString();
}
Copy the code
In fact, the configuration information is concatenated with a “+” sign. If the configuration information itself contains “+” and “%”, the urlEncode method is used to encode and escape the configuration information. Of course, there are corresponding parsing methods, which will not be explained here.
What follows is a series of GET and set operations for cacheMap to maintain the listener. In particular, each update is done as a copy object, and then the entire set is overwritten into cacheMap.
Finally, how the Listener works.
Still found in the ClientWorker, turn your attention to the constructor. Where, you can notice that two thread pools are initialized:
executor = Executors.newScheduledThreadPool(1.new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.Worker." + agent.getName());
t.setDaemon(true);
returnt; }}); executorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(),new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.Worker.longPolling." + agent.getName());
t.setDaemon(true);
returnt; }}); executor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run(a) {
try {
checkConfigInfo();
} catch (Throwable e) {
LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e); }}},1L.10L, TimeUnit.MILLISECONDS);
Copy the code
There are two ScheduledThreadpools for performing scheduled tasks, and the division of labor between the two thread pools is also nested: Executors publish configuration checking tasks, and executorService is the task receiver, the role that actually performs the tasks.
So the thread pool that publishes the task allocates only 1 core thread number, whereas the core thread of the thread pool that executes the task is the CPU core number.
Because configuration checking is a long polling process, the number of configurations that a task performer can monitor needs to be controlled, so NACOS currently uses a simpler task-splitting rule:
public void checkConfigInfo(a) {
/ / task
int listenerSize = cacheMap.get().size();
// Round up to the number of batches
int longingTaskCount = (int) Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize());
if (longingTaskCount > currentLongingTaskCount) {
for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) {
// It takes a lot of thinking to determine if the task is executing. The task list is now unordered. The process of change can be problematic
executorService.execute(newLongPollingRunnable(i)); } currentLongingTaskCount = longingTaskCount; }}Copy the code
In ParamUtil. GetPerTaskConfigSize () returns the number of each task to monitor the configuration of the ceiling, the default is 3000 article, can change the ceiling PER_TASK_CONFIG_SIZE through the system variables.
As you can see from the code, the thread pool for configuration monitoring will not work if the current number of listeners does not exceed 3000. If you take a closer look at the code in this section, you’ll see some problems, mainly a series of problems that have evolved around task management.
There are two main parts of long polling logic:
- Check the local configuration to be consistent with information stored in CacheData.
- Check the server configuration to update the information stored in CacheData.
Service registration and discovery
With that foundation in place, this part of the code should look a bit more relaxed and structurally similar.
Directly into com. Alibaba. Nacos. API. Naming. NamingService, there are multiple registerInstance reconstruction method, used in the service registry.
Let’s start with what the Instance entity class contains: Id, IP, Port, serviceName, clusterName, Weight, healthy, Enabled, Ephemeral, All nine of these attributes are available in Console.
Then, look directly at the method of registering the service:
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
if (instance.isEphemeral()) {
BeatInfo beatInfo = new BeatInfo();
beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));
beatInfo.setIp(instance.getIp());
beatInfo.setPort(instance.getPort());
beatInfo.setCluster(instance.getClusterName());
beatInfo.setWeight(instance.getWeight());
beatInfo.setMetadata(instance.getMetadata());
beatInfo.setScheduled(false);
beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
}
serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
}
Copy the code
The previous chunk of code deals with a temporary service instance, constructing a heartbeat packet to send to the NACOS service.
The registerService method simply wraps the HTTP request and ultimately handles the request in InstanceController.
If the project integrates spring-cloud-starter-Alibaba-nacos-Discovery, the service will be automatically registered after startup by default. If you want to see the process of automatic registration, can from AbstractAutoServiceRegistration start, there is a piece of code:
@EventListener(WebServerInitializedEvent.class)
public void bind(WebServerInitializedEvent event) {
ApplicationContext context = event.getApplicationContext();
if (context instanceof ConfigurableWebServerApplicationContext) {
if ("management".equals(
((ConfigurableWebServerApplicationContext) context).getServerNamespace())) {
return; }}this.port.compareAndSet(0, event.getWebServer().getPort());
this.start();
}
Copy the code
After listening for the event that the Web service is initialized, the start method is executed:
public void start(a) {
if(! isEnabled()) {if (logger.isDebugEnabled()) {
logger.debug("Discovery Lifecycle disabled. Not starting");
}
return;
}
// only initialize if nonSecurePort is greater than 0 and it isn't already running
// because of containerPortInitializer below
if (!this.running.get()) {
register();
if (shouldRegisterManagement()) {
registerManagement();
}
this.context.publishEvent(new InstanceRegisteredEvent<>(this, getConfiguration()));
this.running.compareAndSet(false.true); }}Copy the code
The register method is the core part of this implementation, which comes from the implementation of NacosServiceRegistry:
@Override
public void register(NacosRegistration registration) {
if(! registration.isRegisterEnabled()) { logger.info("Nacos Registration is disabled...");
return;
}
if (StringUtils.isEmpty(registration.getServiceId())) {
logger.info("No service to register for nacos client...");
return;
}
NamingService namingService = registration.getNacosNamingService();
String serviceId = registration.getServiceId();
Instance instance = new Instance();
instance.setIp(registration.getHost());
instance.setPort(registration.getPort());
instance.setWeight(registration.getRegisterWeight());
instance.setClusterName(registration.getCluster());
instance.setMetadata(registration.getMetadata());
try {
namingService.registerInstance(serviceId, instance);
logger.info("nacos registry, {} {}:{} register finished", serviceId, instance.getIp(), instance.getPort());
}catch (Exception e) {
logger.error("nacos registry, {} register failed... {},", serviceId, registration.toString(), e); }}Copy the code
This code is very familiar with, eventually returned to the namingService. RegisterInstance method.
/** * Map
> */
,>
private Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();
Copy the code
Above appeared another entity classes: com. Alibaba. Nacos. Naming. Core. The Service, the Service is contains the Instance, a Service have more than one Instance, can form a Cluster.
When registerInstance is invoked to register an instance, if the corresponding Service is not registered, registerService will be registered and the corresponding Cluster will be initialized to start the health check timer.
The opposite of registerInstance is deregisterInstance, which is either unregistered or taken offline.
Finally, let’s look at how NACOS implements service discovery.
From the perspective of the consumer (caller), the integrated Starter project has a class called NacosServerList that, most importantly, inherits AbstractServerList and implements two key interface methods that act as a interface interface between NACOS and the Ribbon.
public interface ServerList<T extends Server> {
public List<T> getInitialListOfServers(a);
/** * Return updated list of servers. This is called say every 30 secs * (configurable) by the Loadbalancer's Ping cycle * * /
public List<T> getUpdatedListOfServers(a);
}
Copy the code
NACOS for the realization of the two interfaces, use getServers method, and enter into the method getServers body, is actually use the above said NacosNamingService. SelectInstances method, Get the ServiceInfo object from serviceId, and then get all valid instances under Service.
From the provider’s (called) perspective, NACOS updates ServiceInfo in real time with timers, and the main business logic is implemented in HostReactor. Unlike the serviceMap, serviceInfoMap is maintained in HostReactor.
private Map<String, ServiceInfo> serviceInfoMap; The HostReactor uses the FailoverReactor to cache the ServiceInfo disk, still starts the scheduled task, and serializes ServiceInfo in the specified directory to implement the Failover mechanism. Failover mode is also enabled as part of a specific file, and is monitored by a scheduled task.
File switchFile = new File(failoverDir + UtilAndComs.FAILOVER_SWITCH);
Copy the code
The whole process is shown below:
Iv. Management Console
This section is an implementation of the administrative console, which is actually a very typical WEB project.
Spring Security + JWT is used for Security control, and the front-end technology is ReactJs, and JdbcTemplate is used for database persistence.
Note that the functionality provided by the console is not all data fetched from the nacos-Console service, but is scattered across services.
Nacos-console provides console login, namespace management, and console service status, while configuration management and service management request the apis provided by Nacos-Config and Nacos-Naming respectively, and these apis are the open-API mentioned in the official website.
Five, the summary
NACOS related source code is easy to understand, no advanced concepts, no layers of packaging and wrapping, programmers with some programming experience can grasp the context of the entire project in half an hour.
Of course, there are some serious drawbacks, such as too few comments, lots of room for refactoring, and confusion between tenant and namespace.
This concludes the introduction to Spring Cloud Alibaba Nacos, which I hope will be helpful to you.
Scan the qr code below, enter the original dry goods, engage in “technology” holy land.