Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
Семь тысяч rps, один Go.
Сергей Камардин, @mail.ru
Вступление
Что нужно сделать?
Сервер между браузером и сторонним сервисом. Нужно уметь:
● Держать много соединений по WebSocket;
● Делать много HTTPS запросов;
● Разбирать ответы от сервиса;
● Инлайнить ресурсы;
● Много работать с JSON.
Какую технологию выбрать?
Выбирали по соотношению эффективности разработки и
производительности результата:
● Node.js;
● Rust;
● Go;
● Scala.
bit.do/mailgo
WebSocket
WebSocket — протокол полнодуплексной связи поверх TCP-соединения,
предназначенный для обмена сообщениями между браузером и веб-
сервером в режиме реального времени.
● Бинарный;
● Использует HTTP для Upgrade соединения;
● Поддерживается всеми современными браузерами.
WebSocket
Имплементации:
● golang.org/x/net/websocket
● github.com/gorilla/websocket
Аргументы за gorilla/websocket:
● Активно поддерживается;
● Умеет control messages;
JSON-RPC
Протокол вызова удаленных процедур.
● Двунаправленный;
● Асинхронный.
--> {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}
<-- {"jsonrpc": "2.0", "result": 19, "id": 1}
--> {"jsonrpc": "2.0", "method": "divide", "params": [42, 0], "id": 2}
<-- {"jsonrpc": "2.0", "error": "division by zero", "id": 2}
Реальность
Проблемы
● Как сократить количество соединений к серверу?
● Что будет, если сторонний сервис заснет?
● Как сократить затраты CPU на установку HTTPS соединений?
Переиспользование соединений
При большой нагрузке, исходящие соединения практически не
переиспользуются.
Переиспользование соединений
Стандартная библиотека позволяет влиять на это:
provider.SetClient(&http.Client{
Transport: &http.Transport{
...
MaxIdleConnsPerHost: 128,
...
},
})
Переиспользование соединений
Стандартная библиотека позволяет влиять на это.
Если сервис зависнет
В случае таймаута мы можем только закрыть соединение.
Если сервис зависнет
TLS и рукопожатие
Установка HTTPS соединения забирает много CPU:
TLS и рукопожатие
Установка HTTPS соединения забирает много CPU:
Переиспользование TLS сессий
provider.SetClient(&http.Client{
Transport: &http.Transport{
...
TLSClientConfig: &tls.Config{
ClientSessionCache: tls.NewLRUClientSessionCache(128),
},
...
},
})
Стандартная библиотека позволяет влиять и на это:
Переиспользование TLS сессий
Стандартная библиотека позволяет влиять и на это:
Переиспользование TLS сессий
Если сервис зависнет
В случае таймаута мы можем только закрыть соединение и открыть снова.
Если сервис зависнет
В случае таймаута мы все еще можем только закрыть соединение. Но теперь
мы переустанавливаем его гораздо быстрее.
JSON
JSON
type MyCoolStruct struct {
Key string
Value int32
}
Можно значительно сэкономить время, если генерировать код
сериализации/десериализации структур:
{"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()
}
}
}
Можно значительно сэкономить время, если генерировать код
сериализации/десериализации структур:
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
Можно значительно сэкономить время, если генерировать код
сериализации/десериализации структур:
JSON
Библиотеки реализующие данный подход:
● github.com/pquerna/ffjson
● github.com/mailru/easyjson
Аргументы за mailru/easyjson:
● На данный момент самая быстрая;
● Гибкая конфигурация (auto to_snake_case и тд);
Микрооптимизации
Микрооптимизации
● sync.Pool;
● избегать копирований;
● избегать аллокаций;
● инлайнить функции;
● работать с байтами;
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;
}
}
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'
}
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'
}
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
Или косвенно по бенчмаркам:
Как обнаружить проблемы
Утечка памяти
Классический пример утечки с использованием горутин:
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
}
Эмуляция нагрузки
Для HTTP:
● apache jmeter;
● wrk;
● ab;
Для WebSocket:
● gws;
● tcpkali;
● apache jmeter;
Apache JMeter
● Очень гибкий;
● Множество отчетов;
● Умеет следить за состоянием машин;
● Умеет remote testing;
● Можно поставить WebSocket sampler;
WRK
https://github.com/wg/wrk/
● Весьма прост;
● Можно писать lua-скрипты;
● Очень быстрый, может завалить nginx;
GWS
https://github.com/gobwas/gws
● Можно писать lua-скрипты;
● Может быть как клиентом так и сервером;
Концовка
Summary
Переиспользуем ресурсы:
● Коннекты;
● TLS сессии;
● Слайсы и структуры;
Оптимизируем код:
● Парсим синтаксис без reflection;
● Профилируем;
● Пишем бенчмарки;
● Нагружаем и мониторим;
Статистика
● 170 000 живых соединений;
● 1500 в секунду новых WebSocket соединений;
● 7000 json-rpc сообщений в секунду;
● Около 20 000 загрузок небольших изображений (~10Kb) в секунду
(~200mb/s) для последующего инлайна;
Спасибо!
Ссылки
● http://www.jsonrpc.org/specification
● http://www.gorillatoolkit.org/pkg/websocket
● https://github.com/mailru/easyjson
● https://github.com/gobwas/gws
● https://github.com/gobwas/glob
● https://github.com/machinezone/tcpkali
● https://github.com/divan/gobenchui
● https://github.com/valyala/fasthttp

More Related Content

Семь тысяч Rps, один go