hanzhe-one opened a new pull request, #7865:
URL: https://github.com/apache/shenyu/pull/7865

   Fixes #7838.
   
   ## Problem
   
   Two configuration envelopes embed an already serialized JSON message as a 
string, so the configuration is escaped a second time:
   
   1. `WebsocketSyncFrame.payload` is a `String`; `WebsocketCollector` 
serializes the complete configuration message and nests it inside the frame 
during opt-in initial sync.
   2. `ClusterDataChangedEventPayload.source` is a `String`; 
`ClusterDataChangedEventForwarder` serializes `event.getSource()` before 
`RestTemplate` serializes the envelope.
   
   Escaping costs wire bytes and an extra serialize/parse pass on both sides. 
It is worst exactly where it matters: `RuleData.handle` and 
`SelectorData.handle` usually contain JSON themselves, so those characters are 
escaped twice.
   
   ## Impact
   
   - WebSocket initial sync (gateway ← admin) and cluster event forwarding 
(non-master admin → master) only.
   - No protocol change for peers that do not opt in.
   - No new dependency; no change to the internal `DataChangedEvent` source.
   
   ## Changes
   
   **WebSocket initial sync**
   
   - New `WebsocketSyncFrameV2` (`INITIAL_SYNC_V2`) whose `payload` is a 
`JsonElement`.
   - The gateway sends `MYSELF_SYNC_V2` before `MYSELF_V1:<uuid>`. Only 
sessions that announced the capability receive V2 frames; old admin nodes 
ignore the unknown message and answer with V1, which the gateway still handles.
   - `WebsocketCollector.sendConfiguration(namespaceId, data, type)` keeps the 
typed message until the frame is actually built, and uses `Gson.toJsonTree` so 
the structured payload is byte-identical in content to the previous string.
   - The per-frame `eventType` probe on the gateway now uses a streaming 
`JsonReader` instead of materializing every frame as a `Map`.
   
   **Cluster forwarding**
   
   - `ClusterDataChangedEventPayload` gains `payloadVersion` and 
`structuredSource` (Jackson `JsonNode`); `source` is still accepted.
   - The forwarder sends v2 by default. On 400/415 it retries once with the 
legacy string envelope and remembers the decision per master address until 
restart. Transport failures are not retried.
   - The controller accepts both versions, rejects a payload that carries both 
representations or an unknown version, and deserializes the structured array 
through the injected `ObjectMapper`.
   - Jackson and Gson are not assumed interchangeable: the forwarder mapper is 
set to `NON_NULL` to match Gson's output, and a test compares the structured 
tree with the Gson string for all eight `ConfigGroupEnum` values.
   
   ## Compatibility
   
   - `requestId`, sequence validation, the 60 s attempt deadline, connection 
ownership, ordered delivery through the per-session queue, and retry behavior 
are unchanged.
   - A null payload still marks the end of an attempt and stays distinct from 
an empty configuration snapshot (`data: []` in a non-null payload).
   - Namespace grouping, event type, group key, cluster authentication, and 
after-commit dispatch are unchanged.
   - Mixed versions work in both directions:
     - new gateway + old admin → admin answers V1;
     - old gateway + new admin → no capability, admin sends V1;
     - new gateway + new admin → V2;
     - new follower + old master → first forward falls back to legacy and stays 
there;
     - old follower + new master → legacy payload, still accepted.
   
   ## Measurement
   
   Standalone harness, 300 divide rules, warm JVM, `ThreadMXBean` allocation, 
repeated runs. Frame overhead:
   
   | Payload shape | Legacy wire / gzip | Structured wire / gzip | Raw saving |
   | --- | --- | --- | --- |
   | plain handles | 208,741 / 17,762 B | 180,827 / 17,754 B | 13.4% |
   | escaped nested handles | 276,841 / 18,402 B | 234,527 / 18,314 B | 15.3% |
   
   - Gzip savings are only 0.05%–0.5%: escaped bytes compress well. ShenYu does 
not enable WebSocket compression today, so the raw saving is the one currently 
realized.
   - Encode: 4.0–6.0 ms/op, 1.56–1.68 MiB/op → 3.3–3.9 ms/op, 1.42–1.47 MiB/op.
   - Decode + config re-serialization: 10.1–13.0 ms/op → 8.9–10.0 ms/op.
   - Counter-case worth noting: parsing the serialized message first and then 
embedding the tree is about twice as slow as the legacy path. The 
implementation therefore goes straight from the typed object to a tree.
   
   These are single-threaded micro-benchmarks, not an end-to-end gateway load 
test; no fixed QPS gain is claimed.
   
   ## Validation
   
   - `shenyu-admin`: 99 targeted tests pass — `WebsocketCollectorTest` (42), 
`WebsocketDataChangedListenerTest` (18), `ClusterDataChangedEventForwarderTest` 
(10), `ClusterDataChangedEventControllerTest` (10), 
`DataChangedEventDispatcherTest` (19).
   - `shenyu-sync-data-websocket`: 122 tests pass (all tests except the 
real-socket `InitialSyncConnectionTest`, see below).
   - `checkstyle:check` passes for `shenyu-common`, 
`shenyu-sync-data-websocket`, and `shenyu-admin`.
   - New tests cover: V2 frames contain no `\"groupType\"` escaping; the 
capability is dropped on close; non-capable sessions still receive V1; 
completion is still a null payload; requestId/sequence/attempt-timeout 
semantics for V2; structured and legacy sources describe identical records for 
all groups; every group's structured source maps back to its typed class; 400 
triggers exactly one legacy retry and later forwards stay legacy; transport 
failures are not retried.
   
   ## Non-blocking note
   
   `InitialSyncConnectionTest` (a real WebSocket server over a socket) cannot 
run on this Windows machine: any `Selector.open()` fails with `IOException: 
Unable to establish loopback connection`, reproduced by a three-line standalone 
program. CI runs it; the same V2 acceptance and completion semantics are 
covered locally by `InitialSyncStateTest` (17 tests).
   
   ## Out of scope
   
   - Enabling `permessage-deflate`: compression would shrink both envelopes and 
mostly erase the byte saving, so it needs its own compatibility and CPU 
comparison.
   - The `source` field is intentionally kept in the DTO so old senders keep 
working; it can be removed only after a full major-version rollout.
   - #7716 (instance-info report encoding) and #7832 (gateway payload fields) 
are separate; their benchmarks and rollout decisions are untouched.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to