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

Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git


The following commit(s) were added to refs/heads/master by this push:
     new cdfff920f8 fix: skip re-applying unchanged websocket config payloads 
(#6587) (#7528)
cdfff920f8 is described below

commit cdfff920f883cf1feb5a7c8a525e574b4d1c9e74
Author: wy471x <[email protected]>
AuthorDate: Sun Oct 11 22:35:07 2026 +0800

    fix: skip re-applying unchanged websocket config payloads (#6587) (#7528)
---
 .../websocket/handler/WebsocketDataHandler.java    | 28 ++++++++++++
 .../handler/WebsocketDataHandlerTest.java          | 51 +++++++++++++++++++++-
 2 files changed, 78 insertions(+), 1 deletion(-)

diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/handler/WebsocketDataHandler.java
 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/handler/WebsocketDataHandler.java
index 2acf0fa3cb..d23638f4f5 100644
--- 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/handler/WebsocketDataHandler.java
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/handler/WebsocketDataHandler.java
@@ -19,21 +19,36 @@ package 
org.apache.shenyu.plugin.sync.data.websocket.handler;
 
 import java.util.EnumMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
 import org.apache.shenyu.common.enums.ConfigGroupEnum;
+import org.apache.shenyu.common.utils.DigestUtils;
 import org.apache.shenyu.sync.data.api.AuthDataSubscriber;
 import org.apache.shenyu.sync.data.api.DiscoveryUpstreamDataSubscriber;
 import org.apache.shenyu.sync.data.api.MetaDataSubscriber;
 import org.apache.shenyu.sync.data.api.PluginDataSubscriber;
 import org.apache.shenyu.sync.data.api.ProxySelectorDataSubscriber;
 import org.apache.shenyu.sync.data.api.AiProxyApiKeyDataSubscriber;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * The type Websocket cache handler.
  */
 public class WebsocketDataHandler {
 
+    private static final Logger LOG = 
LoggerFactory.getLogger(WebsocketDataHandler.class);
+
     private final EnumMap<ConfigGroupEnum, DataHandler> handlers = new 
EnumMap<>(ConfigGroupEnum.class);
 
+    /**
+     * Fingerprint of the payload that was applied last for each group. Admin 
re-sends the whole
+     * group payload on every change of that group, so an unchanged payload 
means the gateway has
+     * already applied exactly this state and re-caching it only burns CPU on 
the sync thread.
+     */
+    private final Map<ConfigGroupEnum, String> lastAppliedFingerprints = new 
ConcurrentHashMap<>();
+
     /**
      * Instantiates a new Websocket data handler.
      *
@@ -65,7 +80,17 @@ public class WebsocketDataHandler {
      * @param eventType the event type
      */
     public void executor(final ConfigGroupEnum type, final String json, final 
String eventType) {
+        final String fingerprint = fingerprintOf(json, eventType);
+        if (Objects.equals(fingerprint, lastAppliedFingerprints.get(type))) {
+            LOG.info("ignore duplicated {} event of group {}, this payload has 
already been applied", eventType, type);
+            return;
+        }
         handlers.get(type).handle(json, eventType);
+        lastAppliedFingerprints.put(type, fingerprint);
+    }
+
+    private static String fingerprintOf(final String json, final String 
eventType) {
+        return DigestUtils.md5Hex(String.join("|", String.valueOf(eventType), 
String.valueOf(json)));
     }
 
     /**
@@ -82,6 +107,9 @@ public class WebsocketDataHandler {
             throw new IllegalArgumentException("Snapshot namespace does not 
match the connection");
         }
         ((AbstractDataHandler<?>) handlers.get(type)).handleSnapshot(json, 
snapshotNamespace);
+        // a snapshot replaces the whole group, so the next payload must be 
applied even if it
+        // happens to be identical to the payload applied before the snapshot
+        lastAppliedFingerprints.remove(type);
     }
 
 }
diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/handler/WebsocketDataHandlerTest.java
 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/handler/WebsocketDataHandlerTest.java
index 9dd8efaec6..1b69ddef9f 100644
--- 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/handler/WebsocketDataHandlerTest.java
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/handler/WebsocketDataHandlerTest.java
@@ -100,6 +100,51 @@ public final class WebsocketDataHandlerTest {
         pluginDataList.forEach(verify(pluginDataSubscriber)::onSubscribe);
     }
 
+    @Test
+    public void testDuplicatedEventIsAppliedOnlyOnce() {
+        String json = getJson();
+        websocketDataHandler.executor(ConfigGroupEnum.PLUGIN, json, 
DataEventTypeEnum.REFRESH.name());
+        websocketDataHandler.executor(ConfigGroupEnum.PLUGIN, json, 
DataEventTypeEnum.REFRESH.name());
+        Mockito.verify(pluginDataSubscriber, 
Mockito.times(1)).refreshPluginDataAll();
+        Mockito.verify(pluginDataSubscriber, 
Mockito.times(1)).onPluginRefresh(Mockito.anyList());
+    }
+
+    @Test
+    public void testChangedPayloadIsAppliedAgain() {
+        websocketDataHandler.executor(ConfigGroupEnum.PLUGIN, 
getJson("plugin_test"), DataEventTypeEnum.REFRESH.name());
+        websocketDataHandler.executor(ConfigGroupEnum.PLUGIN, 
getJson("plugin_test_2"), DataEventTypeEnum.REFRESH.name());
+        Mockito.verify(pluginDataSubscriber, 
Mockito.times(2)).refreshPluginDataAll();
+    }
+
+    @Test
+    public void testSamePayloadWithAnotherEventTypeIsApplied() {
+        String json = getJson();
+        websocketDataHandler.executor(ConfigGroupEnum.PLUGIN, json, 
DataEventTypeEnum.REFRESH.name());
+        websocketDataHandler.executor(ConfigGroupEnum.PLUGIN, json, 
DataEventTypeEnum.UPDATE.name());
+        Mockito.verify(pluginDataSubscriber).refreshPluginDataAll();
+        List<PluginData> pluginDataList = new 
PluginDataHandler(pluginDataSubscriber).convert(json);
+        pluginDataList.forEach(verify(pluginDataSubscriber)::onSubscribe);
+    }
+
+    @Test
+    public void testSnapshotInvalidatesDeduplication() {
+        String json = getJson();
+        websocketDataHandler.executor(ConfigGroupEnum.PLUGIN, json, 
DataEventTypeEnum.REFRESH.name());
+        websocketDataHandler.snapshot(ConfigGroupEnum.PLUGIN, "[]", 
"namespace-a", "namespace-a");
+        websocketDataHandler.executor(ConfigGroupEnum.PLUGIN, json, 
DataEventTypeEnum.REFRESH.name());
+        Mockito.verify(pluginDataSubscriber, 
Mockito.times(2)).refreshPluginDataAll();
+        
Mockito.verify(pluginDataSubscriber).refreshPluginDataNamespace("namespace-a");
+    }
+
+    @Test
+    public void testDeduplicationIsPerGroup() {
+        websocketDataHandler.executor(ConfigGroupEnum.PLUGIN, "[]", 
DataEventTypeEnum.REFRESH.name());
+        websocketDataHandler.executor(ConfigGroupEnum.PLUGIN, "[]", 
DataEventTypeEnum.REFRESH.name());
+        websocketDataHandler.executor(ConfigGroupEnum.SELECTOR, "[]", 
DataEventTypeEnum.REFRESH.name());
+        Mockito.verify(pluginDataSubscriber, 
Mockito.times(1)).refreshPluginDataAll();
+        Mockito.verify(pluginDataSubscriber, 
Mockito.times(1)).refreshSelectorDataAll();
+    }
+
     @Test
     public void testPluginDeleteExecutor() {
         String json = getJson();
@@ -238,9 +283,13 @@ public final class WebsocketDataHandlerTest {
     }
 
     private String getJson() {
+        return getJson("plugin_test");
+    }
+
+    private String getJson(final String pluginName) {
         PluginData pluginData = new PluginData();
         pluginData.setId("1397952341475799040");
-        pluginData.setName("plugin_test");
+        pluginData.setName(pluginName);
         pluginData.setConfig("config_test");
         pluginData.setNamespaceId("namespace-a");
         pluginData.setEnabled(true);

Reply via email to