For reference to source code analysis, please visit: https://gitee.com/lidishan/apollo-code-analysis
Statement before reading: This article does not provide relevant usage instructions, but only analyzes the Apollo source code
Apollo overall architecture diagram
What is the core function of Apollo server? Configuration change and discovery
Configuration change and discovery
Configuration change and discovery process ReleaseMessage based on a table
Implementation steps
-
After the Admin Service publishes the configuration, insert the data into the ReleaseMessage table
- Data format: appid + cluster (cluster name) + namespace (namespace)
- An example is shown in the figure below
-
The Config Service startup thread scans the ReleaseMessage table once per second
- Location: ReleaseMessageScanner#afterPropertiesSet()
-
When a new message is scanned, all listeners will be notified
- Location: e.g. NotificationControllerV2
-
For example, NotificationControllerV2 notifies the corresponding client
Analysis of source code for appeal flow chart
Regularly scan ReleaseMessage changes
- Location: ReleaseMessageScanner#afterPropertiesSet()
- Start the timed thread pool and scan the configuration changes every 1 second
- Step 1: get the current maximum ID of the ReleaseMessage table
- Step 2: execute the scheduled task, regularly scan whether there is any change, take it out in batches (500 one page) and notify
public class ReleaseMessageScanner { @Override public void afterPropertiesSet() throws Exception { // Get timing request time databaseScanInterval = bizConfig.releaseMessageScanIntervalInMilli(); // Step 1: get the current maximum ID of the ReleaseMessage table maxIdScanned = loadLargestMessageId(); // Perform scheduled tasks executorService.scheduleWithFixedDelay(() -> { Transaction transaction = Tracer.newTransaction("Apollo.ReleaseMessageScanner", "scanMessage"); try { // Step 2: execute the scheduled task, regularly scan whether there is any change, take it out in batches (500 one page) and notify scanMessages(); transaction.setStatus(Transaction.SUCCESS); } catch (Throwable ex) { transaction.setStatus(ex); logger.error("Scan and send message failed", ex); } finally { transaction.complete(); } }, databaseScanInterval, databaseScanInterval, TimeUnit.MILLISECONDS); } private void scanMessages() { boolean hasMoreMessages = true; // Pull in batches, 500 in each batch, and judge whether there is more data first while (hasMoreMessages && !Thread.currentThread().isInterrupted()) { // Scan and send messages hasMoreMessages = scanAndSendMessages(); } } /** scan messages and send */ private boolean scanAndSendMessages() { // Obtain the current batch is 500 release records after obtaining the current id List<ReleaseMessage> releaseMessages = releaseMessageRepository.findFirst500ByIdGreaterThanOrderByIdAsc(maxIdScanned); if (CollectionUtils.isEmpty(releaseMessages)) { return false; } // Notify listener fireMessageScanned(releaseMessages); // Quantity obtained int messageScanned = releaseMessages.size(); // The last id (also the largest id) maxIdScanned = releaseMessages.get(messageScanned - 1).getId(); return messageScanned == 500; } }
The above analysis shows how to scan the changed message and notify the listener. But how do you actually notify the listener? How to respond to the client?
Notify listeners and respond to clients
Following the above, firemessagescanned (release messages) can trace the notification listener logic as follows
- The fireMessageScanned(releaseMessages) call traverses listeners
And notify that NotificationControllerV2#handleMessage() has been called - Via deferredresults Get (content) gets the clients to be notified. If there are too many clients, go asynchronously. If it does not exceed the default value of batch, go to the notification,
The result of the poll will be sent back to the defaultwrapper, and the result of the poll will be notified
Note: deferredResutls is inserted into the server interface / notifications/v2/pollNotification requested by the client, and the onTimeout and onCompletion callback methods are executed,
Just wait for the handleMessage to be processed and the result will be returned (this principle requires tomcat to be an asynchronous connection processing mechanism to realize the method of long polling)
public class NotificationControllerV2 { @Override public void handleMessage(ReleaseMessage message, String channel) { String content = message.getMessage(); // ... Omit Verify legitimacy //create a new list to avoid ConcurrentModificationException List<DeferredResultWrapper> results = Lists.newArrayList(deferredResults.get(content)); ApolloConfigNotification configNotification = new ApolloConfigNotification(changedNamespace, message.getId()); configNotification.addMessage(content, message.getId()); // There are too many clients. Open a thread to do async notification if too many clients if (results.size() > bizConfig.releaseMessageNotificationBatch()) { largeNotificationBatchExecutorService.submit(() -> { for (int i = 0; i < results.size(); i++) { if (i > 0 && i % bizConfig.releaseMessageNotificationBatch() == 0) { try { TimeUnit.MILLISECONDS.sleep(bizConfig.releaseMessageNotificationBatchIntervalInMilli()); } catch (InterruptedException e) {} } results.get(i).setResult(configNotification); } }); return; } // OK, let's go for (DeferredResultWrapper result : results) { result.setResult(configNotification); } } }