Введение в fasthttp
При создании высокопроизводительных HTTP-серверов на Go пакет fasthttp
часто становится предпочтительным выбором для разработчиков, которым необходимо обрабатывать тысячи запросов в секунду с минимальной задержкой. В этой статье мы подробно рассмотрим fasthttp
, его особенности, сравним его со стандартным пакетом net/http
и узнаем, как использовать его для создания сверхбыстрого HTTP-сервера.
Зачем нужен fasthttp?
Пакет fasthttp
предназначен для высоконагруженных сценариев, где стандартного пакета net/http
может быть недостаточно. Вот несколько ключевых причин, по которым вы можете выбрать fasthttp
:
- Скорость:
fasthttp
оптимизирован для скорости и может обрабатывать более 100 тыс. запросов в секунду (QPS) и более 1 млн одновременных постоянных соединений на современном оборудовании. - Низкое выделение памяти: в отличие от
net/http
, который использует значительное выделение кучи,fasthttp
минимизирует выделение памяти в горячих путях, делая его намного быстрее в плане сырой производительности. - Нулевое выделение памяти: во многих тестах
fasthttp
демонстрирует нулевое выделение памяти в своих горячих путях, что значительно повышает производительность.
Настройка окружения
Перед тем как приступить к коду, убедитесь, что у вас установлена последняя версия Go. fasthttp
работает без проблем с Go версии 1.22 или выше.
go get -u github.com/valyala/fasthttp
Базовый сервер fasthttp
Давайте начнём с простого примера сервера fasthttp
. Вот как можно настроить базовый сервер, который будет отвечать на запросы GET:
package main
import (
"log"
"github.com/valyala/fasthttp"
)
func main() {
handler := func(ctx *fasthttp.RequestCtx) {
switch string(ctx.Path()) {
case "/":
ctx.SetContentType("text/plain; charset=utf-8")
ctx.SetBody([]byte("Welcome to the fasthttp server"))
default:
ctx.Error("Unsupported path", fasthttp.StatusNotFound)
}
}
if err := fasthttp.ListenAndServe(":8080", handler); err != nil {
log.Fatalf("Error in ListenAndServe: %s", err)
}
}
Этот пример настраивает сервер, прослушивающий порт 8080 и отвечающий простым текстовым сообщением при обращении к корневому пути.
Обработка запросов и ответов
Одно из ключевых отличий между fasthttp
и net/http
заключается в способе обработки запросов и ответов. Вот сравнение того, как вы можете обработать простой запрос в обоих пакетах:
Пример net/http
package main
import (
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/":
w.Write([]byte("Welcome to the net/http server"))
default:
http.Error(w, "Unsupported path", http.StatusNotFound)
}
}
func main() {
http.HandleFunc("/", handler)
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}
Пример fasthttp
package main
import (
"github.com/valyala/fasthttp"
)
func handler(ctx *fasthttp.RequestCtx) {
switch string(ctx.Path()) {
case "/":
ctx.SetContentType("text/plain; charset=utf-8")
ctx.SetBody([]byte("Welcome to the fasthttp server"))
default:
ctx.Error("Unsupported path", fasthttp.StatusNotFound)
}
}
func main() {
if err := fasthttp.ListenAndServe(":8080", handler); err != nil {
panic(err)
}
}
Обратите внимание на разницу в том, как обрабатывается контекст запроса. В fasthttp
объект RequestCtx
предоставляет всю необходимую функциональность для обработки запросов и записи ответов.
Тестирование вашего сервера fasthttp
Тестирование — важная часть любого процесса разработки. Вот как вы можете протестировать свой сервер fasthttp
, используя слушатели в памяти, которые значительно улучшают производительность тестов.
Использование fasthttputil.NewInmemoryListener
package main
import (
"testing"
"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/fasthttputil"
"github.com/valyala/fasthttp/testing"
)
func TestHandler(t *testing.T) {
ln := fasthttputil.NewInmemoryListener()
defer ln.Close()
go func() {
if err := fasthttp.ListenAndServe(ln, handler); err != nil {
t.Errorf("ListenAndServe error: %v", err)
}
}()
req := testing.DoRequest(ln, "GET", "/", nil)
if req.StatusCode != fasthttp.StatusOK {
t.Errorf("Expected status code %d but got %d", fasthttp.StatusOK, req.StatusCode)
}
}
Такой подход позволяет избежать необходимости прослушивания на реальном сетевом порту, что делает ваши тесты намного быстрее.
Сравнение производительности
Чтобы понять разницу в производительности между net/http
и fasthttp
, давайте посмотрим на некоторые результаты тестов.
Результаты тестов net/http
Вот некоторые результаты тестов для net/http
:
BenchmarkNetHTTPServerGet10ReqPerConn-4 500000 2751 ns/op 2118 B/op 19 allocs/op
BenchmarkNetHTTPServerGet100ReqPerConn-4 500000 2398 ns/op 2037 B/op 18 allocs/op
Результаты тестов fasthttp
BenchmarkServerGet10ReqPerConn-4 3000000 417 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet100ReqPerConn-4 5000000 351 ns/op 0 B/op 0 allocs/op
Как видите, fasthttp
значительно превосходит net/http
, особенно с точки зрения выделения памяти и времени выполнения.
Диаграмма последовательности для обработки запроса fasthttp
Вот диаграмма последовательности, иллюстрирующая, как запрос обрабатывается на сервере fasthttp
:
Заключение
Создание высокопроизводительного HTTP-сервера на Go с использованием fasthttp
— это простой и полезный процесс. Благодаря своей оптимизированной конструкции для скорости и минимального выделения памяти, fasthtml
является идеальным выбором для приложений, требующих обработки тысяч запросов в секунду.