Если вам интересно, какой недочёт есть во всех вебприложениях на языке Go размещенных на хабре. И хотите знать, как сделать своё Go вебприложение на один шаг ближе к production ready, то добро пожаловать по кат.
Важное отличие веб программирования на языке Go это то, что в результате вы обычно получаете программу, которая является вебсервером. Поэтому вы становитесь ответственными за то, что обычно отвечает вебсервер.
Если сравнить как выглядят Hello World на PHP и Go, то мы увидим
PHP
<?php echo “Hello World”; ?>
Go
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
Взято с вики сайта языка Go
Аналогичный код используют, в следующих статьях на хабре: Веб-разработка на Go, Пишем веб-эмулятор терминала на Go, используя Websocket, Goblog: Самодельный статический движок для блога на Go, Написание своего Web-приложения на Go.
Близко по теме: Go Language. Небольшое клиент-серверное приложение, TCP/IP proxy на Go.
Важным отличием и преимуществом Go является то, как Go работает с сетью и может держать множество открытых соединений, например, для реализации long polling.
В статье C10k (Проблема 10000 соединений) на разных языках/платформах Go показал второй результат после Erlang, открыв 9775 соодинений из 10000.
Вторым преимуществом Go — является простота и читаемость кода, легкость в изучении, что делает его часто выбором №1 при разработке приложений, в которых нужно работать со множеством открытых соединений.
Ошибка Недочёт всех этих примеров
Недочёт в том, что Timeout (Deadline) для соединений по умолчанию 0 (т.е. timeout вовсе не установлен). Если клиент не отправит пакет о том, что соединение закрыто, то такое соединение повиснет на всегда. Это приведет к блокировке goroutine в режиме ожидания, до конца жизни вебсервера. Количество открытых соединение ресурс обычно ограниченный. По умолчанию в Линуксе приложение может открыть 1024 файла (TCP соединение приравнено к файлу).
Это значит — сервер созданный на Go следующим образом
http.ListenAndServe(":8080", nil)
Будет постепенно накапливать не закрытые соединения и захлебнется ими через день, месяц или год.
Поэтому важно устанавливать Timeout, которые в обычных вебсерверах установлены по умолчанию.
Например
// log.Fatal(http.ListenAndServe(":8085", nil))
{
s := &http.Server{
Addr: ":8085",
Handler: nil,
ReadTimeout: 1000 * time.Second,
WriteTimeout: 1000 * time.Second,
MaxHeaderBytes: 1 << 20,
}
log.Fatal(s.ListenAndServe())
}
Второй недочёт параметр, который в обычном вебсервере часто указывается это максимальное количество соединений, которое вебсервер готов открыть. В случаи достижения этого ограничения вебсервер начинает жертвовать keep-alive соединениями или выдавать ошибку и закрывать соединения.
Go по умолчанию такого не делает, и вообще не очевидно как в Go ограничить количество соединений или посчитать количество соединений или тем более управлять этим процессом.
Что бы разобраться с тем, как управлять соединениями нужно заглянуть в исходники func (*Server) ListenAndServe и func (*Server) ListenAndServeTLS, тогда мы увидим, что оби функции используют функцию func (*Server) Serve.
Эта функция получает интерфейс net.Listener как аргумент.
Вот его мы и можем реализовать для ограничения и контроля соединений.
Пример обвертки вокруг интерфейса net.Listener является LimitListener
Пройдя по статьям на хабре с тегом [go] я не нашел примеров в, которых устанавливались Timeout, DeadLine или контролировалось количество соединений.
Если учесть, что Go создавался как сверхпараллельный, сетевой язык, то реализация правильной и продуманной стратегии в работе соединениями, является обязательным элементом приложения на Go.
Автор: pyra