Плюс, таймауты на чтение из клиентского коннекта, ограничение доступа к апстриму и т. д.
Можно конечно, переписать на Go логику, отлично работающую в nginx, но не уверен, что это принесет гигантский профит. Возможно, стоит попробовать – но сейчас есть более приоритетные задачи =)
В общении между шиной и клиентами мы не хотим использовать протоколы кровавого энтерпрайза SOAP, JBI, JMS и т. д. В почте у нас нет Java, и большинство вариантов использования покрывает внутренний бинарный протокол IProto. Нам хотелось использовать что-то совсем легковесное и контролирумое чуть более, чем полностью – мы еще не пришли к конечной логике работы системы и можем захотеть ее поменять.
Ну профит в Go здесь как раз в том, что мы можем выборочно оптимизировать узкие места. Например, там, где не требуется высокая эффективность и производительность можно положиться на go runtime, сборщик мусора и т. д.
Были мысли из Go использовать pico http parser, но в итоге написал кусочек функционала на Go и на этом пока вопрос закрылся =)
Nginx в первую очередь берет на себя ssl. Плюс, мы запускаем несколько экземпляров сервера на go, чтобы при падении/локе/жестком рестарте одного остальные продолжали работать: nginx распределяет коннекты между экземплярами.
Теоретически, 3 миллиона соединений на одной машине могут жить. Но для распределения нагрузки и наличия запаса ресурсов мы распределяем соединения на несколько серверов. Сейчас это 8 машин, было 4. Но судя по цифрам, после оптимизаций мы смогли бы держать 3 миллиона соединений на одной машине: не было бы запаса по CPU, но с памятью было бы все ок. Но и там, возможно, можно было бы что-то докрутить.
Что касается адресации – вы, наверное, имеете в виду проблему портов, когда nginx не может спроксировать больше ~64K коннектов на локальный демон? Ее можно решать, как вы правильно сказали, добавлением виртуальных интерфейсов, либо, как это сделали мы – уйти от TCP-сокетов с адресацией по порту, в сторону UNIX-сокетов.
Лимит на открытые файловые дескрипторы – это "ручка", которая настраивается в Linux на процесс или на пользователя.
По цифрам могу сказать про память, что до оптимизаций сервер потреблял ~60Кбайт на соединение, после – 10Кбайт. При этом, можно крутить флажок GOGC в Go, который так же немного влияет на цифры потребления памяти.
package main
import (
"testing"
)
type Fooer interface {
Foo()
}
type Concrete struct {
data byte // Just to see in allocation.
}
func (c Concrete) Foo() {}
func HandleFooer(f Fooer) {}
func BenchmarkCallFooer(b *testing.B) {
for i := 0; i < b.N; i++ {
c := Concrete{} // Moved to heap.
Fooer(&c).Foo()
}
}
func BenchmarkPassFooer(b *testing.B) {
for i := 0; i < b.N; i++ {
c := Concrete{} // Not moved to heap.
HandleFooer(Fooer(&c))
}
}
Возник следующий вопрос (больше даже относящийся к статье Russ Cox):
package main
import (
"testing"
)
type Stringer interface {
String() string
}
func GetString(s Stringer) string {
return s.String()
}
type Text struct {
val string
pad [24]byte
}
func (t Text) String() string {
return t.val
}
func BenchmarkGetString(b *testing.B) {
for i := 0; i < b.N; i++ {
t := Text{val: "hello"} // Moved to heap, 24 bytes for pad and 8 bytes for string.
_ = GetString(&t) // Allocation for Stringer 8 + 8 bytes.
}
}
Если запустить следующую команду:
go test iface_test.go -gcflags="-m -N -l" -bench=. -benchmem
Мы видим, среди прочего:
./iface_test.go:26: moved to heap: t
...
BenchmarkGetString-4 30000000 56.7 ns/op 48 B/op 1 allocs/op
Т.е. в каждой итерации выделяется память на heap под структуру Text, и интерфейс Stringer. Почему нельзя в данном случае ограничиться стеком?
Плюс, таймауты на чтение из клиентского коннекта, ограничение доступа к апстриму и т. д.
Можно конечно, переписать на Go логику, отлично работающую в nginx, но не уверен, что это принесет гигантский профит. Возможно, стоит попробовать – но сейчас есть более приоритетные задачи =)
Go умеет SSL, при этом, если я не ошибаюсь, без binding'ов – т.е. своя реализация.
Спасибо! Всегда есть куда развиваться.
Упс, ответил ниже в треде =)
В общении между шиной и клиентами мы не хотим использовать протоколы
кровавого энтерпрайзаSOAP, JBI, JMS и т. д. В почте у нас нет Java, и большинство вариантов использования покрывает внутренний бинарный протокол IProto. Нам хотелось использовать что-то совсем легковесное и контролирумое чуть более, чем полностью – мы еще не пришли к конечной логике работы системы и можем захотеть ее поменять.И это не ради мониторинга.
В ближайшее время не планировалось. Если интересно про маршрутизацию и некоторые цифры – есть видео с РИТ2017:
На C++ вроде есть недурственный uWebSockets, но это не точно.
Ну профит в Go здесь как раз в том, что мы можем выборочно оптимизировать узкие места. Например, там, где не требуется высокая эффективность и производительность можно положиться на go runtime, сборщик мусора и т. д.
Были мысли из Go использовать pico http parser, но в итоге написал кусочек функционала на Go и на этом пока вопрос закрылся =)
8 Кбайт – это который в зависимости от операционной системы и версии Go. В последних версиях и в Linux, если я не ошибаюсь, стек начинается с 2 Кбайт.
Судя по всему, речь идет об Erlang/Elixir? =)
Получается,
(338-223) * 8 = 920
байт стека? Выходит, с учетом кучи (338 * 8 = 2704
) разницы нет? )Вот в ponylang, например, акторы занимают 256 байт памяти… Но это уже совсем другая история =)
Из "неизданного":

Извиняюсь, с телефона прочитал "домен" как "демон" =) Вопрос тогда не актуален.
Nginx в первую очередь берет на себя ssl. Плюс, мы запускаем несколько экземпляров сервера на go, чтобы при падении/локе/жестком рестарте одного остальные продолжали работать: nginx распределяет коннекты между экземплярами.
А что за демон для вебсокет-соединений?
Привет, Спасибо!
Теоретически, 3 миллиона соединений на одной машине могут жить. Но для распределения нагрузки и наличия запаса ресурсов мы распределяем соединения на несколько серверов. Сейчас это 8 машин, было 4. Но судя по цифрам, после оптимизаций мы смогли бы держать 3 миллиона соединений на одной машине: не было бы запаса по CPU, но с памятью было бы все ок. Но и там, возможно, можно было бы что-то докрутить.
Что касается адресации – вы, наверное, имеете в виду проблему портов, когда nginx не может спроксировать больше ~64K коннектов на локальный демон? Ее можно решать, как вы правильно сказали, добавлением виртуальных интерфейсов, либо, как это сделали мы – уйти от TCP-сокетов с адресацией по порту, в сторону UNIX-сокетов.
Лимит на открытые файловые дескрипторы – это "ручка", которая настраивается в Linux на процесс или на пользователя.
По цифрам могу сказать про память, что до оптимизаций сервер потреблял ~60Кбайт на соединение, после – 10Кбайт. При этом, можно крутить флажок GOGC в Go, который так же немного влияет на цифры потребления памяти.
Ой, уже кинул ниже )
FYI: https://github.com/golang/go/issues/20380
Еще немного упростил пример:
Результат:
Да, видимо, анализатор не учитывает конкретную реализацию Foo() объекта внутри Fooer, и поэтому кладет его на heap.
Попробую создать тикет.
P.S. Спасибо за
-m -m
! Не знал. =)Спасибо за перевод и ссылки!
Возник следующий вопрос (больше даже относящийся к статье Russ Cox):
Если запустить следующую команду:
Мы видим, среди прочего:
Т.е. в каждой итерации выделяется память на heap под структуру
Text
, и интерфейсStringer
. Почему нельзя в данном случае ограничиться стеком?Как-то стремновато будет выглядеть monkey patching в production коде?
Как-то использовал эту прекрасную либу, но только для того, чтобы делать stub'ы в структурах, при использовании которых хотел избежать интерфейсов.
Если по теме – когда-то пилил тоже "свой", с асинхронной загрузкой, ленивым инстанцированием и прочими прелестями:
https://github.com/gobwas/dm.js
Точно! Если можно применить простое не «goroutine-safe» переиспользование – получится быстрее.