
Простой PoC для демонстрации состояний гонки в Websockets
В области информационных технологий обеспечение надёжности и целостности данных имеет решающее значение, особенно когда речь идёт о миллионах пользователей и терабайтах данных. Однако по мере усложнения программных систем могут возникать такие проблемы, как состояния гонки, которые существенно влияют на работу системы и приводят к непредсказуемым результатам.
Состояния гонки — это ошибки, возникающие в многозадачных программах, когда два или более потоков или процессов пытаются одновременно изменить общие данные или ресурсы без синхронизации. Это может привести к неожиданным и непредсказуемым результатам, поскольку порядок выполнения операций зависит от того, какие потоки или процессы завершатся первыми. Обнаружение состояний гонки — сложная задача в программных системах из-за их непредсказуемой природы.
Хотя в интернете доступно множество ресурсов о классических состояниях гонки, эта статья исследует, могут ли состояния гонки возникать в WebSockets.
WebSockets — это передовая технология, которая значительно улучшает взаимодействие в веб-приложениях, обеспечивая открытое двунаправленное соединение между веб-браузером пользователя и веб-сервером. Это бесшовное соединение позволяет обмениваться данными без необходимости постоянно инициировать новые HTTP-запросы, что делает их идеальными для создания интерактивных приложений.
Для демонстрации концепции эта статья включает Java-код, представляющий WebSocket-сервер, который взаимодействует с базой данных PostgreSQL. Сервер использует библиотеку Java-WebSocket для обработки WebSocket-соединений и выполняет следующие задачи:
После запуска программы Java-код подключается к базе данных и проверяет, существует ли таблица "example". Если она не существует, он создаёт таблицу и вставляет случайные данные:
Наиболее интересный код находится в функции "onMessage".
public static int a = 0;
@Override
public void onMessage(WebSocket conn, String message) {
if (a == 0) {
try {
// some activity with db
int rowCount = getCountFromExampleTable();
} catch (SQLException e) {
System.out.println("Error executing query: " + e.getMessage());
}
conn.send("Echo: " + message);
a = a + 1;
System.out.println(a);
}
}
Есть глобальная переменная "a", инициализированная значением 0. Когда клиент подключается к серверу и отправляет сообщение, проверяется условие "id == 0", указывающее, была ли эта функция уже выполнена. Если нет, выполняется простая SQL-команда для выборки количества строк из таблицы "example". Затем "a" увеличивается на 1, и её значение выводится. И теоретически функция не должна выполняться 2 раза.
Что касается клиента, было создано два типа клиентов: "WebSocketParallel_Success"
package io.redrays.ws.concept.client;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class WebSocketParallel_Success {
public static void main(String[] args) {
// Define the WebSocket server URI
String serverUri = "ws://127.0.0.1:8080";
// Number of WebSocket clients to create
int numClients = 100;
// Create an ExecutorService to manage multiple WebSocket client threads
ExecutorService executor = Executors.newFixedThreadPool(numClients);
// Create a list to store WebSocket client instances
List<WebSocketClient> clients = new ArrayList<>();
// Loop to create and configure multiple WebSocket clients
for (int i = 0; i < numClients; i++) {
int clientId = i + 1;
try {
// Create a WebSocket client for each connection
WebSocketClient webSocketClient = new WebSocketClient(new URI(serverUri)) {
@Override
public void onOpen(ServerHandshake handshakedata) {
// Handle WebSocket connection opened event
System.out.println("Client " + clientId + " connected to the WebSocket server");
this.send("Hello, WebSocket server! From client " + clientId);
}
@Override
public void onMessage(String message) {
// Handle incoming WebSocket messages
System.out.println("Client " + clientId + " received message: " + message);
}
@Override
public void onClose(int code, String reason, boolean remote) {
// Handle WebSocket connection closed event
System.out.println("Client " + clientId + " connection closed: " + reason);
}
@Override
public void onError(Exception ex) {
// Handle WebSocket error
System.out.println("Client " + clientId + " error occurred: " + ex.getMessage());
}
};
// Add the WebSocket client to the list
clients.add(webSocketClient);
// Connect the WebSocket client in a separate thread
executor.submit(webSocketClient::connect);
} catch (URISyntaxException e) {
System.out.println("Invalid WebSocket server URI: " + e.getMessage());
}
}
// Shutdown the executor after all tasks are submitted
executor.shutdown();
// Wait for all WebSocket client threads to complete
try {
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
System.out.println("Interrupted while waiting for tasks to complete: " + e.getMessage());
}
}
}
и "WebSocketParallel_Failed".
package io.redrays.ws.concept.client;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class WebSocketParallel_Failed {
public static void main(String[] args) {
String serverUri = "ws://127.0.0.1:8080"; // WebSocket server URI
int numParallelRequests = 285; // Number of parallel WebSocket requests
try {
WebSocketClient webSocketClient = new WebSocketClient(new URI(serverUri)) {