Семь тысяч Rps, один go
-
Upload
badoo-development -
Category
Technology
-
view
5.680 -
download
4
Transcript of Семь тысяч Rps, один go
Семь тысяч 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;
WRKhttps://github.com/wg/wrk/
● Весьма прост;● Можно писать lua-скрипты;● Очень быстрый, может завалить nginx;
GWShttps://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