How to Stress-Test Netty WebSocket for 10,000 Subscriptions and Keepalive
This article presents advanced WebSocket testing techniques—including asynchronous connection creation, result statistics, price verification, latency measurement, TPS modeling—and demonstrates a Netty‑WebSocket subscription test for 10,000 users plus Java‑ and Netty‑based keepalive implementations for high‑concurrency scenarios.
5.4.4 Unfinished WebSocket Tasks
Additional improvements that can be added to the existing WebSocket test suite:
Asynchronous connection creation : use a thread pool to open many WebSocket connections in parallel. Each thread creates a client, calls connect(), and stores the resulting ChannelPromise (or Future) in a list. After all threads are started, a CountDownLatch (or waiting on each promise) synchronizes the main thread until every handshake is complete.
Result statistics : maintain atomic counters for successful and failed validations. After the test run, output a summary report showing total connections, success rate, and failure reasons.
Price verification : after receiving a message, parse the JSON payload, extract the price field and compare it with the expected value for the user role (VIP vs regular). Use assertions to fail the test when the difference is incorrect.
Latency measurement : record System.nanoTime() (or Instant.now()) immediately before sending a request and again when the corresponding response arrives. Store the delta in a list and compute average, min, max latency to evaluate asynchronous response time.
Event‑driven TPS model : replace the simple thread‑per‑connection approach with a transaction‑per‑second (TPS) engine that schedules connection creation and message sending based on an event loop (e.g., Netty’s EventLoopGroup). This reduces thread overhead and improves scalability under high concurrency.
5.4.5 Netty‑WebSocket Subscription Test
To verify the push capability of a server that must handle >10 000 concurrent subscribers, the following Java program creates 10 000 Netty‑based WebSocket clients, completes the handshake, and then broadcasts a subscription request to all channels.
package org.funtester.performance.books.chapter05.section4;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.funtester.performance.books.chapter05.section3.NettyWebSocketClient;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
public class SubscribeTest {
public static void main(String[] args) throws URISyntaxException, InterruptedException {
String url = "ws://localhost:12345/websocket/FunTester";
URI uri = new URI(url);
List<ChannelPromise> promises = new ArrayList<>();
// Create 10 000 clients in parallel
for (int i = 0; i < 10000; i++) {
NettyWebSocketClient client = new NettyWebSocketClient(uri);
ChannelPromise connect = client.connect(); // asynchronous handshake
promises.add(connect);
}
// Wait for all handshakes to finish
for (ChannelPromise p : promises) {
p.get(); // blocks until the handshake succeeds or fails
}
// Send a single subscription frame to all channels
NettyWebSocketClient.channels.writeAndFlush(
new TextWebSocketFrame("Subscribe: vegetable and fruit price & quantity")
).sync();
System.out.println("Subscription completed");
}
}Analysis
Netty‑WebSocket uses an event‑driven architecture with thread reuse. The test demonstrates that a single JVM can establish and maintain 10 000 connections, then push a message to all of them with a single write operation. By varying the size of the payload or the frequency of subscription messages, testers can generate additional load to observe CPU, memory, and network consumption on the server.
5.4.6 Connection Keepalive
In large‑scale subscription scenarios, idle connections are often closed by the server. A periodic heartbeat (ping) keeps the TCP session alive.
Java‑WebSocket keepalive
Each client runs a background thread that sleeps for a configurable interval (e.g., 10 seconds) and then sends a ping frame if the connection state is OPEN. The client also implements a simple reconnection strategy when the state becomes CLOSED.
package org.funtester.performance.books.chapter05.section4;
import org.funtester.performance.books.chapter05.section2.JavaWebSocketClient;
import org.java_websocket.enums.ReadyState;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
public class JavaWebSocketKeepAlive {
public static void main(String[] args) throws URISyntaxException {
String url = "ws://localhost:12345/websocket/FunTester";
URI uri = new URI(url);
List<JavaWebSocketClient> clients = new ArrayList<>();
// Heartbeat thread
new Thread(() -> {
while (true) {
try {
Thread.sleep(10_000); // 10 s interval
for (JavaWebSocketClient client : clients) {
if (client.getReadyState() == ReadyState.OPEN) {
client.sendPing();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}).start();
// Create 1 000 clients
for (int i = 0; i < 1000; i++) {
JavaWebSocketClient client = new JavaWebSocketClient(uri) {
@Override
public void onMessage(String s) {
// No processing needed for keepalive
}
};
client.connect();
clients.add(client);
}
}
}Netty‑WebSocket keepalive
Netty provides a ChannelGroup that holds all active channels. A scheduled task can iterate over the group and write a PingWebSocketFrame to every channel, eliminating the need for per‑client threads.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
