blob: 5f5804454133951a60e6b664d2306c77e8cdcef9 [file] [log] [blame]
<!doctype html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="generator" content="Docusaurus v2.0.0-beta.4">
<link rel="alternate" type="application/rss+xml" href="/blog/rss.xml" title="Apache ShenYu Blog RSS Feed">
<link rel="alternate" type="application/atom+xml" href="/blog/atom.xml" title="Apache ShenYu Blog Atom Feed">
<link rel="search" type="application/opensearchdescription+xml" title="Apache ShenYu" href="/opensearch.xml">
<link rel="alternate" type="application/rss+xml" href="/news/rss.xml" title="Apache ShenYu Blog RSS Feed">
<link rel="alternate" type="application/atom+xml" href="/news/atom.xml" title="Apache ShenYu Blog Atom Feed"><title data-react-helmet="true">Etcd Data Synchronization Source Code Analysis | Apache ShenYu</title><meta data-react-helmet="true" property="og:title" content="Etcd Data Synchronization Source Code Analysis | Apache ShenYu"><meta data-react-helmet="true" name="description" content="Apache ShenYu is an asynchronous, high-performance, cross-language, responsive API gateway."><meta data-react-helmet="true" property="og:description" content="Apache ShenYu is an asynchronous, high-performance, cross-language, responsive API gateway."><meta data-react-helmet="true" property="og:url" content="https://shenyu.apache.org//blog/DataSync-SourceCode-Analysis-Etcd-Data-Sync"><meta data-react-helmet="true" name="docsearch:language" content="en"><meta data-react-helmet="true" name="docsearch:docusaurus_tag" content="default"><link data-react-helmet="true" rel="shortcut icon" href="/img/favicon.svg"><link data-react-helmet="true" rel="canonical" href="https://shenyu.apache.org//blog/DataSync-SourceCode-Analysis-Etcd-Data-Sync"><link data-react-helmet="true" rel="alternate" href="https://shenyu.apache.org//blog/DataSync-SourceCode-Analysis-Etcd-Data-Sync" hreflang="en"><link data-react-helmet="true" rel="alternate" href="https://shenyu.apache.org//zh/blog/DataSync-SourceCode-Analysis-Etcd-Data-Sync" hreflang="zh"><link data-react-helmet="true" rel="alternate" href="https://shenyu.apache.org//blog/DataSync-SourceCode-Analysis-Etcd-Data-Sync" hreflang="x-default"><link data-react-helmet="true" rel="preconnect" href="https://BH4D9OD16A-dsn.algolia.net" crossorigin="anonymous"><link rel="stylesheet" href="/assets/css/styles.b6f9a199.css">
<link rel="preload" href="/assets/js/runtime~main.490de106.js" as="script">
<link rel="preload" href="/assets/js/main.cb386500.js" as="script">
</head>
<body>
<script>!function(){function t(t){document.documentElement.setAttribute("data-theme",t)}var e=function(){var t=null;try{t=localStorage.getItem("theme")}catch(t){}return t}();t(null!==e?e:"light")}()</script><div id="__docusaurus">
<div><a href="#" class="skipToContent_OuoZ">Skip to main content</a></div><nav class="navbar navbar--fixed-top"><div class="navbar__inner"><div class="navbar__items"><button aria-label="Navigation bar toggle" class="navbar__toggle clean-btn" type="button" tabindex="0"><svg width="30" height="30" viewBox="0 0 30 30" aria-hidden="true"><path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M4 7h22M4 15h22M4 23h22"></path></svg></button><a class="navbar__brand" href="/"><img src="/img/logo.svg" alt="Apache ShenYu Logo" class="themedImage_TMUO themedImage--light_4Vu1 navbar__logo"><img src="/img/logo-light.svg" alt="Apache ShenYu Logo" class="themedImage_TMUO themedImage--dark_uzRr navbar__logo"></a></div><div class="navbar__items navbar__items--right"><a class="navbar__item navbar__link" href="/download">Download</a><a class="navbar__item navbar__link" href="/document">Docs</a><a class="navbar__item navbar__link" href="/community/contributor-guide">Community</a><a class="navbar__item navbar__link" href="/team">Team</a><a class="navbar__item navbar__link" href="/event">Event</a><a class="navbar__item navbar__link" href="/news">News</a><a aria-current="page" class="navbar__item navbar__link navbar__link--active" href="/blog">Blog</a><a class="navbar__item navbar__link" href="/users">Users</a><div class="navbar__item dropdown dropdown--hoverable dropdown--right"><a class="navbar__item navbar__link">ASF</a><ul class="dropdown__menu"><li><a href="https://www.apache.org/" target="_blank" rel="noopener noreferrer" class="dropdown__link">Foundation</a></li><li><a href="https://www.apache.org/licenses/" target="_blank" rel="noopener noreferrer" class="dropdown__link">License</a></li><li><a href="https://www.apache.org/events/current-event" target="_blank" rel="noopener noreferrer" class="dropdown__link">Events</a></li><li><a href="https://www.apache.org/security/" target="_blank" rel="noopener noreferrer" class="dropdown__link">Security</a></li><li><a href="https://www.apache.org/foundation/sponsorship.html" target="_blank" rel="noopener noreferrer" class="dropdown__link">Sponsorship</a></li><li><a href="https://www.apache.org/foundation/policies/privacy.html" target="_blank" rel="noopener noreferrer" class="dropdown__link">Privacy</a></li><li><a href="https://www.apache.org/foundation/thanks.html" target="_blank" rel="noopener noreferrer" class="dropdown__link">Thanks</a></li></ul></div><a href="https://github.com/apache/shenyu" target="_blank" rel="noopener noreferrer" class="navbar__item navbar__link"><span>GitHub<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a><div class="navbar__item dropdown dropdown--hoverable dropdown--right"><a href="#" class="navbar__item navbar__link"><span><svg t="1631348384596" class="icon" viewBox="0 0 1024 1024" version="1.1" style="vertical-align:text-bottom;margin-right:5px" p-id="557" width="20" height="20"><path d="M547.797333 638.208l-104.405333-103.168 1.237333-1.28a720.170667 720.170667 0 0 0 152.490667-268.373333h120.448V183.082667h-287.744V100.906667H347.605333v82.218666H59.818667V265.386667h459.178666a648.234667 648.234667 0 0 1-130.304 219.946666 643.242667 643.242667 0 0 1-94.976-137.728H211.541333a722.048 722.048 0 0 0 122.453334 187.434667l-209.194667 206.378667 58.368 58.368 205.525333-205.525334 127.872 127.829334 31.232-83.84m231.424-208.426667h-82.218666l-184.96 493.312h82.218666l46.037334-123.306667h195.242666l46.464 123.306667h82.218667l-185.002667-493.312m-107.690666 287.744l66.56-178.005333 66.602666 178.005333z" fill="currentColor" p-id="558"></path></svg><span>English</span></span></a><ul class="dropdown__menu"><li><a href="/blog/DataSync-SourceCode-Analysis-Etcd-Data-Sync" target="_self" rel="noopener noreferrer" class="dropdown__link dropdown__link--active" style="text-transform:capitalize">English</a></li><li><a href="/zh/blog/DataSync-SourceCode-Analysis-Etcd-Data-Sync" target="_self" rel="noopener noreferrer" class="dropdown__link" style="text-transform:capitalize">简体中文</a></li></ul></div><div class="react-toggle toggle_2i4l react-toggle--disabled"><div class="react-toggle-track" role="button" tabindex="-1"><div class="react-toggle-track-check"><span class="toggle_iYfV">🌜</span></div><div class="react-toggle-track-x"><span class="toggle_iYfV">🌞</span></div><div class="react-toggle-thumb"></div></div><input type="checkbox" class="react-toggle-screenreader-only" aria-label="Switch between dark and light mode"></div><div class="searchBox_Bc3W"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><svg width="20" height="20" class="DocSearch-Search-Icon" viewBox="0 0 20 20" aria-hidden="true"><path d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z" stroke="currentColor" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path></svg><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"></span></button></div></div></div><div role="presentation" class="navbar-sidebar__backdrop"></div></nav><div class="main-wrapper blog-wrapper blog-post-page"><div class="container margin-vert--lg"><div class="row"><main class="col col--9 col--offset-1"><article><header><h1 class="blogPostTitle_d4p0">Etcd Data Synchronization Source Code Analysis</h1><div class="blogPostData_-Im+ margin-vert--md"><time datetime="2024-04-20T01:09:53.293Z">April 20, 2024</time> · 18 min read</div><div class="avatar margin-vert--md"><div class="avatar__intro"><div class="avatar__name"><a href="https://github.com/4zd" target="_blank" rel="noopener noreferrer">4zd</a></div><small class="avatar__subtitle">Apache ShenYu Contributor</small></div></div></header><div class="markdown"><blockquote><p><a href="https://shenyu.apache.org/zh/docs/index" target="_blank" rel="noopener noreferrer">Apache ShenYu</a> is an asynchronous, high-performance, cross-language, responsive API gateway.</p></blockquote><p>In <code>ShenYu</code> 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 <code>ZooKeeper</code>, <code>WebSocket</code>, <code>http long poll</code>, <code>Nacos</code>, <code>Etcd</code> and <code>Consul</code>. The main content of this article is based on <code>Etcd</code> data synchronization source code analysis.</p><blockquote><p>This paper based on <code>shenyu-2.4.0</code> version of the source code analysis, the official website of the introduction of please refer to the <a href="https://shenyu.apache.org/docs/design/data-sync/" target="_blank" rel="noopener noreferrer">Data Synchronization Design</a> .</p></blockquote><h3><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="1-about-etcd"></a>1. About Etcd<a class="hash-link" href="#1-about-etcd" title="Direct link to heading">#</a></h3><p><a href="https://etcd.io" target="_blank" rel="noopener noreferrer">Etcd</a> is a strongly consistent, distributed key-value store that provides a reliable way to store data that needs to be accessed by a distributed system or cluster of machines.</p><h3><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="2-admin-data-sync"></a>2. Admin Data Sync<a class="hash-link" href="#2-admin-data-sync" title="Direct link to heading">#</a></h3><p>We traced the source code from a real case, such as updating a selector data in the <code>Divide</code> plugin to a weight of 90 in a background administration system:</p><p><img src="/assets/images/update-selector-en-4efb58e488bd424a54213d31929d7eb1.png"></p><h4><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="21-accept-data"></a>2.1 Accept Data<a class="hash-link" href="#21-accept-data" title="Direct link to heading">#</a></h4><ul><li>SelectorController.createSelector()</li></ul><p>Enter the createSelector() method of the <code>SelectorController</code> class, which validates data, adds or updates data, and returns results.</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">@Validated</span></span><span class="token-line" style="color:#393A34"><span class="token plain">@RequiredArgsConstructor</span></span><span class="token-line" style="color:#393A34"><span class="token plain">@RestController</span></span><span class="token-line" style="color:#393A34"><span class="token plain">@RequestMapping(&quot;/selector&quot;)</span></span><span class="token-line" style="color:#393A34"><span class="token plain">public class SelectorController {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> @PutMapping(&quot;/{id}&quot;)</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> public ShenyuAdminResult updateSelector(@PathVariable(&quot;id&quot;) final String id, @Valid @RequestBody final SelectorDTO selectorDTO) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // set the current selector data ID</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> selectorDTO.setId(id);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // create or update operation</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> Integer updateCount = selectorService.createOrUpdate(selectorDTO);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // return result </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return ShenyuAdminResult.success(ShenyuResultMessage.UPDATE_SUCCESS, updateCount);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // ......</span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><h4><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="22-handle-data"></a>2.2 Handle Data<a class="hash-link" href="#22-handle-data" title="Direct link to heading">#</a></h4><ul><li>SelectorServiceImpl.createOrUpdate()</li></ul><p>Convert data in the <code>SelectorServiceImpl</code> class using the <code>createOrUpdate()</code> method, save it to the database, publish the event, update <code>upstream</code>.</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">@RequiredArgsConstructor</span></span><span class="token-line" style="color:#393A34"><span class="token plain">@Service</span></span><span class="token-line" style="color:#393A34"><span class="token plain">public class SelectorServiceImpl implements SelectorService {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // eventPublisher</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private final ApplicationEventPublisher eventPublisher;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> @Override</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> @Transactional(rollbackFor = Exception.class)</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> public int createOrUpdate(final SelectorDTO selectorDTO) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> int selectorCount;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // build data DTO --&gt; DO</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> SelectorDO selectorDO = SelectorDO.buildSelectorDO(selectorDTO);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> List&lt;SelectorConditionDTO&gt; selectorConditionDTOs = selectorDTO.getSelectorConditions();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // insert or update ?</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (StringUtils.isEmpty(selectorDTO.getId())) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // insert into data</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> selectorCount = selectorMapper.insertSelective(selectorDO);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // insert into condition data</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> selectorConditionDTOs.forEach(selectorConditionDTO -&gt; {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> selectorConditionDTO.setSelectorId(selectorDO.getId());</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> selectorConditionMapper.insertSelective(SelectorConditionDO.buildSelectorConditionDO(selectorConditionDTO));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> });</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // check selector add</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (dataPermissionMapper.listByUserId(JwtUtils.getUserInfo().getUserId()).size() &gt; 0) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> DataPermissionDTO dataPermissionDTO = new DataPermissionDTO();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> dataPermissionDTO.setUserId(JwtUtils.getUserInfo().getUserId());</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> dataPermissionDTO.setDataId(selectorDO.getId());</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> dataPermissionDTO.setDataType(AdminConstants.SELECTOR_DATA_TYPE);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> dataPermissionMapper.insertSelective(DataPermissionDO.buildPermissionDO(dataPermissionDTO));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> } else {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // update data, delete and then insert</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> selectorCount = selectorMapper.updateSelective(selectorDO);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> //delete rule condition then add</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> selectorConditionMapper.deleteByQuery(new SelectorConditionQuery(selectorDO.getId()));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> selectorConditionDTOs.forEach(selectorConditionDTO -&gt; {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> selectorConditionDTO.setSelectorId(selectorDO.getId());</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> SelectorConditionDO selectorConditionDO = SelectorConditionDO.buildSelectorConditionDO(selectorConditionDTO);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> selectorConditionMapper.insertSelective(selectorConditionDO);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> });</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // publish event</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> publishEvent(selectorDO, selectorConditionDTOs);</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // update upstream</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> updateDivideUpstream(selectorDO);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return selectorCount;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // ......</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p>In the <code>Service</code> class to persist data, i.e. to the database, this should be familiar, not expand. The update upstream operation is analyzed in the corresponding section below, focusing on the publish event operation, which performs data synchronization.</p><p>The logic of the <code>publishEvent()</code> method is to find the plugin corresponding to the selector, build the conditional data, and publish the change data.</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain"> private void publishEvent(final SelectorDO selectorDO, final List&lt;SelectorConditionDTO&gt; selectorConditionDTOs) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // find plugin of selector</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> PluginDO pluginDO = pluginMapper.selectById(selectorDO.getPluginId());</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // build condition data</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> List&lt;ConditionData&gt; conditionDataList = selectorConditionDTOs.stream().map(ConditionTransfer.INSTANCE::mapToSelectorDTO).collect(Collectors.toList());</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // publish event</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.SELECTOR, DataEventTypeEnum.UPDATE,</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> Collections.singletonList(SelectorDO.transFrom(selectorDO, pluginDO.getName(), conditionDataList))));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p>Change data released by <code>eventPublisher.PublishEvent()</code> is complete, the <code>eventPublisher</code> object is a <code>ApplicationEventPublisher</code> class, The fully qualified class name is <code>org.springframework.context.ApplicationEventPublisher</code>. Here we see that publishing data is done through <code>Spring</code> related functionality.</p><blockquote><p><code>ApplicationEventPublisher</code></p><p>When a state change, the publisher calls <code>ApplicationEventPublisher</code> of <code>publishEvent</code> method to release an event, <code>Spring</code> container broadcast event for all observers, The observer&#x27;s <code>onApplicationEvent</code> method is called to pass the event object to the observer. There are two ways to call <code>publishEvent</code> method, one is to implement the interface by the container injection <code>ApplicationEventPublisher</code> object and then call the method, the other is a direct call container, the method of two methods of publishing events not too big difference.</p><ul><li><code>ApplicationEventPublisher</code>: publish event;</li><li><code>ApplicationEvent</code>: <code>Spring</code> event, record the event source, time, and data;</li><li><code>ApplicationListener</code>: event listener, observer.</li></ul></blockquote><p>In Spring event publishing mechanism, there are three objects,</p><p>An object is a publish event <code>ApplicationEventPublisher</code>, in <code>ShenYu</code> through the constructor in the injected a <code>eventPublisher</code>.</p><p>The other object is <code>ApplicationEvent</code> , inherited from <code>ShenYu</code> through <code>DataChangedEvent</code>, representing the event object.</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">public class DataChangedEvent extends ApplicationEvent {</span></span><span class="token-line" style="color:#393A34"><span class="token plain">//......</span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p>The last object is <code>ApplicationListener</code> in <code>ShenYu</code> in through <code>DataChangedEventDispatcher</code> class implements this interface, as the event listener, responsible for handling the event object.</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">@Component</span></span><span class="token-line" style="color:#393A34"><span class="token plain">public class DataChangedEventDispatcher implements ApplicationListener&lt;DataChangedEvent&gt;, InitializingBean {</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> //......</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><h4><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="23-dispatch-data"></a>2.3 Dispatch Data<a class="hash-link" href="#23-dispatch-data" title="Direct link to heading">#</a></h4><ul><li>DataChangedEventDispatcher.onApplicationEvent()</li></ul><p>Released when the event is completed, will automatically enter the <code>DataChangedEventDispatcher</code> class <code>onApplicationEvent()</code> method of handling events.</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">@Component</span></span><span class="token-line" style="color:#393A34"><span class="token plain">public class DataChangedEventDispatcher implements ApplicationListener&lt;DataChangedEvent&gt;, InitializingBean {</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * This method is called when there are data changes</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @param event</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> @Override</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> @SuppressWarnings(&quot;unchecked&quot;)</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> public void onApplicationEvent(final DataChangedEvent event) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // Iterate through the data change listener (usually using a data synchronization approach is fine)</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> for (DataChangedListener listener : listeners) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // What kind of data has changed</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> switch (event.getGroupKey()) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> case APP_AUTH: // app auth data</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> listener.onAppAuthChanged((List&lt;AppAuthData&gt;) event.getSource(), event.getEventType());</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> break;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> case PLUGIN: // plugin data</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> listener.onPluginChanged((List&lt;PluginData&gt;) event.getSource(), event.getEventType());</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> break;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> case RULE: // rule data</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> listener.onRuleChanged((List&lt;RuleData&gt;) event.getSource(), event.getEventType());</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> break;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> case SELECTOR: // selector data</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> listener.onSelectorChanged((List&lt;SelectorData&gt;) event.getSource(), event.getEventType());</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> break;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> case META_DATA: // metadata</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> listener.onMetaDataChanged((List&lt;MetaData&gt;) event.getSource(), event.getEventType());</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> break;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> default: // other types throw exception</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> throw new IllegalStateException(&quot;Unexpected value: &quot; + event.getGroupKey());</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p>When there is a data change, the <code>onApplicationEvent</code> method is called and all the data change listeners are iterated to determine the data type and handed over to the appropriate data listener for processing.</p><p>ShenYu groups all the data into five categories: <code>APP_AUTH</code>, <code>PLUGIN</code>, <code>RULE</code>, <code>SELECTOR</code> and <code>META_DATA</code>.</p><p>Here the data change listener (<code>DataChangedListener</code>) is an abstraction of the data synchronization policy. Its concrete implementation is:</p><p><img src="/assets/images/data-changed-listener-b01d7410746ca4afd526d8c9df865e9b.png"></p><p>These implementation classes are the synchronization strategies currently supported by ShenYu:</p><ul><li><code>WebsocketDataChangedListener</code>: data synchronization based on Websocket;</li><li><code>ZookeeperDataChangedListener</code>:data synchronization based on Zookeeper;</li><li><code>ConsulDataChangedListener</code>: data synchronization based on Consul;</li><li><code>EtcdDataDataChangedListener</code>:data synchronization based on etcd;</li><li><code>HttpLongPollingDataChangedListener</code>:data synchronization based on http long polling;</li><li><code>NacosDataChangedListener</code>:data synchronization based on nacos;</li></ul><p>Given that there are so many implementation strategies, how do you decide which to use?</p><p>Because this paper is based on <code>Etcd</code> data synchronization source code analysis, so here to <code>EtcdDataDataChangedListener</code> as an example, the analysis of how it is loaded and implemented.</p><p>A global search in the source code project shows that its implementation is done in the <code>DataSyncConfiguration</code> class.</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">/**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * Data Sync Configuration</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * By springboot conditional assembly</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * The type Data sync configuration.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain">@Configuration</span></span><span class="token-line" style="color:#393A34"><span class="token plain">public class DataSyncConfiguration {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * The type Etcd listener.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> @Configuration</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> @ConditionalOnProperty(prefix = &quot;shenyu.sync.etcd&quot;, name = &quot;url&quot;)</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> @EnableConfigurationProperties(EtcdProperties.class)</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> static class EtcdListener {</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> @Bean</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> public EtcdClient etcdClient(final EtcdProperties etcdProperties) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> Client client = Client.builder()</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> .endpoints(etcdProperties.getUrl())</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> .build();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return new EtcdClient(client);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * Config event listener data changed listener.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> *</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @param etcdClient the etcd client</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @return the data changed listener</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> @Bean</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> @ConditionalOnMissingBean(EtcdDataDataChangedListener.class)</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> public DataChangedListener etcdDataChangedListener(final EtcdClient etcdClient) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return new EtcdDataDataChangedListener(etcdClient);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * data init.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> *</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @param etcdClient the etcd client</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @param syncDataService the sync data service</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @return the etcd data init</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> @Bean</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> @ConditionalOnMissingBean(EtcdDataInit.class)</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> public EtcdDataInit etcdDataInit(final EtcdClient etcdClient, final SyncDataService syncDataService) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return new EtcdDataInit(etcdClient, syncDataService);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // other code is omitted......</span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p>This configuration class is implemented through the SpringBoot conditional assembly class. The <code>EtcdListener</code> class has several annotations:</p><ul><li><p><code>@Configuration</code>: Configuration file, application context;</p></li><li><p><code>@ConditionalOnProperty(prefix = &quot;shenyu.sync.etcd&quot;, name = &quot;url&quot;)</code>: attribute condition. The configuration class takes effect only when the condition is met. That is, when we have the following configuration, <code>etcd</code> is used for data synchronization.</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI properties"><pre tabindex="0" class="prism-code language-properties codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">shenyu: </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> sync:</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> etcd:</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> url: localhost:2181</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div></li><li><p><code>@EnableConfigurationProperties(EtcdProperties.class)</code>:import <code>EtcdProperties</code>; The properties in the class <code>EtcdProperties</code> is relative to the properties which is with <code>shenyu.sync.etcd</code> as prefix in the configuration file.</p></li></ul><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain"> @Data</span></span><span class="token-line" style="color:#393A34"><span class="token plain">@ConfigurationProperties(prefix = &quot;shenyu.sync.etcd&quot;)</span></span><span class="token-line" style="color:#393A34"><span class="token plain">public class EtcdProperties {</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private String url;</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private Integer sessionTimeout;</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private Integer connectionTimeout;</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private String serializer;</span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p>When the <code>shenyu.sync.etcd.url</code> property is set in the configuration file, <code>Admin</code> would use the <code>etcd</code> data synchronization, <code>EtcdListener</code> is generated and the beans with type <code>EtcdClient</code>, <code>EtcdDataDataChangedListener</code> and <code>EtcdDataInit</code> would also be generated. </p><ul><li>The bean with the type <code>EtcdClient</code> would be generated, named <code>etcdClient</code>. This bean configues the connection properties of the <code>etcd</code> server based on the configuration file and can operate the <code>etcd</code>nodes directly.</li><li>The bean with the type <code>EtcdDataDataChangedListener</code> would be generated, named <code>etcdDataDataChangedListener</code>. This bean use the bean <code>etcdClient</code> as a member variable and so when the event is listened, <code>etcdDataDataChangedListener</code> would call the callback method and use the <code>etcdClient</code> to operate the <code>etcd</code> nodes.</li><li>The bean with the type <code>EtcdDataInit</code> would be generated, named <code>etcdDataInit</code>. This bean use the bean <code>etcdClient</code> and <code>syncDataService</code> as member variables, and use <code>etcdClient</code> to judge whether the data are initialized, if not, would use <code>syncDataService</code> to refresh data. We would dive into the details later. </li></ul><p>So in the event handler <code>onApplicationEvent()</code>, it goes to the corresponding <code>listener</code>. In our case, it is a selector data update, data synchronization is <code>etcd</code>, so, the code will enter the <code>EtcdDataDataChangedListener</code> selector data change process.</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain"> @Override</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> @SuppressWarnings(&quot;unchecked&quot;)</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> public void onApplicationEvent(final DataChangedEvent event) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // Iterate through the data change listener (usually using a data synchronization approach is fine)</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> for (DataChangedListener listener : listeners) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // what kind of data has changed</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> switch (event.getGroupKey()) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // other code logic is omitted</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> case SELECTOR: // selector data</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> listener.onSelectorChanged((List&lt;SelectorData&gt;) event.getSource(), event.getEventType()); // In our case, will enter the EtcdDataDataChangedListener selector data change process</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> break;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><h4><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="24-etcd-data-changed-listener"></a>2.4 Etcd Data Changed Listener<a class="hash-link" href="#24-etcd-data-changed-listener" title="Direct link to heading">#</a></h4><ul><li>EtcdDataDataChangedListener.onSelectorChanged()</li></ul><p>In the <code>onSelectorChanged()</code> method, determine the type of action, whether to refresh synchronization or update or create synchronization. Determine whether the node is in <code>etcd</code> based on the current selector data.</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain">/**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * EtcdDataDataChangedListener.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain">@Slf4j</span></span><span class="token-line" style="color:#393A34"><span class="token plain">public class EtcdDataDataChangedListener implements DataChangedListener {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> @Override</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> public void onSelectorChanged(final List&lt;SelectorData&gt; changed, final DataEventTypeEnum eventType) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (eventType == DataEventTypeEnum.REFRESH &amp;&amp; !changed.isEmpty()) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> String selectorParentPath = DefaultPathConstants.buildSelectorParentPath(changed.get(0).getPluginName());</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> etcdClient.deleteEtcdPathRecursive(selectorParentPath);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> for (SelectorData data : changed) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> String selectorRealPath = DefaultPathConstants.buildSelectorRealPath(data.getPluginName(), data.getId());</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (eventType == DataEventTypeEnum.DELETE) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> etcdClient.delete(selectorRealPath);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> continue;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> //create or update</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> updateNode(selectorRealPath, data);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p>This part is very important. The variable <code>changed</code> represents the <code>SelectorData</code> list, the variable <code>eventType</code> reprents the event type. When the event type is <code>REFRESH</code> and the <code>SelectorData</code> has changed, all the <code>selector</code> nodes under this <code>plugin</code> would be deleted in <code>etcd</code>. We should notice that the condition that the <code>SelectorData</code> has changed is necessary, otherwise a bug would appear that all the selector nodes would be deleted when no <code>SelectorData</code> data has changed. </p><p>As long as the changed data is correctly written to the <code>etcd</code> node, the <code>admin</code> side of the operation is complete. </p><p>In our current case, updating one of the selector data in the <code>Divide</code> plugin with a weight of 90 updates specific nodes in the graph.</p><p><img src="/assets/images/zookeeper-node-c7628b680a1f1afa0eada97b66fcd5b1.png"></p><p>We series the above update flow with a sequence diagram.</p><p><img src="/assets/images/etcd-sync-sequence-admin-en-29e7ea74b69fcc2faa148fc0459fc16d.png"></p><h3><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="3-gateway-data-sync"></a>3. Gateway Data Sync<a class="hash-link" href="#3-gateway-data-sync" title="Direct link to heading">#</a></h3><p>Assume that the ShenYu gateway is already running properly, and the data synchronization mode is also <code>etcd</code>. How does the gateway receive and process the selector data after updating it on the admin side and sending the changed data to etcd? Let&#x27;s continue our source code analysis to find out.</p><h4><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="31-etcdclient-accept-data"></a>3.1 EtcdClient Accept Data<a class="hash-link" href="#31-etcdclient-accept-data" title="Direct link to heading">#</a></h4><ul><li>EtcdClient.watchDataChange()</li></ul><p>There is a <code>EtcdSyncDataService</code> class on the gateway, which subscribing to the data node through <code>etcdClient</code> and can sense when the data changes.</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">/**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * Data synchronize of etcd.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain">@Slf4j</span></span><span class="token-line" style="color:#393A34"><span class="token plain">public class EtcdSyncDataService implements SyncDataService, AutoCloseable {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private void subscribeSelectorDataChanges(final String path) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> etcdClient.watchDataChange(path, (updateNode, updateValue) -&gt; cacheSelectorData(updateValue),</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> this::unCacheSelectorData);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> //other codes omitted</span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p>Etcd&#x27;s <code>Watch</code> mechanism notifies subscribing clients of node changes. In our case, updating the selector information goes to the <code>watchDataChange()</code> method. <code>cacheSelectorData()</code> is used to process data.</p><h4><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="32-handle-data"></a>3.2 Handle Data<a class="hash-link" href="#32-handle-data" title="Direct link to heading">#</a></h4><ul><li>EtcdSyncDataService.cacheSelectorData()</li></ul><p>The data is not null, and caching the selector data is again handled by <code>PluginDataSubscriber</code>.</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain"> private void cacheSelectorData(final SelectorData selectorData) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> Optional.ofNullable(selectorData)</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> .ifPresent(data -&gt; Optional.ofNullable(pluginDataSubscriber).ifPresent(e -&gt; e.onSelectorSubscribe(data)));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p><code>PluginDataSubscriber</code> is an interface, it is only a <code>CommonPluginDataSubscriber</code> implementation class, responsible for data processing plugin, selector and rules.</p><h4><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="33-common-plugin-data-subscriber"></a>3.3 Common Plugin Data Subscriber<a class="hash-link" href="#33-common-plugin-data-subscriber" title="Direct link to heading">#</a></h4><ul><li>PluginDataSubscriber.onSelectorSubscribe()</li></ul><p>It has no additional logic and calls the <code>subscribeDataHandler()</code> method directly. Within methods, there are data types (plugins, selectors, or rules) and action types (update or delete) to perform different logic.</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">/**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * The common plugin data subscriber, responsible for handling all plug-in, selector, and rule information</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain">public class CommonPluginDataSubscriber implements PluginDataSubscriber {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> //......</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // handle selector data</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> @Override</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> public void onSelectorSubscribe(final SelectoData selectorData) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> subscribeDataHandler(selectorData, DataEventTypeEnum.UPDATE);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> } </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // A subscription data handler that handles updates or deletions of data</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private &lt;T&gt; void subscribeDataHandler(final T classData, final DataEventTypeEnum dataType) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> Optional.ofNullable(classData).ifPresent(data -&gt; {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // plugin data</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (data instanceof PluginData) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> PluginData pluginData = (PluginData) data;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (dataType == DataEventTypeEnum.UPDATE) { // update</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // save the data to gateway memory</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> BaseDataCache.getInstance().cachePluginData(pluginData);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // If each plugin has its own processing logic, then do it</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> Optional.ofNullable(handlerMap.get(pluginData.getName())).ifPresent(handler -&gt; handler.handlerPlugin(pluginData));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> } else if (dataType == DataEventTypeEnum.DELETE) { // delete</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // delete the data from gateway memory</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> BaseDataCache.getInstance().removePluginData(pluginData);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // If each plugin has its own processing logic, then do it</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> Optional.ofNullable(handlerMap.get(pluginData.getName())).ifPresent(handler -&gt; handler.removePlugin(pluginData));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> } else if (data instanceof SelectorData) { // selector data</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> SelectorData selectorData = (SelectorData) data;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (dataType == DataEventTypeEnum.UPDATE) { // update</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // save the data to gateway memory</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> BaseDataCache.getInstance().cacheSelectData(selectorData);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // If each plugin has its own processing logic, then do it </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> Optional.ofNullable(handlerMap.get(selectorData.getPluginName())).ifPresent(handler -&gt; handler.handlerSelector(selectorData));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> } else if (dataType == DataEventTypeEnum.DELETE) { // delete</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // delete the data from gateway memory</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> BaseDataCache.getInstance().removeSelectData(selectorData);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // If each plugin has its own processing logic, then do it</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> Optional.ofNullable(handlerMap.get(selectorData.getPluginName())).ifPresent(handler -&gt; handler.removeSelector(selectorData));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> } else if (data instanceof RuleData) { // rule data</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> RuleData ruleData = (RuleData) data;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (dataType == DataEventTypeEnum.UPDATE) { // update</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // save the data to gateway memory</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> BaseDataCache.getInstance().cacheRuleData(ruleData);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // If each plugin has its own processing logic, then do it</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> Optional.ofNullable(handlerMap.get(ruleData.getPluginName())).ifPresent(handler -&gt; handler.handlerRule(ruleData));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> } else if (dataType == DataEventTypeEnum.DELETE) { // delete</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // delete the data from gateway memory</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> BaseDataCache.getInstance().removeRuleData(ruleData);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // If each plugin has its own processing logic, then do it</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> Optional.ofNullable(handlerMap.get(ruleData.getPluginName())).ifPresent(handler -&gt; handler.removeRule(ruleData));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> });</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><h4><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="34-data-cached-to-memory"></a>3.4 Data cached to Memory<a class="hash-link" href="#34-data-cached-to-memory" title="Direct link to heading">#</a></h4><p>Adding a selector will enter the following logic:</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">// save the data to gateway memory</span></span><span class="token-line" style="color:#393A34"><span class="token plain">BaseDataCache.getInstance().cacheSelectData(selectorData);</span></span><span class="token-line" style="color:#393A34"><span class="token plain">// If each plugin has its own processing logic, then do it</span></span><span class="token-line" style="color:#393A34"><span class="token plain">Optional.ofNullable(handlerMap.get(selectorData.getPluginName())).ifPresent(handler -&gt; handler.handlerSelector(selectorData));</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p>One is to save the data to the gateway&#x27;s memory. BaseDataCache is the class that ultimately caches data, implemented in a singleton pattern. The selector data is stored in the <code>SELECTOR_MAP</code> Map. In the subsequent use, also from this data.</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">public final class BaseDataCache {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // private instance</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private static final BaseDataCache INSTANCE = new BaseDataCache();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // private constructor</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private BaseDataCache() {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * Gets instance.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * public method</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @return the instance</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> public static BaseDataCache getInstance() {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return INSTANCE;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * A Map of the cache selector data</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * pluginName -&gt; SelectorData.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private static final ConcurrentMap&lt;String, List&lt;SelectorData&gt;&gt; SELECTOR_MAP = Maps.newConcurrentMap();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> public void cacheSelectData(final SelectorData selectorData) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> Optional.ofNullable(selectorData).ifPresent(this::selectorAccept);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * cache selector data.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @param data the selector data</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private void selectorAccept(final SelectorData data) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> String key = data.getPluginName();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (SELECTOR_MAP.containsKey(key)) { // Update operation, delete before insert</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> List&lt;SelectorData&gt; existList = SELECTOR_MAP.get(key);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> final List&lt;SelectorData&gt; resultList = existList.stream().filter(r -&gt; !r.getId().equals(data.getId())).collect(Collectors.toList());</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> resultList.add(data);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> final List&lt;SelectorData&gt; collect = resultList.stream().sorted(Comparator.comparing(SelectorData::getSort)).collect(Collectors.toList());</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> SELECTOR_MAP.put(key, collect);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> } else { // Add new operations directly to Map</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> SELECTOR_MAP.put(key, Lists.newArrayList(data));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p>Second, if each plugin has its own processing logic, then do it. Through the <code>IDEA</code> editor, you can see that after adding a selector, there are the following plugins and processing. We&#x27;re not going to expand it here.</p><p><img src="/assets/images/handler-selector-bf05b8fdf80a428aa53606178a42bae6.png"></p><p>After the above source tracking, and through a practical case, in the <code>admin</code> end to update a selector data, the <code>ZooKeeper</code> data synchronization process analysis is clear.</p><p>Let&#x27;s series the data synchronization process on the gateway side through the sequence diagram:</p><p><img src="/assets/images/etcd-sync-sequence-gateway-en-4da1d7160168a3ee75741e84d7298e0d.png"></p><p>The data synchronization process has been analyzed. In order to prevent the synchronization process from being interrupted, other logic is ignored during the analysis. We also need to analyze the process of Admin synchronization data initialization and gateway synchronization operation initialization.</p><h3><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="4-admin-data-sync--initialization"></a>4. Admin Data Sync initialization<a class="hash-link" href="#4-admin-data-sync--initialization" title="Direct link to heading">#</a></h3><p>When <code>admin</code> starts, the current data will be fully synchronized to <code>etcd</code>, the implementation logic is as follows:</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain">/**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * EtcdDataInit.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain">@Slf4j</span></span><span class="token-line" style="color:#393A34"><span class="token plain">public class EtcdDataInit implements CommandLineRunner {</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private final EtcdClient etcdClient;</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private final SyncDataService syncDataService;</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> public EtcdDataInit(final EtcdClient client, final SyncDataService syncDataService) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> this.etcdClient = client;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> this.syncDataService = syncDataService;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> @Override</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> public void run(final String... args) throws Exception {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> final String pluginPath = DefaultPathConstants.PLUGIN_PARENT;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> final String authPath = DefaultPathConstants.APP_AUTH_PARENT;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> final String metaDataPath = DefaultPathConstants.META_DATA;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (!etcdClient.exists(pluginPath) &amp;&amp; !etcdClient.exists(authPath) &amp;&amp; !etcdClient.exists(metaDataPath)) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> log.info(&quot;Init all data from database&quot;);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> syncDataService.syncAll(DataEventTypeEnum.REFRESH);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p>Check whether there is data in <code>etcd</code>, if not, then synchronize.</p><p><code>EtcdDataInit</code> implements the <code>CommandLineRunner</code> interface. It is an interface provided by <code>SpringBoot</code> that executes the <code>run()</code> method after all <code>Spring Beans</code> initializations and is often used for initialization operations in a project.</p><ul><li>SyncDataService.syncAll()</li></ul><p>Query data from the database, and then perform full data synchronization, all authentication information, plugin information, selector information, rule information, and metadata information. Synchronous events are published primarily through <code>eventPublisher</code>. After publishing the event via <code>publishEvent()</code>, the <code>ApplicationListener</code> performs the event change operation. In <code>ShenYu</code> is mentioned in <code>DataChangedEventDispatcher</code>.</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">@Service</span></span><span class="token-line" style="color:#393A34"><span class="token plain">public class SyncDataServiceImpl implements SyncDataService {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // eventPublisher</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private final ApplicationEventPublisher eventPublisher;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /***</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * sync all data</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @param type the type</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @return</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> @Override</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> public boolean syncAll(final DataEventTypeEnum type) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // app auth data</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> appAuthService.syncData();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // plugin data</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> List&lt;PluginData&gt; pluginDataList = pluginService.listAll();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.PLUGIN, type, pluginDataList));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // selector data</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> List&lt;SelectorData&gt; selectorDataList = selectorService.listAll();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.SELECTOR, type, selectorDataList));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // rule data</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> List&lt;RuleData&gt; ruleDataList = ruleService.listAll();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.RULE, type, ruleDataList));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // metadata</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> metaDataService.syncData();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return true;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><h3><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="5-gateway-data-sync-init"></a>5. Gateway Data Sync Init<a class="hash-link" href="#5-gateway-data-sync-init" title="Direct link to heading">#</a></h3><p>The initial operation of data synchronization on the gateway side is mainly the node in the subscription <code>etcd</code>. When there is a data change, the changed data will be received. This relies on the <code>Watch</code> mechanism of <code>etcd</code>. In <code>ShenYu</code>, the one responsible for <code>etcd</code> data synchronization is <code>EtcdSyncDataService</code>, also mentioned earlier.</p><p>The function logic of <code>EtcdSyncDataService</code> is completed in the process of instantiation: the subscription to <code>Shenyu</code> data synchronization node in <code>etcd</code> is completed. Subscription here is divided into two kinds, one kind is existing node data updated above, through this <code>etcdClient.subscribeDataChanges()</code> method; Another kind is under the current node, add or delete nodes change namely child nodes, it through <code>etcdClient.subscribeChildChanges()</code> method.</p><p><code>EtcdSyncDataService</code> code is a bit too much, here we use plugin data read and subscribe to track, other types of data operation principle is the same.</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">/**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * Data synchronize of etcd.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain">@Slf4j</span></span><span class="token-line" style="color:#393A34"><span class="token plain">public class EtcdSyncDataService implements SyncDataService, AutoCloseable {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * Instantiates a new Zookeeper cache manager.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> *</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @param etcdClient the etcd client</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @param pluginDataSubscriber the plugin data subscriber</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @param metaDataSubscribers the meta data subscribers</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @param authDataSubscribers the auth data subscribers</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> public EtcdSyncDataService(final EtcdClient etcdClient, final PluginDataSubscriber pluginDataSubscriber,</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> final List&lt;MetaDataSubscriber&gt; metaDataSubscribers, final List&lt;AuthDataSubscriber&gt; authDataSubscribers) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> this.etcdClient = etcdClient;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> this.pluginDataSubscriber = pluginDataSubscriber;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> this.metaDataSubscribers = metaDataSubscribers;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> this.authDataSubscribers = authDataSubscribers;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> watcherData();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> watchAppAuth();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> watchMetaData();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private void watcherData() {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> final String pluginParent = DefaultPathConstants.PLUGIN_PARENT;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> List&lt;String&gt; pluginZKs = etcdClientGetChildren(pluginParent);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> for (String pluginName : pluginZKs) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> watcherAll(pluginName);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> etcdClient.watchChildChange(pluginParent, (updateNode, updateValue) -&gt; {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (!updateNode.isEmpty()) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> watcherAll(updateNode);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }, null);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private void watcherAll(final String pluginName) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> watcherPlugin(pluginName);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> watcherSelector(pluginName);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> watcherRule(pluginName);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private void watcherPlugin(final String pluginName) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> String pluginPath = DefaultPathConstants.buildPluginPath(pluginName);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> cachePluginData(etcdClient.get(pluginPath));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> subscribePluginDataChanges(pluginPath, pluginName);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private void cachePluginData(final String dataString) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> final PluginData pluginData = GsonUtils.getInstance().fromJson(dataString, PluginData.class);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> Optional.ofNullable(pluginData)</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> .flatMap(data -&gt; Optional.ofNullable(pluginDataSubscriber)).ifPresent(e -&gt; e.onSubscribe(pluginData));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private void subscribePluginDataChanges(final String pluginPath, final String pluginName) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> etcdClient.watchDataChange(pluginPath, (updatePath, updateValue) -&gt; {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> final String dataPath = buildRealPath(pluginPath, updatePath);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> final String dataStr = etcdClient.get(dataPath);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> final PluginData data = GsonUtils.getInstance().fromJson(dataStr, PluginData.class);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> Optional.ofNullable(data)</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> .ifPresent(d -&gt; Optional.ofNullable(pluginDataSubscriber).ifPresent(e -&gt; e.onSubscribe(d)));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }, deleteNode -&gt; deletePlugin(pluginName));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p>The above source code is given comments, I believe you can understand. The main logic for subscribing to plug-in data is as follows:</p><blockquote><ol><li>Create the current plugin path</li><li>Read the current node data on etcd and deserialize it</li><li>The plugin data is cached in the gateway memory</li><li>Subscribe to the plug-in node</li></ol></blockquote><h3><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="6-summary"></a>6. Summary<a class="hash-link" href="#6-summary" title="Direct link to heading">#</a></h3><p>This paper through a practical case, <code>etcd</code> data synchronization principle source code analysis. The main knowledge points involved are as follows:</p><ul><li><p>Data synchronization based on <code>etcd</code> is mainly implemented through <code>watch</code> mechanism;</p></li><li><p>Complete event publishing and listening via <code>Spring</code>;</p></li><li><p>Support multiple synchronization strategies through abstract <code>DataChangedListener</code> interface, interface oriented programming;</p></li><li><p>Use singleton design pattern to cache data class <code>BaseDataCache</code>;</p></li><li><p>Loading of configuration classes via conditional assembly of <code>SpringBoot</code> and <code>starter</code> loading mechanism.</p></li></ul></div><footer class="row docusaurus-mt-lg blogPostDetailsFull_xD8n"><div class="col"><b>Tags:</b><a class="margin-horiz--sm" href="/blog/tags/etcd">etcd</a><a class="margin-horiz--sm" href="/blog/tags/data-sync">data sync</a><a class="margin-horiz--sm" href="/blog/tags/apache-shen-yu">Apache ShenYu</a></div><div class="col margin-top--sm"><a href="https://github.com/apache/shenyu-website/edit/main/blog/DataSync-SourceCode-Analysis-Etcd-Data-Sync.md" target="_blank" rel="noreferrer noopener"><svg fill="currentColor" height="20" width="20" viewBox="0 0 40 40" class="iconEdit_mS5F" aria-hidden="true"><g><path d="m34.5 11.7l-3 3.1-6.3-6.3 3.1-3q0.5-0.5 1.2-0.5t1.1 0.5l3.9 3.9q0.5 0.4 0.5 1.1t-0.5 1.2z m-29.5 17.1l18.4-18.5 6.3 6.3-18.4 18.4h-6.3v-6.2z"></path></g></svg>Edit this page</a></div></footer></article><nav class="pagination-nav docusaurus-mt-lg" aria-label="Blog post page navigation"><div class="pagination-nav__item"><a class="pagination-nav__link" href="/blog/DataSync-SourceCode-Analysis-Apollo-Data-Sync"><div class="pagination-nav__sublabel">Newer Post</div><div class="pagination-nav__label">« Apollo Data Synchronization Source Code Analysis</div></a></div><div class="pagination-nav__item pagination-nav__item--next"><a class="pagination-nav__link" href="/blog/DataSync-SourceCode-Analysis-Http-Data-Sync"><div class="pagination-nav__sublabel">Older Post</div><div class="pagination-nav__label">Http Long Polling Data Synchronization Source Code Analysis »</div></a></div></nav></main><div class="col col--2"><div class="tableOfContents_vrFS thin-scrollbar"><ul class="table-of-contents table-of-contents__left-border"><li><a href="#1-about-etcd" class="table-of-contents__link">1. About Etcd</a></li><li><a href="#2-admin-data-sync" class="table-of-contents__link">2. Admin Data Sync</a></li><li><a href="#3-gateway-data-sync" class="table-of-contents__link">3. Gateway Data Sync</a></li><li><a href="#4-admin-data-sync--initialization" class="table-of-contents__link">4. Admin Data Sync initialization</a></li><li><a href="#5-gateway-data-sync-init" class="table-of-contents__link">5. Gateway Data Sync Init</a></li><li><a href="#6-summary" class="table-of-contents__link">6. Summary</a></li></ul></div></div></div></div></div></div>
<script src="/assets/js/runtime~main.490de106.js"></script>
<script src="/assets/js/main.cb386500.js"></script>
</body>
</html>