This is an automated email from the ASF dual-hosted git repository.

hefengen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/shenyu-website.git


The following commit(s) were added to refs/heads/main by this push:
     new d07beb112a Add apollo data sync blog (#1010)
d07beb112a is described below

commit d07beb112a578b29ff13c5106ef5eb592d9e6e3e
Author: hql0312 <[email protected]>
AuthorDate: Tue Mar 12 23:38:07 2024 +0800

    Add apollo data sync blog (#1010)
    
    * [DOC] add apollo data sync blog
    
    * [DOC] modify doc info
    
    * [DOC] modify PR info
    
    * [refactor] fix checkstyle
---
 ...ataSync-SourceCode-Analysis-Apollo-Data-Sync.md | 558 ++++++++++++++++++++
 i18n/zh/code.json                                  |   6 +
 ...ataSync-SourceCode-Analysis-Apollo-Data-Sync.md | 562 +++++++++++++++++++++
 src/data/blogInfo.js                               |  10 +
 .../code-analysis-apollo-data-sync/Apollo-Sync.png | Bin 0 -> 36811 bytes
 5 files changed, 1136 insertions(+)

diff --git a/blog/DataSync-SourceCode-Analysis-Apollo-Data-Sync.md 
b/blog/DataSync-SourceCode-Analysis-Apollo-Data-Sync.md
new file mode 100644
index 0000000000..6fdcc8e7b5
--- /dev/null
+++ b/blog/DataSync-SourceCode-Analysis-Apollo-Data-Sync.md
@@ -0,0 +1,558 @@
+---
+title: Apollo Data Synchronization Source Code Analysis
+author: hql0312
+author_title: Apache ShenYu Contributor
+author_url: https://github.com/hql0312
+tags: [apollo,data sync,Apache ShenYu]
+---
+
+> This article is based on the source code analysis of version 'shenyu-2.6.1'. 
Please refer to the official website for an introduction [Data Synchronization 
Design](https://shenyu.apache.org/docs/design/data-sync/).
+
+### Admin management
+
+Understand the overall process through the process of adding plugins
+
+![](/img/activities/code-analysis-apollo-data-sync/Apollo-Sync.png)
+
+### Receive Data
+
+- PluginController.createPlugin()
+
+Enter the `createPlugin()` method in the `PluginController` class, which is 
responsible for data validation, adding or updating data, and returning result 
information.
+
+```java
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/plugin")
+public class PluginController {
+
+  @PostMapping("")
+  @RequiresPermissions("system:plugin:add")
+  public ShenyuAdminResult createPlugin(@Valid @ModelAttribute final PluginDTO 
pluginDTO) {
+      // Call pluginService.createOrUpdate for processing logic
+      return 
ShenyuAdminResult.success(pluginService.createOrUpdate(pluginDTO));
+  }
+    
+    // ......
+}
+```
+
+### Processing data
+
+- PluginServiceImpl.createOrUpdate() -> PluginServiceImpl.create()
+
+Use the `create()` method in the `PluginServiceImpl` class to convert data, 
save it to the database, and publish events.
+
+```java
+@RequiredArgsConstructor
+@Service
+public class PluginServiceImpl implements SelectorService {
+    // Event publishing object pluginEventPublisher
+    private final PluginEventPublisher pluginEventPublisher;
+
+   private String create(final PluginDTO pluginDTO) {
+      // Check if there is a corresponding plugin
+      Assert.isNull(pluginMapper.nameExisted(pluginDTO.getName()), 
AdminConstants.PLUGIN_NAME_IS_EXIST);
+      // check if Customized plugin jar
+      if (!Objects.isNull(pluginDTO.getFile())) {
+        Assert.isTrue(checkFile(Base64.decode(pluginDTO.getFile())), 
AdminConstants.THE_PLUGIN_JAR_FILE_IS_NOT_CORRECT_OR_EXCEEDS_16_MB);
+      }
+      // Create plugin object
+      PluginDO pluginDO = PluginDO.buildPluginDO(pluginDTO);
+      // Insert object into database
+      if (pluginMapper.insertSelective(pluginDO) > 0) {
+        // publish create event. init plugin data
+        pluginEventPublisher.onCreated(pluginDO);
+      }
+      return ShenyuResultMessage.CREATE_SUCCESS;
+  }
+    
+    
+    // ......
+    
+}
+
+```
+
+Complete the data persistence operation in the `PluginServiceImpl` class, that 
is, save the data to the database and publish events through 
`pluginEventPublisher`.
+
+The logic of the `pluginEventPublisher.onCreated` method is to publish the 
changed event:
+
+```java
+    @Override
+public void onCreated(final PluginDO plugin) {
+        // Publish DataChangeEvent events: event grouping (plugins, selectors, 
rules), event types (create, delete, update), changed data
+        publisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.PLUGIN, 
DataEventTypeEnum.CREATE,
+        Collections.singletonList(PluginTransfer.INSTANCE.mapToData(plugin))));
+        // Publish PluginCreatedEvent
+        publish(new PluginCreatedEvent(plugin, SessionUtil.visitorName()));
+}
+```
+
+Publishing change data is completed through `publisher.publishEvent()`, which 
is an 'Application EventPublisher' object with the fully qualified name of 
'org. springframework. contentxt.' Application EventPublisher `. From here, we 
know that publishing data is accomplished through the Spring related features.
+
+> About `ApplicationEventPublisher`:
+>
+> When there is a state change, the publisher calls the `publishEvent` method 
of `ApplicationEventPublisher` to publish an event, the `Spring` container 
broadcasts the event to all observers, and calls the observer's 
`onApplicationEvent` method to pass the event object to the observer. There are 
two ways to call the `publishEvent` method. One is to implement the interface, 
inject the `ApplicationEventPublisher` object into the container, and then call 
its method. The other is to call the [...]
+>
+> - `ApplicationEventPublisher`:Publish events;
+> - `ApplicationEvent`:`Spring` events,Record the source, time, and data of 
the event;
+> - `ApplicationListener`:Event listeners, observers;
+
+
+In the event publishing mechanism of Spring, there are three objects,
+
+One is the `ApplicationEventPublisher` that publishes events, injecting an 
`publisher` through a constructor in `ShenYu`.
+
+The other object is `ApplicationEvent`, which is inherited from `ShenYu` 
through `DataChangedEvent`, representing the event object
+
+```java
+public class DataChangedEvent extends ApplicationEvent {
+//......
+}
+```
+
+The last one is `ApplicationListener`, which is implemented in `ShenYu` 
through the `DataChangedEventDispatcher` class as a listener for events, 
responsible for handling event objects.
+
+```java
+@Component
+public class DataChangedEventDispatcher implements 
ApplicationListener<DataChangedEvent>, InitializingBean {
+
+    //......
+    
+}
+```
+
+### Distribute data
+
+- DataChangedEventDispatcher.onApplicationEvent()
+
+After the event is published, it will automatically enter the 
`onApplicationEvent()` method in the `DataChangedEventDispatcher` class for 
event processing.
+
+```java
+@Component
+public class DataChangedEventDispatcher implements 
ApplicationListener<DataChangedEvent>, InitializingBean {
+
+  /**
+     * When there is a data change, call this method
+     * @param event
+     */
+  @Override
+  @SuppressWarnings("unchecked")
+  public void onApplicationEvent(final DataChangedEvent event) {
+    // Traverse data change listeners (only ApolloDataChangedListener will be 
registered here)
+    for (DataChangedListener listener : listeners) {
+      // Forward according to different grouping types
+      switch (event.getGroupKey()) {
+        case APP_AUTH: // authentication information
+          listener.onAppAuthChanged((List<AppAuthData>) event.getSource(), 
event.getEventType());
+          break;
+        case PLUGIN: // Plugin events
+          // Calling the registered listener object
+          listener.onPluginChanged((List<PluginData>) event.getSource(), 
event.getEventType());
+          break;
+        case RULE: // Rule events
+          listener.onRuleChanged((List<RuleData>) event.getSource(), 
event.getEventType());
+          break;
+        case SELECTOR: // Selector event
+          listener.onSelectorChanged((List<SelectorData>) event.getSource(), 
event.getEventType());
+          break;
+        case META_DATA: // Metadata events
+          listener.onMetaDataChanged((List<MetaData>) event.getSource(), 
event.getEventType());
+          break;
+        case PROXY_SELECTOR: // Proxy selector event
+          listener.onProxySelectorChanged((List<ProxySelectorData>) 
event.getSource(), event.getEventType());
+          break;
+        case DISCOVER_UPSTREAM: // Registration discovery of downstream list 
events
+          listener.onDiscoveryUpstreamChanged((List<DiscoverySyncData>) 
event.getSource(), event.getEventType());
+          
applicationContext.getBean(LoadServiceDocEntry.class).loadDocOnUpstreamChanged((List<DiscoverySyncData>)
 event.getSource(), event.getEventType());
+          break;
+        default:
+          throw new IllegalStateException("Unexpected value: " + 
event.getGroupKey());
+      }
+    }
+  }
+    
+}
+```
+
+When there is a data change, call the `onApplicationEvent` method, then 
traverse all data change listeners, determine which data type it is, and hand 
it over to the corresponding data listeners for processing.
+
+`ShenYu` has grouped all data into the following types: authentication 
information, plugin information, rule information, selector information, 
metadata, proxy selector, and downstream event discovery.
+
+The Data Change Listener here is an abstraction of the data synchronization 
strategy, processed by specific implementations, and different listeners are 
processed by different implementations. Currently, Apollo is being analyzed
+Listening, so here we only focus on `ApolloDataChangedListener`.
+
+```java
+// Inheriting AbstractNodeDataChangedListener
+public class ApolloDataChangedListener extends AbstractNodeDataChangedListener 
{
+    
+}
+```
+
+`ApolloDataChangedListener` inherits the `AbstractNodeDataChangedListener` 
class, which mainly uses key as the base class for storage, such as Apollo, 
Nacos, etc., while others such as Zookeeper
+Consul, etc. are searched in a hierarchical manner using a path.
+
+```java
+// Using key as the base class for finding storage methods
+public abstract class AbstractNodeDataChangedListener implements 
DataChangedListener { 
+    
+    protected AbstractNodeDataChangedListener(final ChangeData changeData) {
+      this.changeData = changeData;
+    }
+}
+```
+
+`AbstractNodeDataChangedListener` receives ChangeData as a parameter, which 
defines the key names for each data stored in Apollo. The data stored in Apollo 
includes the following data:
+- Plugin(plugin)
+- Selector(selector)
+- Rules(rule)
+- Authorization(auth)
+- Metadata(meta)
+- Proxy selector(proxy.selector)
+- Downstream List (discovery)
+
+These information are specified by the ApolloDataChangedListener constructor:
+
+```java
+public class ApolloDataChangedListener extends AbstractNodeDataChangedListener 
{
+  public ApolloDataChangedListener(final ApolloClient apolloClient) {
+    // Configure prefixes for several types of grouped data
+    super(new ChangeData(ApolloPathConstants.PLUGIN_DATA_ID,
+            ApolloPathConstants.SELECTOR_DATA_ID,
+            ApolloPathConstants.RULE_DATA_ID,
+            ApolloPathConstants.AUTH_DATA_ID,
+            ApolloPathConstants.META_DATA_ID,
+            ApolloPathConstants.PROXY_SELECTOR_DATA_ID,
+            ApolloPathConstants.DISCOVERY_DATA_ID));
+    // Manipulating objects of Apollo
+    this.apolloClient = apolloClient;
+  }
+}
+```
+
+`DataChangedListener` defines the following methods:
+
+```java
+// Data Change Listener
+public interface DataChangedListener {
+
+    // Call when authorization information changes
+    default void onAppAuthChanged(List<AppAuthData> changed, DataEventTypeEnum 
eventType) {
+    }
+
+    // Called when plugin information changes
+    default void onPluginChanged(List<PluginData> changed, DataEventTypeEnum 
eventType) {
+    }
+
+    // Called when selector information changes
+    default void onSelectorChanged(List<SelectorData> changed, 
DataEventTypeEnum eventType) {
+    }
+    
+     // Called when metadata information changes
+    default void onMetaDataChanged(List<MetaData> changed, DataEventTypeEnum 
eventType) {
+
+    }
+
+    // Call when rule information changes
+    default void onRuleChanged(List<RuleData> changed, DataEventTypeEnum 
eventType) {
+    }
+
+    // Called when proxy selector changes
+    default void onProxySelectorChanged(List<ProxySelectorData> changed, 
DataEventTypeEnum eventType) {
+    }
+    // Called when downstream information changes are discovered
+    default void onDiscoveryUpstreamChanged(List<DiscoverySyncData> changed, 
DataEventTypeEnum eventType) {
+    }
+
+}
+```
+
+When the plugin is processed by `DataChangedEventDispatcher`, the method 
`listener.onPluginChanged` is called. Next, analyze the logic of the object and 
implement the processing by `AbstractNodeDataChangedListener`:
+
+```java
+public abstract class AbstractNodeDataChangedListener implements 
DataChangedListener {
+  @Override
+  public void onPluginChanged(final List<PluginData> changed, final 
DataEventTypeEnum eventType) {
+    //Configure prefix as plugin.
+    final String configKeyPrefix = changeData.getPluginDataId() + 
DefaultNodeConstants.JOIN_POINT;
+    this.onCommonChanged(configKeyPrefix, changed, eventType, 
PluginData::getName, PluginData.class);
+    LOG.debug("[DataChangedListener] PluginChanged {}", configKeyPrefix);
+  }
+}
+```
+
+Firstly, the key prefix for constructing configuration data is: `plugin.`, 
Call `onCommonChanged` again for unified processing:
+
+```java
+private <T> void onCommonChanged(final String configKeyPrefix, final List<T> 
changedList,
+                                     final DataEventTypeEnum eventType, final 
Function<? super T, ? extends String> mapperToKey,
+                                     final Class<T> tClass) {
+        // Avoiding concurrent operations on list nodes
+        final ReentrantLock reentrantLock = 
listSaveLockMap.computeIfAbsent(configKeyPrefix, key -> new ReentrantLock());
+        try {
+            reentrantLock.lock();
+            // Current incoming plugin list
+            final List<String> changeNames = 
changedList.stream().map(mapperToKey).collect(Collectors.toList());
+            switch (eventType) {
+                // Delete Operation
+                case DELETE:
+                    // delete plugin.${pluginName}
+                    changedList.stream().map(mapperToKey).forEach(removeKey -> 
{
+                        delConfig(configKeyPrefix + removeKey);
+                    });
+                    // Remove the corresponding plugin name from plugin. list
+                    // The plugin.list records the currently enabled list
+                    delChangedData(configKeyPrefix, changeNames);
+                    break;
+                case REFRESH:
+                case MYSELF:
+                    // Overload logic
+                    // Get a list of all plugins in plugin.list
+                    final List<String> configDataNames = 
this.getConfigDataNames(configKeyPrefix);
+                    // Update each currently adjusted plug-in in turn
+                    changedList.forEach(changedData -> {
+                        // Publish Configuration
+                        publishConfig(configKeyPrefix + 
mapperToKey.apply(changedData), changedData);
+                    });
+                    // If there is more data in the currently stored list than 
what is currently being passed in, delete the excess data
+                    if (configDataNames != null && configDataNames.size() > 
changedList.size()) {
+                        // Kick out the currently loaded data
+                        configDataNames.removeAll(changeNames);
+                        // Delete cancelled data one by one
+                        configDataNames.forEach(this::delConfig);
+                    }
+                    // Update list data again
+                    publishConfig(configKeyPrefix + 
DefaultNodeConstants.LIST_STR, changeNames);
+                    break;
+                default:
+                    // Add or update
+                    changedList.forEach(changedData -> {
+                        publishConfig(configKeyPrefix + 
mapperToKey.apply(changedData), changedData);
+                    });
+                    // Update the newly added plugin
+                    putChangeData(configKeyPrefix, changeNames);
+                    break;
+            }
+        } catch (Exception e) {
+            LOG.error("AbstractNodeDataChangedListener onCommonMultiChanged 
error ", e);
+        } finally {
+            reentrantLock.unlock();
+        }
+    }
+```
+
+In the above logic, it actually includes the handling of full overloading 
(REFRESH, MYSELF) and increment (Delete, UPDATE, CREATE)
+
+The plugin mainly includes two nodes:
+- `plugin.list` List of currently effective plugins
+- `plugin.${plugin.name}` Detailed information on specific plugins
+Finally, write the data corresponding to these two nodes into Apollo.
+
+
+
+### Data initialization
+
+After starting `admin`, the current data information will be fully 
synchronized to `Apollo`, which is implemented by `ApolloDataChangedInit`:
+
+```java
+// Inheriting AbstractDataChangedInit
+public class ApolloDataChangedInit extends AbstractDataChangedInit {
+    // Apollo operation object
+    private final ApolloClient apolloClient;
+    
+    public ApolloDataChangedInit(final ApolloClient apolloClient) {
+        this.apolloClient = apolloClient;
+    }
+    
+    @Override
+    protected boolean notExist() {
+        // Check if nodes such as plugin, auth, meta, proxy.selector exist
+        // As long as one does not exist, it enters reload (these nodes will 
not be created, why check once?)
+        return Stream.of(ApolloPathConstants.PLUGIN_DATA_ID, 
ApolloPathConstants.AUTH_DATA_ID, ApolloPathConstants.META_DATA_ID, 
ApolloPathConstants.PROXY_SELECTOR_DATA_ID).allMatch(
+                this::dataIdNotExist);
+    }
+
+    /**
+     * Data id not exist boolean.
+     *
+     * @param pluginDataId the plugin data id
+     * @return the boolean
+     */
+    private boolean dataIdNotExist(final String pluginDataId) {
+        return Objects.isNull(apolloClient.getItemValue(pluginDataId));
+    }
+}
+
+```
+
+Check if there is data in `apollo`, and if it does not exist, synchronize it.
+There is a bug here because the key determined here will not be created during 
synchronization, which will cause data to be reloaded every time it is 
restarted. [PR#5435](https://github.com/apache/shenyu/pull/5435)
+
+`ApolloDataChangedInit` implements the `CommandLineRunner` interface. It is an 
interface provided by `springboot` that executes the `run()` method after all 
`Spring Beans` are initialized. It is commonly used for initialization 
operations in projects.
+- SyncDataService.syncAll()
+
+Query data from the database, then perform full data synchronization, 
including all authentication information, plugin information, rule information, 
selector information, metadata, proxy selector, and discover downstream events. 
Mainly, synchronization events are published through `eventPublisher`. After 
publishing events through `publishEvent()`, `ApplicationListener` performs 
event change operations, which is referred to as `DataChangedEventDispatcher` 
in `ShenYu`.
+
+```java
+@Service
+public class SyncDataServiceImpl implements SyncDataService {
+    // Event Publishing
+    private final ApplicationEventPublisher eventPublisher;
+    
+     /***
+     * Full data synchronization
+     * @param type the type
+     * @return
+     */
+     @Override
+     public boolean syncAll(final DataEventTypeEnum type) {
+         // Synchronize auth data
+         appAuthService.syncData();
+         // Synchronize plugin data
+         List<PluginData> pluginDataList = pluginService.listAll();
+         //Notify subscribers through the Spring publish/subscribe mechanism 
(publishing DataChangedEvent)
+         //Unified monitoring by DataChangedEventDispatcher
+         //DataChangedEvent comes with configuration grouping type, current 
operation type, and data
+         eventPublisher.publishEvent(new 
DataChangedEvent(ConfigGroupEnum.PLUGIN, type, pluginDataList));
+         // synchronizing selector
+         List<SelectorData> selectorDataList = selectorService.listAll();
+         eventPublisher.publishEvent(new 
DataChangedEvent(ConfigGroupEnum.SELECTOR, type, selectorDataList));
+         // Synchronization rules
+         List<RuleData> ruleDataList = ruleService.listAll();
+         eventPublisher.publishEvent(new 
DataChangedEvent(ConfigGroupEnum.RULE, type, ruleDataList));
+         // Synchronization metadata
+         metaDataService.syncData();
+         // Synchronization Downstream List
+         discoveryService.syncData();
+         return true;
+     }
+    
+}
+```
+
+### Bootstrap synchronization operation initialization
+
+The data synchronization initialization operation on the gateway side mainly 
involves subscribing to nodes in `apollo`, and receiving changed data when 
there are changes. This depends on the `listener` mechanism of `apollo`. In 
`ShenYu`, the person responsible for `Apollo` data synchronization is 
`ApolloDataService`.
+The functional logic of Apollo DataService is completed during the 
instantiation process: subscribe to the `shenyu` data synchronization node in 
Apollo. Implement through the `configService.addChangeListener()` method;
+
+
+```java
+public class ApolloDataService extends AbstractNodeDataSyncService implements 
SyncDataService {
+    public ApolloDataService(final Config configService, final 
PluginDataSubscriber pluginDataSubscriber,
+                             final List<MetaDataSubscriber> 
metaDataSubscribers,
+                             final List<AuthDataSubscriber> 
authDataSubscribers,
+                             final List<ProxySelectorDataSubscriber> 
proxySelectorDataSubscribers,
+                             final List<DiscoveryUpstreamDataSubscriber> 
discoveryUpstreamDataSubscribers) {
+        // Configure the prefix for listening
+        super(new ChangeData(ApolloPathConstants.PLUGIN_DATA_ID,
+                        ApolloPathConstants.SELECTOR_DATA_ID,
+                        ApolloPathConstants.RULE_DATA_ID,
+                        ApolloPathConstants.AUTH_DATA_ID,
+                        ApolloPathConstants.META_DATA_ID,
+                        ApolloPathConstants.PROXY_SELECTOR_DATA_ID,
+                        ApolloPathConstants.DISCOVERY_DATA_ID),
+                pluginDataSubscriber, metaDataSubscribers, 
authDataSubscribers, proxySelectorDataSubscribers, 
discoveryUpstreamDataSubscribers);
+        this.configService = configService;
+        // Start listening
+        // Note: The Apollo method is only responsible for obtaining data from 
Apollo and adding it to the local cache, and does not handle listening
+        startWatch();
+        // Configure listening
+        apolloWatchPrefixes();
+    }
+}
+```
+
+Firstly, configure the key information that needs to be processed and 
synchronize it with the admin's key. Next, call the `startWatch()` method to 
process data acquisition and listening. But in the implementation of Apollo, 
this method is only responsible for handling data retrieval and setting it to 
the local cache.
+Listening is handled by the `apolloWatchPrefixes` method
+
+```java
+private void apolloWatchPrefixes() {
+        // Defining Listeners
+        final ConfigChangeListener listener = changeEvent -> {
+            changeEvent.changedKeys().forEach(changeKey -> {
+                try {
+                    final ConfigChange configChange = 
changeEvent.getChange(changeKey);
+                    // Skip if not changed
+                    if (configChange == null) {
+                        LOG.error("apollo watchPrefixes error configChange is 
null {}", changeKey);
+                        return;
+                    }
+                    final String newValue = configChange.getNewValue();
+                    // skip last is "list"
+                    // If it is a Key at the end of the list, such as 
plugin.list, skip it because it is only a list that records the effectiveness 
and will not be cached locally
+                    final int lastListStrIndex = changeKey.length() - 
DefaultNodeConstants.LIST_STR.length();
+                    if (changeKey.lastIndexOf(DefaultNodeConstants.LIST_STR) 
== lastListStrIndex) {
+                        return;
+                    }
+                    // If it starts with plugin. => Process plugin data
+                    if (changeKey.indexOf(ApolloPathConstants.PLUGIN_DATA_ID) 
== 0) {
+                        // delete
+                        if 
(PropertyChangeType.DELETED.equals(configChange.getChangeType())) {
+                            // clear cache
+                            unCachePluginData(changeKey);
+                        } else {
+                            // update cache
+                            cachePluginData(newValue);
+                        }
+                        // If it starts with selector. => Process selector data
+                    } else if 
(changeKey.indexOf(ApolloPathConstants.SELECTOR_DATA_ID) == 0) {
+                        if 
(PropertyChangeType.DELETED.equals(configChange.getChangeType())) {
+                            unCacheSelectorData(changeKey);
+                        } else {
+                            cacheSelectorData(newValue);
+                        }
+                        // If it starts with rule. => Process rule data
+                    } else if 
(changeKey.indexOf(ApolloPathConstants.RULE_DATA_ID) == 0) {
+                        if 
(PropertyChangeType.DELETED.equals(configChange.getChangeType())) {
+                            unCacheRuleData(changeKey);
+                        } else {
+                            cacheRuleData(newValue);
+                        }
+                      // If it starts with auth. => Process auth data
+                    } else if 
(changeKey.indexOf(ApolloPathConstants.AUTH_DATA_ID) == 0) {
+                        if 
(PropertyChangeType.DELETED.equals(configChange.getChangeType())) {
+                            unCacheAuthData(changeKey);
+                        } else {
+                            cacheAuthData(newValue);
+                        }
+                        // If it starts with meta. => Process meta data
+                    } else if 
(changeKey.indexOf(ApolloPathConstants.META_DATA_ID) == 0) {
+                        if 
(PropertyChangeType.DELETED.equals(configChange.getChangeType())) {
+                            unCacheMetaData(changeKey);
+                        } else {
+                            cacheMetaData(newValue);
+                        }
+                        // If it starts with proxy.selector. => Process 
proxy.selector meta
+                    } else if 
(changeKey.indexOf(ApolloPathConstants.PROXY_SELECTOR_DATA_ID) == 0) {
+                        if 
(PropertyChangeType.DELETED.equals(configChange.getChangeType())) {
+                            unCacheProxySelectorData(changeKey);
+                        } else {
+                            cacheProxySelectorData(newValue);
+                        }
+                        // If it starts with discovery. => Process discovery 
meta
+                    } else if 
(changeKey.indexOf(ApolloPathConstants.DISCOVERY_DATA_ID) == 0) {
+                        if 
(PropertyChangeType.DELETED.equals(configChange.getChangeType())) {
+                            unCacheDiscoveryUpstreamData(changeKey);
+                        } else {
+                            cacheDiscoveryUpstreamData(newValue);
+                        }
+                    }
+                } catch (Exception e) {
+                    LOG.error("apollo sync listener change key handler error", 
e);
+                }
+            });
+        };
+        watchConfigChangeListener = listener;
+        // Add listening
+        configService.addChangeListener(listener, Collections.emptySet(), 
ApolloPathConstants.pathKeySet());
+
+    }
+```
+
+The logic of loading data from the previous admin will only add two keys to 
the plugin: `plugin.list` and `plugin.${plugin.name}`, while `plugin.list` is a 
list of all enabled plugins, and the data for this key is in the
+There is no data in the local cache, only `plugin${plugin.name} will be  focus.
+
+At this point, the synchronization logic of bootstrap in `apollo` has been 
analyzed.
+
diff --git a/i18n/zh/code.json b/i18n/zh/code.json
index c499da7ddb..66a6d8157a 100755
--- a/i18n/zh/code.json
+++ b/i18n/zh/code.json
@@ -595,5 +595,11 @@
   },
   "Code Analysis Ext Plugin Loader": {
     "message": "扩展插件加载逻辑源码分析"
+  },
+  "Apollo Data Synchronization Source Code Analysis": {
+    "message": "Apollo 数据同步源码分析"
+  },
+  "Introduce Shenyu data sync by apollo.": {
+    "message": "Apollo 数据同步介绍,源码分析整体流程."
   }
 }
diff --git 
a/i18n/zh/docusaurus-plugin-content-blog/DataSync-SourceCode-Analysis-Apollo-Data-Sync.md
 
b/i18n/zh/docusaurus-plugin-content-blog/DataSync-SourceCode-Analysis-Apollo-Data-Sync.md
new file mode 100644
index 0000000000..cf299ee073
--- /dev/null
+++ 
b/i18n/zh/docusaurus-plugin-content-blog/DataSync-SourceCode-Analysis-Apollo-Data-Sync.md
@@ -0,0 +1,562 @@
+---
+title: Apollo数据同步源码分析
+author: hql0312
+author_title: Apache ShenYu Contributor
+author_url: https://github.com/hql0312
+tags: [apollo,data sync,Apache ShenYu]
+---
+
+> 本文基于`shenyu-2.6.1`版本进行源码分析,官网的介绍请参考 
[数据同步原理](https://shenyu.apache.org/zh/docs/design/data-sync) 。
+
+### Admin管理端
+
+以新增插件的流程来理解下整体的流程
+
+![](/img/activities/code-analysis-apollo-data-sync/Apollo-Sync.png)
+
+### 接收数据
+
+- PluginController.createPlugin()
+
+进入`PluginController`类中的`createPlugin()`方法,它负责数据的校验,添加或更新数据,返回结果信息。
+
+```java
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/plugin")
+public class PluginController {
+
+  @PostMapping("")
+  @RequiresPermissions("system:plugin:add")
+  public ShenyuAdminResult createPlugin(@Valid @ModelAttribute final PluginDTO 
pluginDTO) {
+      // 调用pluginService.createOrUpdate 进行处理逻辑
+      return 
ShenyuAdminResult.success(pluginService.createOrUpdate(pluginDTO));
+  }
+    
+    // ......
+}
+```
+
+### 处理数据
+
+- PluginServiceImpl.createOrUpdate() -> PluginServiceImpl.create()
+
+在`PluginServiceImpl`类中通过`create()`方法完成数据的转换,保存到数据库,发布事件。
+
+```java
+@RequiredArgsConstructor
+@Service
+public class PluginServiceImpl implements SelectorService {
+    // 事件发布对象 pluginEventPublisher
+    private final PluginEventPublisher pluginEventPublisher;
+
+   private String create(final PluginDTO pluginDTO) {
+      // 判断有没有对应的插件
+      Assert.isNull(pluginMapper.nameExisted(pluginDTO.getName()), 
AdminConstants.PLUGIN_NAME_IS_EXIST);
+      // 自定义的插件jar
+      if (!Objects.isNull(pluginDTO.getFile())) {
+        Assert.isTrue(checkFile(Base64.decode(pluginDTO.getFile())), 
AdminConstants.THE_PLUGIN_JAR_FILE_IS_NOT_CORRECT_OR_EXCEEDS_16_MB);
+      }
+      // 创建plugin对象
+      PluginDO pluginDO = PluginDO.buildPluginDO(pluginDTO);
+      // 插入对象到数据库
+      if (pluginMapper.insertSelective(pluginDO) > 0) {
+        // 插件新增成功,则发布创建事件
+        // publish create event. init plugin data
+        pluginEventPublisher.onCreated(pluginDO);
+      }
+      return ShenyuResultMessage.CREATE_SUCCESS;
+  }
+    
+    
+    // ......
+    
+}
+
+```
+
+在`PluginServiceImpl`类完成数据的持久化操作,即保存数据到数据库,并通过 `pluginEventPublisher` 进行发布事件。
+
+`pluginEventPublisher.onCreateed`方法的逻辑是:发布变更的事件。
+
+```java
+    @Override
+public void onCreated(final PluginDO plugin) {
+        // 发布DataChangeEvent事件:事件分组(插件、选择器、规则)、事件类型(创建、删除、更新)、变更的数据
+        publisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.PLUGIN, 
DataEventTypeEnum.CREATE,
+        Collections.singletonList(PluginTransfer.INSTANCE.mapToData(plugin))));
+        // 发布PluginCreatedEvent
+        publish(new PluginCreatedEvent(plugin, SessionUtil.visitorName()));
+}
+```
+
+发布变更数据通过`publisher.publishEvent()`完成,这个`publisher`对象是一个`ApplicationEventPublisher`类,这个类的全限定名是`org.springframework.context.ApplicationEventPublisher`。看到这儿,我们知道了发布数据是通过`Spring`相关的功能来完成的。
+
+> 关于`ApplicationEventPublisher`:
+>
+> 当有状态发生变化时,发布者调用 `ApplicationEventPublisher` 的 `publishEvent` 
方法发布一个事件,`Spring`容器广播事件给所有观察者,调用观察者的 `onApplicationEvent` 方法把事件对象传递给观察者。调用 
`publishEvent`方法有两种途径,一种是实现接口由容器注入 `ApplicationEventPublisher` 
对象然后调用其方法,另一种是直接调用容器的方法,两种方法发布事件没有太大区别。
+>
+> - `ApplicationEventPublisher`:发布事件;
+> - `ApplicationEvent`:`Spring` 事件,记录事件源、时间和数据;
+> - `ApplicationListener`:事件监听者,观察者;
+
+
+在`Spring`的事件发布机制中,有三个对象,
+
+一个是发布事件的`ApplicationEventPublisher`,在`ShenYu`中通过构造器注入了一个`eventPublisher`。
+
+另一个对象是`ApplicationEvent`,在`ShenYu`中通过`DataChangedEvent`继承了它,表示事件对象。
+
+```java
+public class DataChangedEvent extends ApplicationEvent {
+//......
+}
+```
+
+最后一个是 
`ApplicationListener`,在`ShenYu`中通过`DataChangedEventDispatcher`类实现了该接口,作为事件的监听者,负责处理事件对象。
+
+```java
+@Component
+public class DataChangedEventDispatcher implements 
ApplicationListener<DataChangedEvent>, InitializingBean {
+
+    //......
+    
+}
+```
+
+### 分发数据
+
+- DataChangedEventDispatcher.onApplicationEvent()
+
+当事件发布完成后,会自动进入到`DataChangedEventDispatcher`类中的`onApplicationEvent()`方法,进行事件处理。
+
+```java
+@Component
+public class DataChangedEventDispatcher implements 
ApplicationListener<DataChangedEvent>, InitializingBean {
+
+  /**
+     * 有数据变更时,调用此方法
+     * @param event
+     */
+  @Override
+  @SuppressWarnings("unchecked")
+  public void onApplicationEvent(final DataChangedEvent event) {
+    // 遍历数据变更监听器(这里只会注册ApolloDataChangedListener)
+    for (DataChangedListener listener : listeners) {
+      // 依据不同的分组类型进行转发
+      switch (event.getGroupKey()) {
+        case APP_AUTH: // 认证信息
+          listener.onAppAuthChanged((List<AppAuthData>) event.getSource(), 
event.getEventType());
+          break;
+        case PLUGIN: // 插件事件
+          // 调用注册的listener对象
+          listener.onPluginChanged((List<PluginData>) event.getSource(), 
event.getEventType());
+          break;
+        case RULE: // 规则事件
+          listener.onRuleChanged((List<RuleData>) event.getSource(), 
event.getEventType());
+          break;
+        case SELECTOR: // 选择器事件
+          listener.onSelectorChanged((List<SelectorData>) event.getSource(), 
event.getEventType());
+          break;
+        case META_DATA: // 元数据事件
+          listener.onMetaDataChanged((List<MetaData>) event.getSource(), 
event.getEventType());
+          break;
+        case PROXY_SELECTOR: // 代理选择器事件
+          listener.onProxySelectorChanged((List<ProxySelectorData>) 
event.getSource(), event.getEventType());
+          break;
+        case DISCOVER_UPSTREAM: // 注册发现下游列表事件
+          listener.onDiscoveryUpstreamChanged((List<DiscoverySyncData>) 
event.getSource(), event.getEventType());
+          
applicationContext.getBean(LoadServiceDocEntry.class).loadDocOnUpstreamChanged((List<DiscoverySyncData>)
 event.getSource(), event.getEventType());
+          break;
+        default:
+          throw new IllegalStateException("Unexpected value: " + 
event.getGroupKey());
+      }
+    }
+  }
+    
+}
+```
+
+当有数据变更时,调用`onApplicationEvent`方法,然后遍历所有数据变更监听器,判断是哪种数据类型,交给相应的数据监听器进行处理。
+
+`ShenYu`将所有数据进行了分组,一共会有以下种:认证信息、插件信息、规则信息、选择器信息、元数据、代理选择器、发现下游事件。
+
+这里的数据变更监听器(`DataChangedListener`),就是数据同步策略的抽象,由特定的实现来处理,而不同的监听由不同的实现来处理,当前分析的是Apollo来
+监听,所以这里只关注 `ApolloDataChangedListener`。
+
+```java
+// 继承AbstractNodeDataChangedListener
+public class ApolloDataChangedListener extends AbstractNodeDataChangedListener 
{
+    
+}
+```
+
+`ApolloDataChangedListener` 继承了 `AbstractNodeDataChangedListener` 
类,该类主要是以key作为存储方式的基类,如apollo、nacos等,其他的如zookeeper、
+consul、etcd 等是以path的方式进行分层级来查找的。
+
+```java
+// 以key作为查找存储方式的基类
+public abstract class AbstractNodeDataChangedListener implements 
DataChangedListener { 
+    
+    protected AbstractNodeDataChangedListener(final ChangeData changeData) {
+      this.changeData = changeData;
+    }
+}
+```
+
+`AbstractNodeDataChangedListener` 接收 
`ChangeData`作为参数,该对象定义了存储于Apollo中的各个数据的key命名,存储于Apollo中的数据包括以下数据:
+- 插件(plugin)
+- 选择器(selector)
+- 规则(rule)
+- 授权(auth)
+- 元数据(meta)
+- 代理选择器(proxy.selector)
+- 下游列表(discovery)
+
+这些信息由ApolloDataChangedListener构造器指定:
+
+```java
+public class ApolloDataChangedListener extends AbstractNodeDataChangedListener 
{
+  public ApolloDataChangedListener(final ApolloClient apolloClient) {
+    // 配置几类分组数据的前缀
+    super(new ChangeData(ApolloPathConstants.PLUGIN_DATA_ID,
+            ApolloPathConstants.SELECTOR_DATA_ID,
+            ApolloPathConstants.RULE_DATA_ID,
+            ApolloPathConstants.AUTH_DATA_ID,
+            ApolloPathConstants.META_DATA_ID,
+            ApolloPathConstants.PROXY_SELECTOR_DATA_ID,
+            ApolloPathConstants.DISCOVERY_DATA_ID));
+    // 操作apollo的对象
+    this.apolloClient = apolloClient;
+  }
+}
+```
+
+`DataChangedListener` 定义了以下几个方法:
+
+```java
+// 数据变更监听器
+public interface DataChangedListener {
+
+    // 授权信息变更时调用
+    default void onAppAuthChanged(List<AppAuthData> changed, DataEventTypeEnum 
eventType) {
+    }
+
+    // 插件信息变更时调用
+    default void onPluginChanged(List<PluginData> changed, DataEventTypeEnum 
eventType) {
+    }
+
+    // 选择器信息变更时调用
+    default void onSelectorChanged(List<SelectorData> changed, 
DataEventTypeEnum eventType) {
+    }
+    
+     // 元数据信息变更时调用
+    default void onMetaDataChanged(List<MetaData> changed, DataEventTypeEnum 
eventType) {
+
+    }
+
+    // 规则信息变更时调用
+    default void onRuleChanged(List<RuleData> changed, DataEventTypeEnum 
eventType) {
+    }
+
+    // 代理选择器变更时调用
+    default void onProxySelectorChanged(List<ProxySelectorData> changed, 
DataEventTypeEnum eventType) {
+    }
+    // 发现下游信息变更时调用
+    default void onDiscoveryUpstreamChanged(List<DiscoverySyncData> changed, 
DataEventTypeEnum eventType) {
+    }
+
+}
+```
+
+由 `DataChangedEventDispatcher`处理插件时,调用方法 `listener.onPluginChanged`, 
接下来分析下对象的逻辑,实现由`AbstractNodeDataChangedListener`处理:
+
+```java
+public abstract class AbstractNodeDataChangedListener implements 
DataChangedListener {
+  @Override
+  public void onPluginChanged(final List<PluginData> changed, final 
DataEventTypeEnum eventType) {
+    // 配置前缀为plugin.
+    final String configKeyPrefix = changeData.getPluginDataId() + 
DefaultNodeConstants.JOIN_POINT;
+    this.onCommonChanged(configKeyPrefix, changed, eventType, 
PluginData::getName, PluginData.class);
+    LOG.debug("[DataChangedListener] PluginChanged {}", configKeyPrefix);
+  }
+}
+```
+
+首先构建配置数据的key前缀为:`plugin.`, 再调用`onCommonChanged`统一处理:
+
+```java
+private <T> void onCommonChanged(final String configKeyPrefix, final List<T> 
changedList,
+                                     final DataEventTypeEnum eventType, final 
Function<? super T, ? extends String> mapperToKey,
+                                     final Class<T> tClass) {
+        // Avoiding concurrent operations on list nodes
+        final ReentrantLock reentrantLock = 
listSaveLockMap.computeIfAbsent(configKeyPrefix, key -> new ReentrantLock());
+        try {
+            reentrantLock.lock();
+            // 当前传入的插件列表
+            final List<String> changeNames = 
changedList.stream().map(mapperToKey).collect(Collectors.toList());
+            switch (eventType) {
+                // 删除操作
+                case DELETE:
+                    // 按 plugin.${pluginName} 进行删除
+                    changedList.stream().map(mapperToKey).forEach(removeKey -> 
{
+                        delConfig(configKeyPrefix + removeKey);
+                    });
+                    // 从plugin.list中移除对应的插件名称
+                    // plugin.list 记录下了目前启用的列表
+                    delChangedData(configKeyPrefix, changeNames);
+                    break;
+                case REFRESH:
+                case MYSELF:
+                    // 重载逻辑
+                    // 获取plugin.list中的所有插件列表
+                    final List<String> configDataNames = 
this.getConfigDataNames(configKeyPrefix);
+                    // 依次更新当前调整的每个插件
+                    changedList.forEach(changedData -> {
+                        // 发布配置
+                        publishConfig(configKeyPrefix + 
mapperToKey.apply(changedData), changedData);
+                    });
+                    // 目前存储的列表中,如果数据比当前传入的多,则删除多余的数据
+                    if (configDataNames != null && configDataNames.size() > 
changedList.size()) {
+                        // 踢除当前加载的数据
+                        configDataNames.removeAll(changeNames);
+                        // 逐个删除已经取消的数据
+                        configDataNames.forEach(this::delConfig);
+                    }
+                    // 重新更新列表数据
+                    publishConfig(configKeyPrefix + 
DefaultNodeConstants.LIST_STR, changeNames);
+                    break;
+                default:
+                    // 新增或是更新
+                    changedList.forEach(changedData -> {
+                        publishConfig(configKeyPrefix + 
mapperToKey.apply(changedData), changedData);
+                    });
+                    // 将新加的插件更新
+                    putChangeData(configKeyPrefix, changeNames);
+                    break;
+            }
+        } catch (Exception e) {
+            LOG.error("AbstractNodeDataChangedListener onCommonMultiChanged 
error ", e);
+        } finally {
+            reentrantLock.unlock();
+        }
+    }
+```
+
+在以上逻辑,其实包含全量重载(REFRESH、MYSELF)与增量(DELETE、UPDATE、CREATE)的处理
+
+在插件中主要包含两个节点:
+- `plugin.list` 当前生效的插件列表
+- `plugin.${plugin.name}` 具体插件的详细信息
+最后,将这两个节点对应的数据写入Apollo。
+
+
+
+### 数据初始化
+
+当`admin`启动后,会将当前的数据信息全量同步到`apollo`中,由`ApolloDataChangedInit`实现:
+
+```java
+// 继承AbstractDataChangedInit
+public class ApolloDataChangedInit extends AbstractDataChangedInit {
+    // apollo操作对象
+    private final ApolloClient apolloClient;
+    
+    public ApolloDataChangedInit(final ApolloClient apolloClient) {
+        this.apolloClient = apolloClient;
+    }
+    
+    @Override
+    protected boolean notExist() {
+        // 判断 plugin、auth、meta、proxy.selector等节点是否存在
+        // 只要有一个不存在,则进入重新加载(这些节点不会创建,为什么要判断一次呢?)
+        return Stream.of(ApolloPathConstants.PLUGIN_DATA_ID, 
ApolloPathConstants.AUTH_DATA_ID, ApolloPathConstants.META_DATA_ID, 
ApolloPathConstants.PROXY_SELECTOR_DATA_ID).allMatch(
+                this::dataIdNotExist);
+    }
+
+    /**
+     * Data id not exist boolean.
+     *
+     * @param pluginDataId the plugin data id
+     * @return the boolean
+     */
+    private boolean dataIdNotExist(final String pluginDataId) {
+        return Objects.isNull(apolloClient.getItemValue(pluginDataId));
+    }
+}
+
+```
+
+判断`apollo`中是否存在数据,如果不存在,则进行同步。
+这里有一个bug, 
因为这里判断的key,在同步时,并不会创建,则会导致每次重启时都重新加载数据,已提[PR#5435](https://github.com/apache/shenyu/pull/5435)
+
+
+`ApolloDataChangedInit`实现了`CommandLineRunner`接口。它是`springboot`提供的接口,会在所有 
`Spring Beans`初始化之后执行`run()`方法,常用于项目中初始化的操作。
+
+- SyncDataService.syncAll()
+
+从数据库查询数据,然后进行全量数据同步,所有的认证信息、插件信息、规则信息、选择器信息、元数据、代理选择器、发现下游事件。主要是通过`eventPublisher`发布同步事件,`eventPublisher`通过`publishEvent()`发布完事件后,有`ApplicationListener`执行事件变更操作,在`ShenYu`中就是前面提到的`DataChangedEventDispatcher`。
+
+```java
+@Service
+public class SyncDataServiceImpl implements SyncDataService {
+    // 事件发布
+    private final ApplicationEventPublisher eventPublisher;
+    
+     /***
+     * 全量数据同步
+     * @param type the type
+     * @return
+     */
+     @Override
+     public boolean syncAll(final DataEventTypeEnum type) {
+         // 同步auth数据
+         appAuthService.syncData();
+         // 同步插件数据
+         List<PluginData> pluginDataList = pluginService.listAll();
+         // 通过spring发布/订阅机制进行通知订阅者(发布DataChangedEvent)
+         // 统一由DataChangedEventDispatcher进行监听
+         // DataChangedEvent带上了配置分组类型、当前操作类型、数据
+         eventPublisher.publishEvent(new 
DataChangedEvent(ConfigGroupEnum.PLUGIN, type, pluginDataList));
+         // 同步选择器
+         List<SelectorData> selectorDataList = selectorService.listAll();
+         eventPublisher.publishEvent(new 
DataChangedEvent(ConfigGroupEnum.SELECTOR, type, selectorDataList));
+         // 同步规则
+         List<RuleData> ruleDataList = ruleService.listAll();
+         eventPublisher.publishEvent(new 
DataChangedEvent(ConfigGroupEnum.RULE, type, ruleDataList));
+         //元数据
+         metaDataService.syncData();
+         // 下游列表
+         discoveryService.syncData();
+         return true;
+     }
+    
+}
+```
+
+### bootstrap同步操作初始化
+
+网关这边的数据同步初始化操作主要是订阅`apollo`中的节点,当有数据变更时,收到变更数据。这依赖于`apollo`的`listener`机制。在`ShenYu`中,负责`apollo`数据同步的是`ApolloDataService`。
+
+`ApolloDataService`的功能逻辑是在实例化的过程中完成的:对`apollo`中的`shenyu`数据同步节点完成订阅。通过`configService.addChangeListener()`方法实现;
+
+
+```java
+public class ApolloDataService extends AbstractNodeDataSyncService implements 
SyncDataService {
+    public ApolloDataService(final Config configService, final 
PluginDataSubscriber pluginDataSubscriber,
+                             final List<MetaDataSubscriber> 
metaDataSubscribers,
+                             final List<AuthDataSubscriber> 
authDataSubscribers,
+                             final List<ProxySelectorDataSubscriber> 
proxySelectorDataSubscribers,
+                             final List<DiscoveryUpstreamDataSubscriber> 
discoveryUpstreamDataSubscribers) {
+        // 配置监听的前缀
+        super(new ChangeData(ApolloPathConstants.PLUGIN_DATA_ID,
+                        ApolloPathConstants.SELECTOR_DATA_ID,
+                        ApolloPathConstants.RULE_DATA_ID,
+                        ApolloPathConstants.AUTH_DATA_ID,
+                        ApolloPathConstants.META_DATA_ID,
+                        ApolloPathConstants.PROXY_SELECTOR_DATA_ID,
+                        ApolloPathConstants.DISCOVERY_DATA_ID),
+                pluginDataSubscriber, metaDataSubscribers, 
authDataSubscribers, proxySelectorDataSubscribers, 
discoveryUpstreamDataSubscribers);
+        this.configService = configService;
+        // 开始监听
+        // 注:Apollo该方法,只负责获取apollo的数据获取,并添加到本地缓存中,不处理监听
+        startWatch();
+        // 配置监听
+        apolloWatchPrefixes();
+    }
+}
+```
+
+首先配置需要处理的key信息,同admin同步的key。接着调用`startWatch()` 
方法进行处理数据获取与监听。但对于Apollo的实现中,该方法只负责处理数据的获取并设置到本地缓存中。
+监听由`apolloWatchPrefixes`方法来处理
+
+```java
+private void apolloWatchPrefixes() {
+        // 定义监听器
+        final ConfigChangeListener listener = changeEvent -> {
+            changeEvent.changedKeys().forEach(changeKey -> {
+                try {
+                    final ConfigChange configChange = 
changeEvent.getChange(changeKey);
+                    // 未变更则跳过
+                    if (configChange == null) {
+                        LOG.error("apollo watchPrefixes error configChange is 
null {}", changeKey);
+                        return;
+                    }
+                    final String newValue = configChange.getNewValue();
+                    // skip last is "list"
+                    // 如果是list结尾的Key,如plugin.list则跳过,因为这里只是记录生效的一个列表,不会在本地缓存中
+                    final int lastListStrIndex = changeKey.length() - 
DefaultNodeConstants.LIST_STR.length();
+                    if (changeKey.lastIndexOf(DefaultNodeConstants.LIST_STR) 
== lastListStrIndex) {
+                        return;
+                    }
+                    // 如果是plugin.开头 => 处理插件数据
+                    if (changeKey.indexOf(ApolloPathConstants.PLUGIN_DATA_ID) 
== 0) {
+                        // 删除
+                        if 
(PropertyChangeType.DELETED.equals(configChange.getChangeType())) {
+                            // 清除缓存
+                            unCachePluginData(changeKey);
+                        } else {
+                            // 更新缓存
+                            cachePluginData(newValue);
+                        }
+                        // 如果是selector.开头 => 处理选择器数据
+                    } else if 
(changeKey.indexOf(ApolloPathConstants.SELECTOR_DATA_ID) == 0) {
+                        if 
(PropertyChangeType.DELETED.equals(configChange.getChangeType())) {
+                            unCacheSelectorData(changeKey);
+                        } else {
+                            cacheSelectorData(newValue);
+                        }
+                        // 如果是rule.开头 => 处理规则数据
+                    } else if 
(changeKey.indexOf(ApolloPathConstants.RULE_DATA_ID) == 0) {
+                        if 
(PropertyChangeType.DELETED.equals(configChange.getChangeType())) {
+                            unCacheRuleData(changeKey);
+                        } else {
+                            cacheRuleData(newValue);
+                        }
+                        // 如果是auth.开头 => 处理授权数据
+                    } else if 
(changeKey.indexOf(ApolloPathConstants.AUTH_DATA_ID) == 0) {
+                        if 
(PropertyChangeType.DELETED.equals(configChange.getChangeType())) {
+                            unCacheAuthData(changeKey);
+                        } else {
+                            cacheAuthData(newValue);
+                        }
+                        // 如果是meta.开头 => 处理元数据
+                    } else if 
(changeKey.indexOf(ApolloPathConstants.META_DATA_ID) == 0) {
+                        if 
(PropertyChangeType.DELETED.equals(configChange.getChangeType())) {
+                            unCacheMetaData(changeKey);
+                        } else {
+                            cacheMetaData(newValue);
+                        }
+                        // 如果是proxy.selector.开头 => 处理代理选择器数据
+                    } else if 
(changeKey.indexOf(ApolloPathConstants.PROXY_SELECTOR_DATA_ID) == 0) {
+                        if 
(PropertyChangeType.DELETED.equals(configChange.getChangeType())) {
+                            unCacheProxySelectorData(changeKey);
+                        } else {
+                            cacheProxySelectorData(newValue);
+                        }
+                        // 如果是discovery.开头 => 处理下游列表数据
+                    } else if 
(changeKey.indexOf(ApolloPathConstants.DISCOVERY_DATA_ID) == 0) {
+                        if 
(PropertyChangeType.DELETED.equals(configChange.getChangeType())) {
+                            unCacheDiscoveryUpstreamData(changeKey);
+                        } else {
+                            cacheDiscoveryUpstreamData(newValue);
+                        }
+                    }
+                } catch (Exception e) {
+                    LOG.error("apollo sync listener change key handler error", 
e);
+                }
+            });
+        };
+        watchConfigChangeListener = listener;
+        // 添加监听
+        configService.addChangeListener(listener, Collections.emptySet(), 
ApolloPathConstants.pathKeySet());
+
+    }
+```
+
+由前面admin加载数据的逻辑,插件只会增加两个Key:`plugin.list` 与 `plugin.${plugin.name}`,而 
`plugin.list` 是所有启用的插件列表,该key的数据在
+本地缓存中没有数据,只会关注`plugin.${plugin.name}` key对应的数据,这是对应的插件的详细信息。
+
+至此,bootstrap在`apollo`中的同步逻辑就分析完成。
+
diff --git a/src/data/blogInfo.js b/src/data/blogInfo.js
index 093f3fb3d2..38e76753cc 100644
--- a/src/data/blogInfo.js
+++ b/src/data/blogInfo.js
@@ -151,6 +151,16 @@ export default [
                 date: '2022-07-2',
                 abs:<Translate>In ShenYu gateway, data synchronization refers 
to how to synchronize the updated data to the gateway after the data is sent in 
the background management system. The Apache ShenYu gateway currently supports 
data synchronization for ZooKeeper, WebSocket, http long poll, Nacos, etcd and 
Consul. The main content of this article is based on WebSocket data 
synchronization source code analysis.</Translate>
             },
+            {
+                title: <Translate>Apollo Data Synchronization Source Code 
Analysis</Translate>,
+                author: "hql0312 (Committer)",
+                autImg: "/img/blog/hql0312.png",
+                autPage: "https://github.com/hql0312";,
+                src: "DataSync-SourceCode-Analysis-Apollo-Data-Sync",
+                cover: "/img/logo.svg",
+                date: '2024-02-22',
+                abs:<Translate>Introduce Shenyu data sync by 
apollo.</Translate>
+            },
         ]
     },
     {
diff --git 
a/static/img/activities/code-analysis-apollo-data-sync/Apollo-Sync.png 
b/static/img/activities/code-analysis-apollo-data-sync/Apollo-Sync.png
new file mode 100644
index 0000000000..fe2afe7cd9
Binary files /dev/null and 
b/static/img/activities/code-analysis-apollo-data-sync/Apollo-Sync.png differ

Reply via email to