Всем привет! Это вторая статья по системе мониторинга приложений от компании bitDive. В данной статье мы расскажем, как мы разрабатывали библиотеку, которая интегрируется в клиентские приложения и передаёт события на сервер мониторинга. Основная цель проекта — обработка миллионов сообщений в секунду с минимальным влиянием на производительность приложений клиентов.
1. Постановка задачи
Вводные данные
Перед нашей командой стояла задача:
-
Обработка 2 миллионов сообщений в секунду.
-
Минимизация использования CPU.
-
Минимизация использования оперативной памяти.
Иными словами, мы искали подход, который обеспечивал бы высокую производительность и низкое потребление ресурсов.
2. Попытка использовать Apache Kafka
Почему Kafka?
Apache Kafka — популярный брокер сообщений, известный своей производительностью и надежностью. Мы предположили, что его архитектура идеально подойдёт для нашей задачи.
Проблемы с Kafka
Однако после начала тестирования мы столкнулись с рядом ограничений:
-
Работа через сокеты.
Kafka работает с использованием TCP-сокетов:private PlaintextTransportLayer(SelectionKey key) throws IOException { super(key); SocketChannel socketChannel = (SocketChannel) key.channel(); socketChannel.configureBlocking(false); }Это создает сложности при интеграции с прокси-серверами. Единственное решение — использование JVM-параметров, что нас не устраивало.
-
Высокое потребление памяти.
Для гарантии доставки сообщений Kafka использует промежуточное хранение, что значительно увеличивает использование оперативной памяти. -
Сложности администрирования.
Чтобы обеспечить отказоустойчивость, клиенту необходимо вручную развертывать дополнительные ноды, что требует дополнительных ресурсов.
Вывод: Kafka не подошла из-за её требований к ресурсам и сложности настройки.
3. Асинхронная отправка через Spring WebFlux
Следующим шагом мы попробовали использовать Spring WebFlux, который позволяет отправлять сообщения асинхронно.
Реализация
Для отправки сообщений мы использовали следующий код:
import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; public class WebFluxSender { private final WebClient webClient; public WebFluxSender(String serverUrl) { this.webClient = WebClient.create(serverUrl); } public Mono<Void> sendMessage(String message) { return webClient.post() .uri("/api/messages") .bodyValue(message) .retrieve() .bodyToMono(Void.class); } }
Проблемы с WebFlux
-
Буферизация данных. Сообщения сохранялись в памяти перед отправкой, что увеличивало потребление ОЗУ.
-
Высокая нагрузка на CPU. Асинхронная обработка потоков вызывала значительное увеличение нагрузки на процессор.
Вывод: WebFlux улучшил ситуацию с прокси-серверами, но его использование оказалось неэффективным при больших объемах данных.
4. Оптимальное решение: файловая система
После всех экспериментов мы пришли к простому, но эффективному решению: запись сообщений в файлы с последующей отправкой пакетами.
Как это работает?
-
Запись данных в файлы.
Каждое сообщение записывается в файл сразу после его генерации:import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; public class FileMessageWriter { private BufferedWriter writer; public FileMessageWriter(String fileName) throws IOException { writer = new BufferedWriter(new FileWriter(fileName, true)); } public synchronized void writeMessage(String message) throws IOException { writer.write(message); writer.newLine(); } public void close() throws IOException { if (writer != null) { writer.close(); } } } -
Пакетная отправка файлов.
Файлы отправляются на сервер каждые 10 секунд:import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import java.io.File; public class FileSender { public void sendFile(File file, String serverUrl) throws Exception { try (CloseableHttpClient client = HttpClients.createDefault()) { HttpPost post = new HttpPost(serverUrl + "/upload"); post.setEntity(MultipartEntityBuilder.create() .addBinaryBody("file", file) .build()); try (CloseableHttpResponse response = client.execute(post)) { System.out.println("Response: " + response.getStatusLine()); } } } } -
Шифрование и подпись.
Перед отправкой файлы шифруются и подписываются для обеспечения безопасности.
Результаты
-
CPU: 20% загрузки.
-
Память: 10% использования.
-
Пропускная способность: 300 000 сообщений/сек.
5. Сравнение подходов: иллюстрации
Использование CPU
Использование памяти
Пропускная способность
6. Заключение
Использование файловой системы для хранения и передачи сообщений оказалось оптимальным решением для нашей задачи. Этот подход минимизировал использование ресурсов клиента и обеспечил высокую производительность.
Ключевые преимущества:
-
Низкое потребление CPU и памяти.
-
Простота интеграции.
-
Высокая безопасность.
Мы надеемся, что наш опыт будет полезен другим командам, которые работают над подобными задачами. Если у вас есть вопросы или идеи, будем рады их обсудить!
ссылка на оригинал статьи https://habr.com/ru/articles/874694/
Добавить комментарий