Вы знаете, что обычные сетевые библиотеки Go начинают «тяжело дышать», если их нагрузить десятками тысяч соединений? Неважно, делали вы HTTP API или свой TCP сервер — дефолтные инструменты вроде net
всегда имеют свои лимиты. Тут-то хорош зайдет Netpoll — библиотека, которая позволяет серверам обрабатывать сотни тысяч соединений одновременно и при этом не терять в производительности.
Почему Netpoll?
Если вы уже успели поиграться со стандартной библиотекой net
, то знаете: она классная… до поры до времени. Как только подключений становится больше тысячи, начинается боль: блокировки, миллионы горутин и изматывающее профилирование.
А вот Netpoll решает всё это за счёт асинхронности и низкоуровневого доступа к системе. Она использует epoll
на Linux и kqueue
на macOS. Для нас это значит:
-
Асинхронная работа: никаких блокировок. Всё крутится на событиях.
-
Нагрузоустойчивость: сервер может держать сотни тысяч соединений и даже не вспотеть.
-
Без лишнего копирования: данные читаются напрямую из буфера. Память не страдает, производительность радует.
-
Гибкая настройка: можно тонко подогнать под конкретные нужды.
Но не всё так идеально. Если вы пишете простой REST API, Netpoll вам вряд ли нужен. Зато если у вас чаты, игровые серверы, вебсокеты или TCP-прокси — это для вас.
Сразу к делу: пишем сервер
Создаём Listener и EventLoop
Netpoll делит работу между Listener (точка входа для соединений) и EventLoop (мозг, который обрабатывает события). Настроим этот тандем:
package main import ( "fmt" "time" "os" "os/signal" "syscall" "github.com/cloudwego/netpoll" ) // Обработчик запросов func handleRequest(ctx netpoll.Context, conn netpoll.Connection) error { reader := conn.Reader() data, err := reader.Next(512) // Читаем до 512 байт if err != nil { fmt.Printf("Ошибка чтения: %v\n", err) return err } defer reader.Release() // Освобождаем буфер fmt.Printf("Получено: %s\n", string(data)) writer := conn.Writer() writer.WriteString("Привет! Это сервер на Netpoll!\n") return writer.Flush() // Отправляем ответ } func main() { // Настраиваем Listener listener, err := netpoll.CreateListener("tcp", ":8080") if err != nil { panic(fmt.Sprintf("Ошибка создания Listener: %v", err)) } defer listener.Close() // Настраиваем EventLoop eventLoop, err := netpoll.NewEventLoop( handleRequest, netpoll.WithReadTimeout(10*time.Second), netpoll.WithIdleTimeout(5*time.Minute), ) if err != nil { panic(fmt.Sprintf("Ошибка создания EventLoop: %v", err)) } fmt.Println("Сервер запущен на порту 8080...") go func() { err := eventLoop.Serve(listener) if err != nil { fmt.Printf("Ошибка EventLoop: %v\n", err) } }() // Грейсфул-шатдаун c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) <-c fmt.Println("Завершаю работу...") ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() eventLoop.Shutdown(ctx) }
handleRequest обрабатывает запросы, так же настроили тайм-ауты для чтения и простоя. Плюсом реализовали грейсфул-шатдаун, сервер корректно завершает работу, закрывая соединения.
nocopy API
Netpoll позволяет работать с памятью напрямую. Т.е можно лишнего копирования, но здесь есть нюансы. Например, забудете вызвать Release()
— получите утечку памяти.
Чтение данных:
func handleRequest(ctx netpoll.Context, conn netpoll.Connection) error { reader := conn.Reader() buf, err := reader.Next(512) if err != nil { return fmt.Errorf("Ошибка чтения: %v", err) } defer reader.Release() // Буфер обязательно освобождаем fmt.Printf("Получено: %s\n", string(buf)) return nil }
Запись данных:
func writeResponse(conn netpoll.Connection, message string) error { writer := conn.Writer() buf, err := writer.Malloc(len(message)) // Выделяем память if err != nil { return fmt.Errorf("Ошибка выделения памяти: %v", err) } copy(buf, message) return writer.Flush() // Отправляем данные }
Клиентская часть Netpoll
Серверы — это круто, но часто нужен еще и мощный клиент. Пример:
package main import ( "fmt" "time" "github.com/cloudwego/netpoll" ) func main() { conn, err := netpoll.DialConnection("tcp", "127.0.0.1:8080", 5*time.Second) if err != nil { panic(fmt.Sprintf("Ошибка подключения: %v", err)) } defer conn.Close() writer := conn.Writer() writer.WriteString("Привет, сервер!") writer.Flush() reader := conn.Reader() response, _ := reader.Next(512) fmt.Printf("Ответ сервера: %s\n", string(response)) }
Продвинутые настройки
Поллеры
Настраиваем количество поллеров:
package main import ( "runtime" "github.com/cloudwego/netpoll" ) func init() { runtime.GOMAXPROCS(runtime.NumCPU()) // Используем все ядра netpoll.SetNumLoops(runtime.NumCPU()) // Поллер на каждое ядро }\
Балансировка нагрузки
Netpoll поддерживает стратегии распределения:
package main import "github.com/cloudwego/netpoll" func init() { netpoll.SetLoadBalance(netpoll.RoundRobin) // Равномерное распределение }
Тайм-ауты
Тайм-ауты защищают сервер от зависших соединений:
package main import ( "time" "github.com/cloudwego/netpoll" ) func main() { var conn netpoll.Connection conn.SetReadTimeout(10 * time.Second) // Тайм-аут чтения conn.SetIdleTimeout(5 * time.Minute) // Тайм-аут простоя }
Мониторинг: метрики и логи
Используем logrus
или zap
для структурированного логирования:
package main import ( "github.com/sirupsen/logrus" ) var log = logrus.New() func handleRequest(ctx netpoll.Context, conn netpoll.Connection) error { reader := conn.Reader() data, err := reader.Next(512) if err != nil { log.WithError(err).Error("Ошибка чтения") return err } defer reader.Release() log.WithField("data", string(data)).Info("Получены данные") return nil }
Далее подключаем Prometheus для мониторинга соединений:
package main import ( "github.com/prometheus/client_golang/prometheus" "net/http" ) var ( activeConnections = prometheus.NewGauge(prometheus.GaugeOpts{ Name: "active_connections", Help: "Количество активных соединений", }) ) func init() { prometheus.MustRegister(activeConnections) } func main() { go func() { http.Handle("/metrics", prometheus.Handler()) http.ListenAndServe(":9090", nil) }() }
Возможные проблемы
-
nocopy API
и утечки памяти. Не забываем (!!!) вызыватьRelease()
после чтения. -
Перегрузка. Если сервер перегружен, ограничиваем количество соединений через
WithMaxConnections
. -
Грейсфул-шатдаун. Всегда освобождайте ресурсы корректно.
Заключение
Netpoll — это идеальный инструмент для высоконагруженных серверов.
Попробуйте, внедряйте и делитесь своими успехами в комментариях. И помните: хороший сервер — это сервер, который не падает.
Больше инструментов и практических кейсов эксперты OTUS рассматривают в рамках практических онлайн курсов.Также напомню о том, что в календаре мероприятий вы можете зарегистрироваться на ряд интересных бесплатных вебинаров.
ссылка на оригинал статьи https://habr.com/ru/articles/859964/
Добавить комментарий