111 lines
10 KiB
Markdown
111 lines
10 KiB
Markdown
## Высоконагруженный STUN-сервер на Go
|
||
Данный проект представляет собой сверхбыстрый, отказоустойчивый и оптимизированный
|
||
под экстремальные нагрузки STUN-сервер (Session Traversal Utilities for NAT),
|
||
написанный на языке Go (Golang). Сервер разработан в соответствии со стандартами
|
||
RFC 5389 и RFC 8489 и предназначен для использования в инфраструктурах реального
|
||
времени (WebRTC, VoIP, SIP, онлайн-игры) с целью определения внешних IP-адресов
|
||
и портов клиентов, находящихся за NAT.
|
||
Проект полностью контейнеризирован с помощью Docker и Docker Compose, а также
|
||
защищен на уровне ядра ОС Linux, что позволяет ему обрабатывать тысячи запросов
|
||
в секунду (RPS) на минимальных серверных мощностях.
|
||
------------------------------
|
||
## 🚀 Архитектурные особенности и ключевые фишки
|
||
Архитектура проекта сфокусирована на достижении максимальной пропускной способности
|
||
и минимального времени отклика (в тестах задержка составляет всего 20–30 мс):
|
||
|
||
1. Концепция True Zero-Alloc (Нулевое выделение памяти в куче):
|
||
* Вместо аллокации нового буфера под каждый входящий UDP-пакет, каждый независимый
|
||
* воркер создает фиксированный срез байт (buf := make([]byte, 1500)) единожды
|
||
* перед запуском бесконечного цикла.
|
||
* Ответ клиенту формируется в том же самом массиве данных поверх исходного
|
||
запроса (buf = buf[:cap(buf)]).
|
||
* Сборщик мусора Go (Garbage Collector) вообще не нагружается во время
|
||
обработки сетевого флуда. Это исключает микропаузы (STW) сервера под нагрузкой.
|
||
2. Использование современного пакета net/netip:
|
||
* Взамен старого типа net.IP (который является срезом и всегда аллоцируется в куче),
|
||
в коде применен пакет net/netip.
|
||
* Объект netip.AddrPort хранит данные в виде легковесной структуры фиксированного
|
||
размера, которая обрабатывается исключительно на уровне быстрого стека процессора.
|
||
3. Многопоточный пул воркеров (Worker Pool):
|
||
* Сервер автоматически определяет количество доступных логических ядер процессора
|
||
(runtime.NumCPU()) и запускает строго эквивалентное количество горутин-воркеров.
|
||
* Ядро Linux равномерно распределяет сетевые прерывания от UDP-сокета,
|
||
утилизируя процессор на все 100%.
|
||
4. Мягкое завершение работы (Graceful Shutdown):
|
||
* Сервер перехватывает системные сигналы остановки от операционной системы
|
||
или Docker-демона (SIGINT, SIGTERM).
|
||
* При получении команды на выключение сокет закрывается, воркеры корректно
|
||
дорабатывают текущую итерацию, а sync.WaitGroup гарантирует завершение процессов
|
||
без потери обрабатываемых пакетов.
|
||
|
||
------------------------------
|
||
## 🐳 Контейнеризация и Безопасность в Docker
|
||
Проект поставляется со специализированной конфигурацией развертывания,
|
||
обеспечивающей максимальную производительность и изоляцию:
|
||
|
||
* Многоэтапная сборка (Multi-stage Build): Образ собирается в официальном
|
||
контейнере golang:alpine, где происходит компиляция бинарника со флагами
|
||
-ldflags="-s -w" (вырезаются отладочные символы, что уменьшает размер файла
|
||
в 2 раза).
|
||
* Супер-легковесный и безопасный финальный слой (scratch): Финальный образ
|
||
собирается на базе абсолютно пустого слоя FROM scratch. Внутри контейнера
|
||
нет Linux-дистрибутива, нет пакетных менеджеров, утилит (sh, bash, ls)
|
||
и зависимостей. Вес готового Docker-образа составляет всего ~15-20 МБ.
|
||
* Принцип Non-Root: Внутри контейнера процесс STUN-сервера запускается
|
||
от имени непривилегированного системного пользователя stunuser.
|
||
В случае гипотетической уязвимости в коде, злоумышленник не сможет выйти на
|
||
хост-систему или получить права суперпользователя.
|
||
* Сетевой режим network_mode: "host": Контейнер подключен к сетевой карте
|
||
VPS напрямую. Это исключает прослойку docker-proxy и встроенную маршрутизацию
|
||
Docker, которая является главным «узким горлышком» для UDP-трафика и приводит
|
||
к потере пакетов под нагрузкой.
|
||
|
||
------------------------------
|
||
## 🛠️ Системный тюнинг ОС Linux под Highload UDP
|
||
По умолчанию буферы сетевых сокетов в Linux настроены на низкую нагрузку.
|
||
При шторме UDP-запросов буфер быстро переполняется, и ядро ОС начинает молча
|
||
дропать (терять) пакеты еще до того, как Go-приложение успеет их прочитать.
|
||
Для решения этой проблемы в проекте применяется сетевой тюнинг ядра через
|
||
конфигурацию sysctl. Максимальные и дефолтные буферы приема (rmem) и отправки
|
||
(wmem) сетевых сокетов расширяются до 25 Мегабайт (26214400 байт):
|
||
|
||
* net.core.rmem_max=26214400
|
||
* net.core.wmem_max=26214400
|
||
|
||
Эти настройки жестко фиксируются в системном файле /etc/sysctl.conf хост-машины,
|
||
благодаря чему сервер гарантированно выдерживает кратковременные всплески трафика
|
||
(микро-штормы), удерживая пакеты в памяти ОС до момента их вычитки воркерами.
|
||
|
||
------------------------------
|
||
## 📋 Структура конфигурационных файлов проекта
|
||
Проект имеет модульную и простую структуру, состоящую из следующих компонентов:
|
||
|
||
1. main.go — Весь исходный код сервера. Содержит логику инициализации,
|
||
пул воркеров, парсинг сырых байт STUN-заголовка, генерацию XOR-маппинга для
|
||
обхода NAT и отправку ответов.
|
||
2. go.mod — Файл описания Go-модуля (проект STUN на базе Go 1.27.1) с
|
||
автоматической фиксацией низкоуровневых системных зависимостей (golang.org/x/sys).
|
||
3. Dockerfile — Инструкция сборки безопасного микро-контейнера на базе
|
||
scratch от лица non-root пользователя.
|
||
4. docker-compose.yml — Манифест для оркестрации: настраивает запуск в
|
||
режиме host, прокидывает переменные окружения, ограничивает ресурсы
|
||
(Limits: 2 CPU, 128MB RAM) и защищает диск от переполнения логами
|
||
(ротация логов ограничена 3 файлами по 10МБ).
|
||
5. .env — Файл конфигурации переменных среды, где хранится внешний публичный
|
||
IPv4-адрес сервера (PUBLIC_IP), необходимый для корректного заполнения
|
||
STUN-аттрибутов ответа.
|
||
|
||
------------------------------
|
||
## 📈 Результаты тестирования пропускной способности
|
||
В ходе стресс-тестирования методом асинхронного UDP-флуда
|
||
(параллельный запуск тысячи клиентов turnutils_stunclient), сервер показал
|
||
следующие результаты:
|
||
|
||
* Линейная масштабируемость: Обработка пакета из 2000 одновременных
|
||
запросов выполняется за ~1.19 сек, демонстрируя чистую скорость ~1550-1700 RPS
|
||
(запросов в секунду) на один клиентский поток тестирования.
|
||
* Параллельная утилизация ядер: Время работы в пользовательском пространстве
|
||
(user) и пространстве ядра (sys) превышает реальное время теста (real),
|
||
что доказывает идеальную параллельную работу пула воркеров на всех доступных
|
||
ядрах процессора хоста.
|