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);