Нагрузочное тестирование позволяет определить пределы производительности приложения, использующего STOMP.js, а также выявить проблемы масштабирования, утечки памяти, задержки доставки сообщений и деградацию WebSocket-соединений при высокой интенсивности обмена данными.
В системах реального времени нагрузка на STOMP-клиент возникает в нескольких сценариях:
Нагрузочное тестирование особенно важно для:
Проверка поведения системы при экстремальной нагрузке.
Цели:
Пример:
10000 сообщений/секунда
5000 активных подписчиков
1000 параллельных подключений
Проверка реакции на резкий скачок нагрузки.
Типичный сценарий:
100 клиентов → 5000 клиентов за 5 секунд
Проблемы, которые выявляются:
Длительное тестирование под постоянной нагрузкой.
Продолжительность:
6 часов
12 часов
24 часа
72 часа
Цели:
Тестирование больших объёмов данных.
Проверяются:
Типичная схема включает:
Load Generator
↓
STOMP.js Clients
↓
WebSocket
↓
STOMP Broker
↓
Consumers / Queues
В роли генераторов нагрузки могут использоваться:
import { Client } fr om '@stomp/stompjs';
import WebSocket from 'ws';
global.WebSocket = WebSocket;
const client = new Client({
brokerURL: 'ws://localhost:15674/ws',
reconnectDelay: 0
});
client.onConn ect = () => {
console.log('connected');
};
client.activate();
import { Client } from '@stomp/stompjs';
import WebSocket from 'ws';
global.WebSocket = WebSocket;
const clients = [];
for (let i = 0; i < 1000; i++) {
const client = new Client({
brokerURL: 'ws://localhost:15674/ws',
reconnectDelay: 0
});
client.onConn ect = () => {
console.log(`Client ${i} connected`);
};
client.activate();
clients.push(client);
}
При большом количестве клиентов появляются:
Linux:
ulimit -n 100000
Признаки:
Некоторые брокеры ограничивают:
let sent = 0;
setInterval(() => {
client.publish({
destination: '/queue/load',
body: JSON.stringify({
index: sent,
timestamp: Date.now()
})
});
sent++;
if (sent % 1000 === 0) {
console.log(`Sent: ${sent}`);
}
}, 1);
Симптомы:
Частое создание объектов:
{
timestamp: Date.now(),
payload: hugeObject
}
создаёт:
Плохо:
setInterval(() => {
client.publish({
destination: '/queue/test',
body: JSON.stringify({
time: Date.now()
})
});
}, 1);
Лучше:
const payload = {
time: 0
};
setInterval(() => {
payload.time = Date.now();
client.publish({
destination: '/queue/test',
body: JSON.stringify(payload)
});
}, 1);
const messages = [];
for (let i = 0; i < 100; i++) {
messages.push({
id: i,
value: Math.random()
});
}
client.publish({
destination: '/queue/batch',
body: JSON.stringify(messages)
});
for (let i = 0; i < 5000; i++) {
client.subscribe(`/topic/channel-${i}`, () => {});
}
Каждая подписка хранит:
Брокер начинает тратить больше времени на:
Количество сообщений в секунду.
Формула:
Throughput =
Пример:
const start = Date.now();
let messages = 0;
client.subscribe('/topic/load', () => {
messages++;
const elapsed = (Date.now() - start) / 1000;
console.log(messages / elapsed);
});
Задержка между отправкой и получением.
Формула:
Latency = T_{receive} - T_{send}
Пример:
client.subscribe('/topic/test', message => {
const data = JSON.parse(message.body);
const latency = Date.now() - data.timestamp;
console.log(`Latency: ${latency} ms`);
});
Средние значения часто бесполезны.
Пример:
Average: 20ms
P95: 400ms
P99: 3000ms
Это означает наличие серьёзных задержек у части сообщений.
setInterval(() => {
const memory = process.memoryUsage();
console.log({
rss: memory.rss,
heapUsed: memory.heapUsed,
heapTotal: memory.heapTotal
});
}, 5000);
100 MB
150 MB
220 MB
400 MB
без снижения после GC.
Плохо:
client.subscribe('/topic/test', callback);
Лучше:
const subscription =
client.subscribe('/topic/test', callback);
subscription.unsubscribe();
setInterval(() => {
reconnect();
}, 1000);
без очистки:
clearInterval(timer);
Причины:
Запуск:
node --inspect load-test.js
или:
node --prof load-test.js
client.webSocket.close();
const client = new Client({
brokerURL: 'ws://localhost:15674/ws',
reconnectDelay: 1000
});
Проблема возникает, когда тысячи клиентов переподключаются одновременно.
Последствия:
Правильный подход:
const reconnectDelay =
Math.random() * 5000;
const client = new Client({
brokerURL: 'ws://localhost:15674/ws',
reconnectDelay
});
const client = new Client({
brokerURL: 'ws://localhost:15674/ws',
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000
});
Под высокой нагрузкой:
client.debug = str => {
if (str.includes('PING') ||
str.includes('PONG')) {
console.log(str);
}
};
В RabbitMQ используется плагин Web STOMP:
rabbitmq_web_stomp
Под высокой нагрузкой возможны:
rabbitmqctl list_queues
Ключевые показатели:
Apache ActiveMQ активно использует:
Если подписчик не успевает:
Producer > Broker > Consumer
начинается накопление сообщений.
Брокер может блокировать publisher:
Usage Manager Memory Lim it reached
Браузеры имеют:
Проблемы:
Популярный инструмент для WebSocket-нагрузки.
Конфигурация:
config:
target: "ws://localhost:15674/ws"
scenarios:
- engine: ws
flow:
- send: "CONNECT"
Поддерживает WebSocket-нагрузку.
Пример:
import ws from 'k6/ws';
export default function () {
ws.connect(
'ws://localhost:15674/ws',
{},
socket => {
socket.send('CONNECT');
}
);
}
Используется для HTTP-нагрузки вокруг STOMP-инфраструктуры.
На стороне клиента анализируются:
На стороне брокера:
Особенно тяжела при:
permessage-deflate способен:
Опасный код:
while (true) {}
или:
heavySyncOperation();
приводит к:
Вместо:
1 process × 100000 clients
лучше:
10 processes × 10000 clients
import {
Worker
} from 'worker_threads';
позволяют:
import cluster from 'cluster';
используется для:
Publisher:
let sent = 0;
Subscriber:
let received = 0;
console.log({
sent,
received,
lost: sent - received
});
client.publish({
destination: '/topic/order',
body: JSON.stringify({
seq: counter++
})
});
let previous = -1;
client.subscribe('/topic/order', msg => {
const current =
JSON.parse(msg.body).seq;
if (current <= previous) {
console.log('Order violation');
}
previous = current;
});
Проверяется:
Проверяется:
Проверяется:
Нагрузочные тесты могут запускаться:
Позволяет обнаружить:
client.deactivate();
не вызывается после тестов.
Проблема:
1 publisher
0 subscribers
не отражает production.
В production влияют:
Реалистичная модель нагрузки должна учитывать: