Из презентации вы узнаете:
— как мы пришли к Go, оставив идею использования Node.js, Scala или Rust;
— про первый сервис, который мы написали на Go и запустили в продакшен;
— про ошибки, с которыми сталкивались под нагрузкой;
— про оптимизации, которые мы сделали и еще планируем сделать;
— про тестирование и предотвращение тестирования на продакшене (в частности, websocket'ов).
3. Что нужно сделать?
Сервер между браузером и сторонним сервисом. Нужно уметь:
● Держать много соединений по WebSocket;
● Делать много HTTPS запросов;
● Разбирать ответы от сервиса;
● Инлайнить ресурсы;
● Много работать с JSON.
4. Какую технологию выбрать?
Выбирали по соотношению эффективности разработки и
производительности результата:
● Node.js;
● Rust;
● Go;
● Scala.
bit.do/mailgo
5. WebSocket
WebSocket — протокол полнодуплексной связи поверх TCP-соединения,
предназначенный для обмена сообщениями между браузером и веб-
сервером в режиме реального времени.
● Бинарный;
● Использует HTTP для Upgrade соединения;
● Поддерживается всеми современными браузерами.
9. Проблемы
● Как сократить количество соединений к серверу?
● Что будет, если сторонний сервис заснет?
● Как сократить затраты CPU на установку HTTPS соединений?
23. JSON
type MyCoolStruct struct {
Key string
Value int32
}
Можно значительно сэкономить время, если генерировать код
сериализации/десериализации структур:
24. {"key": "question", "value": 42}
JSON
func decodeMyStruct(in *lexer.Lexer, out *MyCoolStruct) {
for !in.HasNext() {
switch in.UnsafeString() {
case "key":
out.Key = in.String()
case "value":
out.Value = in.Int32()
}
}
}
Можно значительно сэкономить время, если генерировать код
сериализации/десериализации структур:
25. JSON
BenchmarkEncodingJson-8 1000000 1236 ns/op 288 B/op 4 allocs/op
BenchmarkEasyJson-8 10000000 179 ns/op 0 B/op 0 allocs/op
Можно значительно сэкономить время, если генерировать код
сериализации/десериализации структур:
26. JSON
Библиотеки реализующие данный подход:
● github.com/pquerna/ffjson
● github.com/mailru/easyjson
Аргументы за mailru/easyjson:
● На данный момент самая быстрая;
● Гибкая конфигурация (auto to_snake_case и тд);
29. Inline функций
func CheckChar(c byte) int {
if isGoodChar(c) {
return 42
} else {
return 0
}
}
func isGoodChar(c byte) bool {
switch c {
case 'x', 'y', 'z':
return false;
default:
return true;
}
}
30. Inline функций
func CheckChar(c byte) int {
if isGoodChar(c) {
return 42
} else {
return 0
}
}
func isGoodChar(c byte) bool {
return c != 'x' && c != 'y' && c != 'z'
}
31. Inline функций
func CheckChar(c byte) int {
if c != 'x' && c != 'y' && c != 'z' {
return 42
} else {
return 0
}
}
func isGoodChar(c byte) bool {
return c != 'x' && c != 'y' && c != 'z'
}
32. Inline функций
$ go build -gcflags=-m inline.go
inline/inline.go:20: can inline isGoodChar
inline/inline.go:3: can inline CheckChar
inline/inline.go:4: inlining call to isGoodChar
Можно проверить с помощью gcflags -m:
BenchmarkInline-8 2000000000 1.28 ns/op
BenchmarkNoInline-8 500000000 3.84 ns/op
Или косвенно по бенчмаркам:
34. Утечка памяти
Классический пример утечки с использованием горутин:
func LoadImages(done chan struct{}, urls []string) (ret [][]byte) {
results := make(chan []byte)
for _, url := range urls {
go loadImage(results, url)
}
for count := 0; count < len(urls); count++ {
select {
case <-done: // client has canceled loading
return
case r := <-results: // yet another image
ret = append(ret, r)
}
}
return
}
40. Summary
Переиспользуем ресурсы:
● Коннекты;
● TLS сессии;
● Слайсы и структуры;
Оптимизируем код:
● Парсим синтаксис без reflection;
● Профилируем;
● Пишем бенчмарки;
● Нагружаем и мониторим;
41. Статистика
● 170 000 живых соединений;
● 1500 в секунду новых WebSocket соединений;
● 7000 json-rpc сообщений в секунду;
● Около 20 000 загрузок небольших изображений (~10Kb) в секунду
(~200mb/s) для последующего инлайна;