Background:
Today's network services are mostly distributed cluster deployment, and the adopted architecture is also service-oriented architecture (SOA). Everyone is striving in the field of service governance. No, today's Nacos is also a product of the field of service governance. Nacos is an excellent registration and configuration center. Recommended by Nacos, borrow a picture: https://www.jianshu.com/p/39ade28c150d
Introduction:
This article mainly introduces the clients of Nacos. How can Nacos get the configuration of the server and update it in real time.
Example:
@Test public void testNacos() throws NacosException { String serverAddr = "10.199.150.216:8848"; String namespace = "8ba3b4f6-1c6a-4c7d-99b0-f444002d526d"; String dataId = "cxtj.properties"; String group = "cxtj"; Properties properties = new Properties(); properties.put("serverAddr",serverAddr); properties.put("namespace",namespace); //1. Create Nacos configuration service ConfigService configService = NacosFactory.createConfigService(properties); //2. Get configuration information from service String content = configService.getConfig(dataId,group,3000); System.out.println("receive" + content); }
Print out profile information:
Start reading the source code:
//1. Create Nacos configuration service ConfigService configService = NacosFactory.createConfigService(properties); //2. Get configuration information from service String content = configService.getConfig(dataId,group,3000);
First, create the configuration service through the previous configuration properties:
public static ConfigService createConfigService(Properties properties) throws NacosException { Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.config.NacosConfigService"); Constructor constructor = driverImplClass.getConstructor(Properties.class); ConfigService vendorImpl = (ConfigService) constructor.newInstance(properties); return vendorImpl; }
It can be seen that the object is instantiated through reflection by obtaining the construction method of the NacosConfigService class. (question 1: is it not good to directly new an object? Finally)
Take a look at the construction method:
public NacosConfigService(Properties properties) throws NacosException { //Initialize encoding format String encodeTmp = properties.getProperty(PropertyKeyConst.ENCODE); if (StringUtils.isBlank(encodeTmp)) { encode = Constants.ENCODE; } else { encode = encodeTmp.trim(); } //Initialize namespace initNamespace(properties); //Create a proxy for the server agent = new MetricsHttpAgent(new ServerHttpAgent(properties)); //Start agent agent.start(); //Create client working worker = new ClientWorker(agent, configFilterChainManager, properties); }
The previous coding and namespace initialization are skipped. MetricsHttpAgent is based on ServerHttpAgent, which adds monitoring function and records the requested time and other information. It's mainly ServerHttpAgent. Let's see the construction method of ServerHttpAgent.
public ServerHttpAgent(Properties properties) throws NacosException { //Create service list manager serverListMgr = new ServerListManager(properties); //Initialization init(properties); }
The service list in ServerListManager generally maintains these two concepts, one is isStarted (whether the service is started), the other is isFixed (whether the service address is fixed, in the example, String serverAddr = "10.199.150.216:8848"; that is, fixed address). The service list ServerListManager holds some basic information of the server, such as service address, server name endpoint, etc.
public ServerListManager(Properties properties) throws NacosException { isStarted = false; serverAddrsStr = properties.getProperty(PropertyKeyConst.SERVER_ADDR); String namespace = properties.getProperty(PropertyKeyConst.NAMESPACE); //Initialize endpoint, contextPath, clusterName initParam(properties); //If serverAddrsStr has a value, the server address is fixed if (StringUtils.isNotEmpty(serverAddrsStr)) { isFixed = true; List<String> serverAddrs = new ArrayList<String>(); //Get service address String[] serverAddrsArr = serverAddrsStr.split(","); for (String serverAddr: serverAddrsArr) { if (serverAddr.startsWith(HTTPS) || serverAddr.startsWith(HTTP)) { serverAddrs.add(serverAddr); } else { String[] serverAddrArr = serverAddr.split(":"); if (serverAddrArr.length == 1) { serverAddrs.add(HTTP + serverAddrArr[0] + ":" + ParamUtil.getDefaultServerPort()); } else { serverAddrs.add(HTTP + serverAddr); } } } serverUrls = serverAddrs; //Namespace selection if (StringUtils.isBlank(namespace)) { name = FIXED_NAME + "-" + getFixedNameSuffix(serverUrls.toArray(new String[serverUrls.size()])); } else { this.namespace = namespace; this.tenant = namespace; name = FIXED_NAME + "-" + getFixedNameSuffix(serverUrls.toArray(new String[serverUrls.size()])) + "-" + namespace; } //If it's an endpoint connection } else { if (StringUtils.isBlank(endpoint)) { throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "endpoint is blank"); } isFixed = false; if (StringUtils.isBlank(namespace)) { name = endpoint; addressServerUrl = String.format("http://%s:%d/%s/%s", endpoint, endpointPort, contentPath, serverListName); } else { this.namespace = namespace; this.tenant = namespace; name = endpoint + "-" + namespace; addressServerUrl = String.format("http://%s:%d/%s/%s?namespace=%s", endpoint, endpointPort, contentPath, serverListName, namespace); } } }
Initialization method:
private void init(Properties properties) { //Initialize encoding format, default utf-8 initEncode(properties); //Initialize accessKey and secretKey for asymmetric encryption initAkSk(properties); //Initialize the maximum number of retries, the default is 3 initMaxRetry(properties); }
Knowledge of AK/SK, refer to https://blog.csdn.net/makenothing/article/details/81158481
When the agent creation is complete, start the agent. The role of this agent is to dynamically obtain the address list of the server.
//Start agent agent.start();
The start of the agent executes the start method of serverListMgr, which is the serverListManager in the agent we created earlier
@Override public synchronized void start() throws NacosException { serverListMgr.start(); }
In the example, if the server address is fixed, start will return directly. There is no need to obtain the server address dynamically, not for now.
public synchronized void start() throws NacosException { //If the serverAddr in the example is fixed, return directly if (isStarted || isFixed) { return; } //The operation here is to dynamically obtain the server address GetServerListTask getServersTask = new GetServerListTask(addressServerUrl); for (int i = 0; i < initServerlistRetryTimes && serverUrls.isEmpty(); ++i) { getServersTask.run(); try { this.wait((i + 1) * 100L); } catch (Exception e) { LOGGER.warn("get serverlist fail,url: {}", addressServerUrl); } } TimerService.scheduleWithFixedDelay(getServersTask, 0L, 30L, TimeUnit.SECONDS); isStarted = true; }
Next is ClientWorker, whose main task is to configure the real-time synchronization server.
//The client gets the configuration of the server in real time worker = new ClientWorker(agent, configFilterChainManager, properties);
public ClientWorker(final HttpAgent agent, final ConfigFilterChainManager configFilterChainManager, final Properties properties) { //Assign a value to the agent and configured filter of the server list this.agent = agent; this.configFilterChainManager = configFilterChainManager; // Initialize the timeout parameter init(properties); //Create a pool of daemons, a thread, to start (daemons are threads that live and die with user threads) 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); return t; } }); //Create another daemons pool with the number of currently available threads to synchronize the configuration information on the server 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); return t; } }); //Start the thread once every 10ms executor.scheduleWithFixedDelay(new Runnable() { @Override public void run() { try { //Check configuration information checkConfigInfo(); } catch (Throwable e) { LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e); } } }, 1L, 10L, TimeUnit.MILLISECONDS); }
Let's look at the checkConfigInfo method:
public void checkConfigInfo() { // The cache map stores all configuration information and is a thread safe AtomicReference. int listenerSize = cacheMap.get().size(); // Every 3000 monitors are in one batch, and if less than 3000, the number of batches is rounded up int longingTaskCount = (int) Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize()); if (longingTaskCount > currentLongingTaskCount) { for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) { //Execution synchronization executorService.execute(new LongPollingRunnable(i)); } currentLongingTaskCount = longingTaskCount; } }
Update the configuration information cacheData in the cache in batches, and start the LongPollingRunnable thread. There is such a comment in the source code that "to judge whether the task is executing or not, it needs to be considered carefully. The task list is now unordered. There may be problems in the process of change ", because it is necessary to monitor this thread, which cannot be stopped, but at present, it is not possible to monitor these threads.
Let's take a look at how the thread LongPollingRunnable is configured to listen to the server.
class LongPollingRunnable implements Runnable { private int taskId; public LongPollingRunnable(int taskId) { this.taskId = taskId; } @Override public void run() { List<CacheData> cacheDatas = new ArrayList<CacheData>(); List<String> inInitializingCacheList = new ArrayList<String>(); try { // check failover config for (CacheData cacheData : cacheMap.get().values()) { if (cacheData.getTaskId() == taskId) { cacheDatas.add(cacheData); try { //Check the local configuration. nacos will create a local copy of the configuration to enhance the disaster tolerance of the system checkLocalConfig(cacheData); if (cacheData.isUseLocalConfigInfo()) { cacheData.checkListenerMd5(); } } catch (Exception e) { LOGGER.error("get local config info error", e); } } } //Check the configuration information of the server and return the groupId and dataId of the changed configuration List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList); //Get the latest configuration from the server and update the changed configuration for (String groupKey : changedGroupKeys) { String[] key = GroupKey.parseKey(groupKey); String dataId = key[0]; String group = key[1]; String tenant = null; if (key.length == 3) { tenant = key[2]; } try { String content = getServerConfig(dataId, group, tenant, 3000L); CacheData cache = cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant)); cache.setContent(content); LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, content={}", agent.getName(), dataId, group, tenant, cache.getMd5(), ContentUtils.truncateContent(content)); } catch (NacosException ioe) { String message = String.format( "[%s] [get-update] get changed config exception. dataId=%s, group=%s, tenant=%s", agent.getName(), dataId, group, tenant); LOGGER.error(message, ioe); } } //New configuration appears in synchronization, and the listener needs to be updated at the same time for (CacheData cacheData : cacheDatas) { if (!cacheData.isInitializing() || inInitializingCacheList .contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant))) { cacheData.checkListenerMd5(); cacheData.setInitializing(false); } } inInitializingCacheList.clear(); //After synchronization, the thread is used to keep 3000 configurations consistent with the server executorService.execute(this); } catch (Throwable e) { // If the rotation training task is abnormal, the next execution time of the task will be punished LOGGER.error("longPolling error : ", e); executorService.schedule(this, taskPenaltyTime, TimeUnit.MILLISECONDS); } } }
nacos will make a local configuration copy, which can greatly enhance the disaster tolerance of the system. When the nacos service goes down, you can also use the local configuration to run.
In the long polling runnable thread, the heartbeat information of the client and the server is also maintained to identify the current health of the server. Specifically, get the groupkey of the server change:
/** * Get a list of dataids with changed values from the Server. Only dataid and group are valid in the returned object. Ensure that NULL is not returned. */ List<String> checkUpdateDataIds(List<CacheData> cacheDatas, List<String> inInitializingCacheList) throws IOException { StringBuilder sb = new StringBuilder(); for (CacheData cacheData : cacheDatas) { if (!cacheData.isUseLocalConfigInfo()) { sb.append(cacheData.dataId).append(WORD_SEPARATOR); sb.append(cacheData.group).append(WORD_SEPARATOR); if (StringUtils.isBlank(cacheData.tenant)) { sb.append(cacheData.getMd5()).append(LINE_SEPARATOR); } else { sb.append(cacheData.getMd5()).append(WORD_SEPARATOR); sb.append(cacheData.getTenant()).append(LINE_SEPARATOR); } if (cacheData.isInitializing()) { // cacheData appears in cacheMap for the first time & the first check update inInitializingCacheList .add(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant)); } } } boolean isInitializingCacheList = !inInitializingCacheList.isEmpty(); return checkUpdateConfigStr(sb.toString(), isInitializingCacheList); }
Enter the checkupdateconfig STR method:
List<String> checkUpdateConfigStr(String probeUpdateString, boolean isInitializingCacheList) throws IOException { List<String> params = Arrays.asList(Constants.PROBE_MODIFY_REQUEST, probeUpdateString); List<String> headers = new ArrayList<String>(2); headers.add("Long-Pulling-Timeout"); headers.add("" + timeout); // told server do not hang me up if new initializing cacheData added in if (isInitializingCacheList) { headers.add("Long-Pulling-Timeout-No-Hangup"); headers.add("true"); } if (StringUtils.isBlank(probeUpdateString)) { return Collections.emptyList(); } try { HttpResult result = agent.httpPost(Constants.CONFIG_CONTROLLER_PATH + "/listener", headers, params, agent.getEncode(), timeout); //Call the server's listener interface, and identify the health status of the server according to the returned http status code if (HttpURLConnection.HTTP_OK == result.code) { setHealthServer(true); return parseUpdateDataIdResponse(result.content); } else { setHealthServer(false); LOGGER.error("[{}] [check-update] get changed dataId error, code: {}", agent.getName(), result.code); } } catch (IOException e) { setHealthServer(false); LOGGER.error("[" + agent.getName() + "] [check-update] get changed dataId exception", e); throw e; } return Collections.emptyList(); }
Say what I said before, question 1: is it not good to directly new an object? And use reflection
1. new belongs to static compilation. At the time of compilation, the object to be instantiated has been determined, while the reflection is uncertain
2. Reflection belongs to dynamic compilation, which means that only at run time can we get the instance of the object. Most frameworks use reflection to get the instance of the object, such as Spring
Let 's talk about this for now