SlideShare uma empresa Scribd logo
1 de 162
Baixar para ler offline
Миллион WebSocket
и pub/sub
Сергей Камардин, MailRu Group.
Состояние и события
Состояние и события
• Состояние – любая хранимая информация программы.
• Событие – информация об изменении состояния.
Скорость реакции на событие, как и ее
отсутствие, имеет свою цену.
Состояние в почте
Какая информация является состоянием в любом почтовом
сервисе?
• письма в ящике
• пометки о прочтении
• срок жизни сессии
• …
События в почте
Как пользователь узнает об изменении ящика?
HTTP polling каждые 2 минуты:
События в почте
Как пользователь узнает об изменении ящика?
HTTP polling каждые 2 минуты:
• 3 миллиона запросов в минуту
События в почте
Как пользователь узнает об изменении ящика?
HTTP polling каждые 2 минуты:
• 3 миллиона запросов в минуту
• 50 тысяч запросов в секунду
Как пользователь узнает об изменении ящика?
HTTP polling каждые 2 минуты:
• 3 миллиона запросов в минуту
• 50 тысяч запросов в секунду
• 60% ответов - 304 Not Modified
События в почте
План
План
Хочется, чтобы сервер сам посылал сообщение о том, что его
состояние изменилось.
Publisher/Subscriber
Способ реагирования на изменение состояния при помощи
шины событий.
• Издатель отправляет множество событий в шину.
• Подписчик интересуется подмножеством событий из шины.
Как было
+-----------+ +-----------+ +-----------+
| | | | | |
| Storage | | API | | Browser |
| | | | | |
+-----------+ +-----------+ +-----------+
Как было
+-----------+ +-----------+ +-----------+
| | | | ◄-------+ | |
| Storage | | API | HTTP | Browser |
| | | | | |
+-----------+ +-----------+ +-----------+
Как было
+-----------+ +-----------+ +-----------+
| | ◄-------+ | | ◄-------+ | |
| Storage | | API | HTTP | Browser |
| | | | | |
+-----------+ +-----------+ +-----------+
Как было
+-----------+ +-----------+ +-----------+
| | ◄-------+ | | ◄-------+ | |
| Storage | | API | HTTP | Browser |
| | +-------► | | | |
+-----------+ +-----------+ +-----------+
Как было
+-----------+ +-----------+ +-----------+
| | ◄-------+ | | ◄-------+ | |
| Storage | | API | HTTP | Browser |
| | +-------► | | +-------► | |
+-----------+ +-----------+ +-----------+
План
+-------------+ +---------+ +-----------+
| Storage | | API | | Browser |
+-------------+ +---------+ +-----------+
+---------------------------------+
| Bus |
+---------------------------------+
План
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ○-----------○ | Browser |
+-------------+ +---------+ +-----------+
+---------------------------------+
| Bus |
+---------------------------------+
План
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ◄-----------○ | Browser |
+-------------+ +---------+ +-----------+
+---------------------------------+
| Bus |
+---------------------------------+
План
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ○-----------○ | Browser |
+-------------+ +---------+ +-----------+
+---------------------------------+
| Bus |
+---------------------------------+
*
План
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ○-----------○ | Browser |
+-------------+ +---------+ +-----------+
|
|
▼
+---------------------------------+
| Bus |
+---------------------------------+
*
План
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ○-----------○ | Browser |
+-------------+ +---------+ +-----------+
+---------------------------------+
| Bus |
+---------------------------------+
*
*
План
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ○-----------○ | Browser |
+-------------+ +---------+ +-----------+
|
|
▼
+---------------------------------+
| Bus |
+---------------------------------+
*
*
План
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ○-----------○ | Browser |
+-------------+ +---------+ +-----------+
▲
|
|
+---------------------------------+
| Bus |
+---------------------------------+
*
*
План
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ○-----------► | Browser |
+-------------+ +---------+ +-----------+
+---------------------------------+
| Bus |
+---------------------------------+
*
*
План
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ○-----------○ | Browser |
+-------------+ +---------+ +-----------+
+---------------------------------+
| Bus |
+---------------------------------+
*
*
План
+-------------+ +---------+
| Storage | | API |
+-------------+ +---------+
+-------------+ +---------+ +-----------+
| Storage | | API | | Browser |
+-------------+ +---------+ +-----------+
+---------+ +---------+ +---------+
| Bus | | Bus | | Bus |
+---------+ +---------+ +---------+
План
+-------------+ +---------+
| Storage | | API |
+-------------+ +---------+
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ○-----------○ | Browser |
+-------------+ +---------+ +-----------+
+---------+ +---------+ +---------+
| Bus | | Bus | | Bus |
+---------+ +---------+ +---------+
План
+-------------+ +---------+
| Storage | | API |
+-------------+ +---------+
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ◄-----------○ | Browser |
+-------------+ +---------+ +-----------+
+---------+ +---------+ +---------+
| Bus | | Bus | | Bus |
+---------+ +---------+ +---------+
План
+-------------+ +---------+
| Storage | | API |
+-------------+ +---------+
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ○-----------○ | Browser |
+-------------+ +---------+ +-----------+
+---------+ +---------+ +---------+
| Bus | | Bus | | Bus |
+---------+ +---------+ +---------+
*
План
+-------------+ +---------+
| Storage | | API |
+-------------+ +---------+
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ○-----------○ | Browser |
+-------------+ +---------+ +-----------+
|
|
▼
+---------+ +---------+ +---------+
| Bus | | Bus | | Bus |
+---------+ +---------+ +---------+
*
План
+-------------+ +---------+
| Storage | | API |
+-------------+ +---------+
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ○-----------○ | Browser |
+-------------+ +---------+ +-----------+
|
+------+
▼
+---------+ +---------+ +---------+
| Bus | | Bus | | Bus |
+---------+ +---------+ +---------+
*
*
План
+-------------+ +---------+
| Storage | | API |
+-------------+ +---------+
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ○-----------○ | Browser |
+-------------+ +---------+ +-----------+
|
+------------------+
▼
+---------+ +---------+ +---------+
| Bus | | Bus | | Bus |
+---------+ +---------+ +---------+
*
**
План
+-------------+ +---------+
| Storage | | API |
+-------------+ +---------+
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ○-----------○ | Browser |
+-------------+ +---------+ +-----------+
+---------+ +---------+ +---------+
| Bus | | Bus | | Bus |
+---------+ +---------+ +---------+
*
***
План
+-------------+ +---------+
| Storage | | API |
+-------------+ +---------+
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ○-----------○ | Browser |
+-------------+ +---------+ +-----------+
|
+--------+
▼
+---------+ +---------+ +---------+
| Bus | | Bus | | Bus |
+---------+ +---------+ +---------+
*
***
План
+-------------+ +---------+
| Storage | | API |
+-------------+ +---------+
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ○-----------○ | Browser |
+-------------+ +---------+ +-----------+
▲
+------+
|
+---------+ +---------+ +---------+
| Bus | | Bus | | Bus |
+---------+ +---------+ +---------+
*
***
План
+-------------+ +---------+
| Storage | | API |
+-------------+ +---------+
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ○-----------► | Browser |
+-------------+ +---------+ +-----------+
+---------+ +---------+ +---------+
| Bus | | Bus | | Bus |
+---------+ +---------+ +---------+
*
***
План
+-------------+ +---------+
| Storage | | API |
+-------------+ +---------+
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ○-----------○ | Browser |
+-------------+ +---------+ +-----------+
|
+--------------------+
▼
+---------+ +---------+ +---------+
| Bus | | Bus | | Bus |
+---------+ +---------+ +---------+
*
***
План
+-------------+ +---------+
| Storage | | API |
+-------------+ +---------+
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ○-----------○ | Browser |
+-------------+ +---------+ +-----------+
▲
|
|
+---------+ +---------+ +---------+
| Bus | | Bus | | Bus |
+---------+ +---------+ +---------+
*
***
План
+-------------+ +---------+
| Storage | | API |
+-------------+ +---------+
+-------------+ +---------+ WebSocket +-----------+
| Storage | | API | ○-----------► | Browser |
+-------------+ +---------+ +-----------+
+---------+ +---------+ +---------+
| Bus | | Bus | | Bus |
+---------+ +---------+ +---------+
*
***
Маршрутизация
Маршрутизация
Стратегии:
• flooding
Маршрутизация
Стратегии:
• flooding
• gossiping
Маршрутизация
Стратегии:
• flooding
• gossiping } Много трафика, большая задержка
Маршрутизация
Стратегии:
• flooding
• gossiping
• filtering
} Много трафика, большая задержка
Маршрутизация событий
Filtering можно сделать по-разному
• с помощью таблиц и деревьев в памяти
• можно не париться и взять подходящую базу данных
• издатели публикуют полный набор параметров
• подписчики подписываются на любой набор параметров
Маршрутизация событий
Как применяем фильтры?
• издатели публикуют полный набор параметров
• подписчики подписываются на любой набор параметров
Маршрутизация событий
└──*──[ 1 ]
│
├──Email
│ │
│ └──"bus@mail.ru"──[ 8 ]
│
└──Publisher
│
└──"storage"──[ 2 ]
│
└──Event
│
└──"change"──[ 3, 7 ]
│
└──Email
├──"bus@mail.ru"──[ 4 ]
└──"bar@mail.ru"──[ 5 ]
Маршрутизация событий
└──*──[ 1 ]
│
├──Email
│ │
│ └──"bus@mail.ru"──[ 8 ]
│
└──Publisher
│
└──"storage"──[ 2 ]
│
└──Event
│
└──"change"──[ 3, 7 ]
│
└──Email
├──"bus@mail.ru"──[ 4 ]
└──"bar@mail.ru"──[ 5 ]
[ ]
storage:change { email: "bus@mail.ru" }
Маршрутизация событий
└──*──[ 1 ]
│
├──Email
│ │
│ └──”bus@mail.ru”──[ 8 ]
│
└──Publisher
│
└──"storage"──[ 2 ]
│
└──Event
│
└──"change"──[ 3, 7 ]
│
└──Email
├──"bus@mail.ru"──[ 4 ]
└──"bar@mail.ru"──[ 5 ]
[ ]
storage:change { email: "bus@mail.ru" }
Маршрутизация событий
└──*──[ 1 ]
│
├──Email
│ │
│ └──”bus@mail.ru”──[ 8 ]
│
└──Publisher
│
└──"storage"──[ 2 ]
│
└──Event
│
└──"change"──[ 3, 7 ]
│
└──Email
├──"bus@mail.ru"──[ 4 ]
└──"bar@mail.ru"──[ 5 ]
[ 1 ]
storage:change { email: "bus@mail.ru" }
Маршрутизация событий
└──*──[ 1 ]
│
├──Email
│ │
│ └──”bus@mail.ru”──[ 8 ]
│
└──Publisher
│
└──"storage"──[ 2 ]
│
└──Event
│
└──"change"──[ 3, 7 ]
│
└──Email
├──"bus@mail.ru"──[ 4 ]
└──"bar@mail.ru"──[ 5 ]
[ 1 ]
storage:change { email: "bus@mail.ru" }
Маршрутизация событий
└──*──[ 1 ]
│
├──Email
│ │
│ └──"bus@mail.ru"──[ 8 ]
│
└──Publisher
│
└──"storage"──[ 2 ]
│
└──Event
│
└──"change"──[ 3, 7 ]
│
└──Email
├──"bus@mail.ru"──[ 4 ]
└──"bar@mail.ru"──[ 5 ]
[ 1 ]
storage:change { email: "bus@mail.ru" }
Маршрутизация событий
└──*──[ 1 ]
│
├──Email
│ │
│ └──"bus@mail.ru"──[ 8 ]
│
└──Publisher
│
└──"storage"──[ 2 ]
│
└──Event
│
└──"change"──[ 3, 7 ]
│
└──Email
├──"bus@mail.ru"──[ 4 ]
└──"bar@mail.ru"──[ 5 ]
[ 1, 8 ]
storage:change { email: "bus@mail.ru" }
Маршрутизация событий
└──*──[ 1 ]
│
├──Email
│ │
│ └──"bus@mail.ru"──[ 8 ]
│
└──Publisher
│
└──"storage"──[ 2 ]
│
└──Event
│
└──"change"──[ 3, 7 ]
│
└──Email
├──"bus@mail.ru"──[ 4 ]
└──"bar@mail.ru"──[ 5 ]
[ 1, 8 ]
storage:change { email: "bus@mail.ru" }
Маршрутизация событий
└──*──[ 1 ]
│
├──Email
│ │
│ └──"bus@mail.ru"──[ 8 ]
│
└──Publisher
│
└──"storage"──[ 2 ]
│
└──Event
│
└──"change"──[ 3, 7 ]
│
└──Email
├──"bus@mail.ru"──[ 4 ]
└──"bar@mail.ru"──[ 5 ]
[ 1, 8 ]
storage:change { email: "bus@mail.ru" }
Маршрутизация событий
└──*──[ 1 ]
│
├──Email
│ │
│ └──"bus@mail.ru"──[ 8 ]
│
└──Publisher
│
└──"storage"──[ 2 ]
│
└──Event
│
└──"change"──[ 3, 7 ]
│
└──Email
├──"bus@mail.ru"──[ 4 ]
└──"bar@mail.ru"──[ 5 ]
[ 1, 8, 2 ]
storage:change { email: "bus@mail.ru" }
Маршрутизация событий
└──*──[ 1 ]
│
├──Email
│ │
│ └──"bus@mail.ru"──[ 8 ]
│
└──Publisher
│
└──"storage"──[ 2 ]
│
└──Event
│
└──"change"──[ 3, 7 ]
│
└──Email
├──"bus@mail.ru"──[ 4 ]
└──"bar@mail.ru"──[ 5 ]
[ 1, 8, 2 ]
storage:change { email: "bus@mail.ru" }
Маршрутизация событий
└──*──[ 1 ]
│
├──Email
│ │
│ └──"bus@mail.ru"──[ 8 ]
│
└──Publisher
│
└──"storage"──[ 2 ]
│
└──Event
│
└──"change"──[ 3, 7 ]
│
└──Email
├──"bus@mail.ru"──[ 4 ]
└──"bar@mail.ru"──[ 5 ]
[ 1, 8, 2 ]
storage:change { email: "bus@mail.ru" }
Маршрутизация событий
└──*──[ 1 ]
│
├──Email
│ │
│ └──"bus@mail.ru"──[ 8 ]
│
└──Publisher
│
└──"storage"──[ 2 ]
│
└──Event
│
└──"change"──[ 3, 7 ]
│
└──Email
├──"bus@mail.ru"──[ 4 ]
└──"bar@mail.ru"──[ 5 ]
[ 1, 8, 2, 3, 7 ]
storage:change { email: "bus@mail.ru" }
Маршрутизация событий
└──*──[ 1 ]
│
├──Email
│ │
│ └──"bus@mail.ru"──[ 8 ]
│
└──Publisher
│
└──"storage"──[ 2 ]
│
└──Event
│
└──"change"──[ 3, 7 ]
│
└──Email
├──"bus@mail.ru"──[ 4 ]
└──"bar@mail.ru"──[ 5 ]
[ 1, 8, 2, 3, 7 ]
storage:change { email: "bus@mail.ru" }
Маршрутизация событий
└──*──[ 1 ]
│
├──Email
│ │
│ └──"bus@mail.ru"──[ 8 ]
│
└──Publisher
│
└──"storage"──[ 2 ]
│
└──Event
│
└──"change"──[ 3, 7 ]
│
└──Email
├──"bus@mail.ru"──[ 4 ]
└──"bar@mail.ru"──[ 5 ]
[ 1, 8, 2, 3, 7 ]
storage:change { email: "bus@mail.ru" }
Маршрутизация событий
└──*──[ 1 ]
│
├──Email
│ │
│ └──"bus@mail.ru"──[ 8 ]
│
└──Publisher
│
└──"storage"──[ 2 ]
│
└──Event
│
└──"change"──[ 3, 7 ]
│
└──Email
├──"bus@mail.ru"──[ 4 ]
└──"bar@mail.ru"──[ 5 ]
[ 1, 8, 2, 3, 7, 4 ]
storage:change { email: "bus@mail.ru" }
Маршрутизация событий
[ 1, 8, 2, 3, 7, 4 ]
storage:change { email: "bus@mail.ru" }
WebSocket
WebSocket
Бинарный двухсторонний протокол общения между браузером
и сервером.
• Стандартизован в 2011г. как RFC6455.
• Поддерживается всеми современными браузерами.
WebSocket
Использует HTTP для Upgrade.
После оперирует «фреймами» внутри соединения:
• Контрольные: ping, pong, close
• Пользовательские: text, binary
WebSocket
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
WebSocket
WebSocket
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
WebSocket
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
WebSocket
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
WebSocket
Браузеры по-разному реализуют RFC6455.
• Chrome не дожидается ответного Close фрейма и сразу
закрывает соединение
WebSocket
Браузеры по-разному реализуют RFC6455.
• Chrome не дожидается ответного Close фрейма и сразу
закрывает соединение
• Firefox периодически посылает Ping фреймы, ожидая в
ответ Pong
WebSocket
Браузеры по-разному реализуют RFC6455:
• Chrome не дожидается ответного Close фрейма и сразу
закрывает соединение
• Firefox периодически посылает Ping фреймы, ожидая в
ответ Pong
• IE периодически посылает.. Pong фреймы (но это не
противоречит спецификации)
Трудности
• 3 миллиона «живых» соединений
• время жизни соединения от нескольких секунд до
нескольких дней
• при разрыве соединений клиенты (браузер)
переподключаются
Go
Go
• Компилируемый, со строгой статической типизацией
• Только структуры и интерфейсы
• Управление памятью с помощью сборщика мусора
• Каналы и «горутины» для конкурентного программирования
Idiomatic реализация
Go net.Conn
package "net"
type Conn interface {
Read(b []byte) (n int, err error)
Write(b []byte) (n int, err error)
...
}
WebSocket Channel
type Channel struct {
conn net.Conn
}
WebSocket Channel
type Channel struct {
conn net.Conn
out chan Packet // Output packet queue.
}
WebSocket Channel
type Channel struct {
conn net.Conn
out chan Packet
}
func NewChannel(conn net.Conn) *Channel {
c := &Channel{...}
}
WebSocket Channel
type Channel struct {
conn net.Conn
out chan Packet
}
func NewChannel(conn net.Conn) *Channel {
c := &Channel{...}
go c.reader()
}
WebSocket Channel
type Channel struct {
conn net.Conn
out chan Packet
}
func NewChannel(conn net.Conn) *Channel {
c := &Channel{...}
go c.reader()
go c.writer()
}
type Channel struct {
conn net.Conn
out chan Packet
}
func NewChannel(conn net.Conn) *Channel {
c := &Channel{...}
go c.reader() // stack size += 4KB
go c.writer()
}
WebSocket Channel
type Channel struct {
conn net.Conn
out chan Packet
}
func NewChannel(conn net.Conn) *Channel {
c := &Channel{...}
go c.reader() // stack size += 4KB
go c.writer() // stack size += 4KB
}
WebSocket Channel
24GB(4KB + 4KB) * 3M
WebSocket Channel
WebSocket Channel
func (c *Channel) reader() {
}
WebSocket Channel
func (c *Channel) reader() {
buf := bufio.NewReader(c.conn)
}
WebSocket Channel
func (c *Channel) reader() {
buf := bufio.NewReader(c.conn) // heap size += 4KB
}
WebSocket Channel
func (c *Channel) reader() {
buf := bufio.NewReader(c.conn)
for {
}
}
WebSocket Channel
func (c *Channel) reader() {
buf := bufio.NewReader(c.conn)
for {
buf.Read()
}
}
WebSocket Channel
func (c *Channel) reader() {
buf := bufio.NewReader(c.conn)
for {
buf.Read() // wait incoming bytes
}
}
WebSocket Channel
func (c *Channel) writer() {
}
WebSocket Channel
func (c *Channel) writer() {
buf := bufio.NewWriter(c.conn)
}
WebSocket Channel
func (c *Channel) writer() {
buf := bufio.NewWriter(c.conn) // heap size += 4KB
}
WebSocket Channel
func (c *Channel) writer() {
buf := bufio.NewWriter(c.conn)
for packet := range c.out {
}
}
WebSocket Channel
func (c *Channel) writer() {
buf := bufio.NewWriter(c.conn)
for packet := range c.out {
buf.Write(...)
}
}
func (c *Channel) writer() {
buf := bufio.NewWriter(c.conn)
for packet := range c.out { // wait outgoing packets
buf.Write(...)
}
}
WebSocket Channel
48GB(4KB + 4KB) * 3M
(4KB + 4KB) * 3M
WebSocket Channel
WebSocket Channel
import "net/http"
http.HandleFunc("/ws",
)
WebSocket Channel
import "net/http"
http.HandleFunc("/ws",
func(w http.ResponseWriter, r *http.Request) {
},
)
WebSocket Channel
import “net/http"
http.HandleFunc("/ws",
func(w http.ResponseWriter, r *http.Request) {
// response write buffer; heap size += 4KB
// request read buffer; heap size += 4KB
},
)
WebSocket Channel
import “net/http"
http.HandleFunc("/ws",
func(w http.ResponseWriter, r *http.Request) {
},
)
WebSocket Channel
import "net/http"
import "some/websocket"
http.HandleFunc("/ws",
func(w http.ResponseWriter, r *http.Request) {
// Upgrade HTTP conn to WebSocket somehow.
conn := websocket.Upgrade(r, w)
},
)
72GB(4KB + 4KB) * 3M
(4KB + 4KB) * 3M
(4KB + 4KB) * 3M
WebSocket Channel
Оптимизации
epoll
epoll - I/O event notification facility (c) man epoll.
Как работает go runtime?
func (c *Channel) reader() {
for {
conn.Read() // wait incoming bytes
}
}
Как работает go runtime?
func read(c *conn, p []byte) (int, error) {
}
Как работает go runtime?
func read(c *conn, p []byte) (int, error) {
n, err := syscall.Read(c.fd, p)
}
Как работает go runtime?
func read(c *conn, p []byte) (int, error) {
n, err := syscall.Read(c.fd, p)
if err.IsTemp() {
}
}
Как работает go runtime?
func read(c *conn, p []byte) (int, error) {
n, err := syscall.Read(c.fd, p)
if err.IsTemp() {
// EAGAIN || EWOULDBLOCK
}
}
Как работает go runtime?
func read(c *conn, p []byte) (int, error) {
n, err := syscall.Read(c.fd, p)
if err.IsTemp() {
pollWait(c.descriptor, 'r')
}
}
Как работает go runtime?
• сокеты в Go неблокирующие
• runtime_pollWait на Linux реализован с помощью Epoll
• почему бы не использовать похожий подход?
epoll
ch := NewChannel(conn)
epoll
ch := NewChannel(conn)
epoll.Add(conn, func() {
})
epoll
ch := NewChannel(conn)
epoll.Add(conn, func() {
// EPOLLIN | EPOLLONESHOT
})
epoll
ch := NewChannel(conn)
epoll.Add(conn, func() {
epoll.Resume(conn)
})
epoll
ch := NewChannel(conn)
epoll.Add(conn, func() {
epoll.Resume(conn)
})
func Receive(ch *Channel) {
}
epoll
ch := NewChannel(conn)
epoll.Add(conn, func() {
epoll.Resume(conn)
})
func Receive(ch *Channel) {
buf := bufio.NewReader(ch.conn)
buf.Read()
}
epoll
ch := NewChannel(conn)
epoll.Add(conn, func() {
epoll.Resume(conn)
})
func Receive(ch *Channel) {
buf := bufio.NewReader(ch.conn)
buf.Read()
}
epoll
ch := NewChannel(conn)
epoll.Add(conn, func() {
Receive(ch)
epoll.Resume(conn)
})
func Receive(ch *Channel) {
buf := bufio.NewReader(ch.conn)
buf.Read()
}
Запись
С отправкой пакетов дело обстоит проще – мы всегда знаем,
когда хотим что-то записать в соединение.
Единственный сложный момент – синхронизация записи из
разных горутин.
Запись
func (ch *Channel) Send(p Packet) {
}
Запись
func (ch *Channel) Send(p Packet) {
if notSpawnedYet {
}
}
Запись
func (ch *Channel) Send(p Packet) {
if notSpawnedYet {
go ch.writer()
}
}
Запись
func (ch *Channel) Send(p Packet) {
if notSpawnedYet {
go ch.writer()
}
ch.out <- p
}
-48GB(4KB + 4KB) * 3M
(4KB + 4KB) * 3M
Контроль ресурсов
Контроль ресурсов
Что, если вдруг все соединения решат отправить нам
сообщение?
Тогда реализация с epoll ничем не будет отличаться от
изначальной idiomatic реализации.
Goroutine pool
p := pool.New(128)
pool.Schedule(func() {
// Do some work.
})
pool.ScheduleTimeout(time.Second, func() {
// Do some work or return error.
})
Goroutine pool
epoll.Add(conn, func() {
Receive(ch)
epoll.Resume(conn)
})
Goroutine pool
p := pool.New(128)
epoll.Add(conn, func() {
pool.Schedule(func() {
Receive(ch)
})
epoll.Resume(conn)
})
Запись
func (ch *Channel) Send(p Packet) {
if notSpawnedYet {
go ch.writer()
}
ch.out <- p
}
Запись
p := pool.New(128)
func (ch *Channel) Send(p Packet) {
if notSpawnedYet {
p.Schedule(ch.writer)
}
ch.out <- p
}
Zero-copy upgrade
Zero-copy upgrade
GET /ws HTTP/1.1
Host: mail.ru
Connection: Upgrade
Sec-Websocket-Key: A3xNe7sEB9HixkmBhVrYaA==
Sec-Websocket-Version: 13
Upgrade: websocket
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Sec-Websocket-Accept: ksu0wXWG+YmkVx+KQR2agP0cQn4=
Upgrade: websocket
Zero-copy upgrade
import "net/http"
import "some/websocket"
http.HandleFunc("/ws",
func(w http.ResponseWriter, r *http.Request) {
conn := websocket.Upgrade(r, w)
},
)
Zero-copy upgrade
import "net"
ln := net.Listen("tcp", ":8080")
for {
conn := ln.Accept()
}
Zero-copy upgrade
import "net"
ln := net.Listen("tcp", ":8080")
for {
conn := ln.Accept()
// How to upgrade raw bytes?
}
Время запилить свою либу
В Go существуют две реализации WebSocket.
И обе они не позволяют полностью контролировать работу с
аллокацией памяти.
Поэтому пришлось реализовать RFC6455 с более
низкоуровневым API.
github.com/gobwas/ws
• работает с io.Reader, io.Writer
• умеет zero-copy upgrade
• позволяет кешировать фреймы
• проходит autobahn test suite
Zero-copy upgrade
BenchmarkUpgradeHTTP 5156 ns/op 8576 B/op 9 allocs/op
BenchmarkUpgradeTCP 973 ns/op 0 B/op 0 allocs/op
В случае простого Upgrade без сохранения значений заголовков.
Код бенчмарков: http://bit.ly/2svISpr.
-24GB(4KB + 4KB) * 3M
Все вместе
При использовании goroutine pool можно вообще не
принимать соединения, если ресурсов больше нет.
Это решает многие проблемы DDOS в случае нештатных
ситуаций.
import "net"
import "github.com/gobwas/ws"
ln := net.Listen("tcp", ":8080")
for {
}
import "net"
import "github.com/gobwas/ws"
ln := net.Listen("tcp", ":8080")
for {
err := pool.ScheduleTimeout(
)
}
import "net"
import "github.com/gobwas/ws"
ln := net.Listen("tcp", ":8080")
for {
err := pool.ScheduleTimeout(
time.Millisecond,
func() {
},
)
}
import "net"
import "github.com/gobwas/ws"
ln := net.Listen("tcp", ":8080")
for {
err := pool.ScheduleTimeout(
time.Millisecond,
func() {
conn := ln.Accept()
ws.UpgradeConn(conn)
},
)
}
import "net"
import "github.com/gobwas/ws"
ln := net.Listen("tcp", ":8080")
for {
err := pool.ScheduleTimeout(
time.Millisecond,
func() {
conn := ln.Accept()
ws.UpgradeConn(conn)
},
)
if err != nil {
}
}
import "net"
import "github.com/gobwas/ws"
ln := net.Listen("tcp", ":8080")
for {
err := pool.ScheduleTimeout(
time.Millisecond,
func() {
conn := ln.Accept()
ws.UpgradeConn(conn)
},
)
if err != nil {
time.Sleep(time.Millisecond)
}
}
Все вместе
• При большом количестве бездействующих соединений
лучше использовать epoll и отказаться от читающей
горутины
• При большом количестве соединений отказаться от
пишущей горутины
• Goroutine pool позволяет зафиксировать количество
максимально потребляемой памяти
• Goroutine pool позволяет ограничить количество
обрабатываемых соединений
Итого
Итого
Событийная модель позволяет:
• реализовать действительно интерактивный сервис
• предлагать пользователям множество дополнительных
функций
• снизить сопряженность сервисов и упростить
взаимодействие
Цифры
• 3 миллиона “живых” соединений
• 30 тысяч уведомлений в секунду от storage
• 9 тысяч из которых доходят до пользователей ежесекундно
• 75 тысяч событий EPOLLIN (доступность для чтения) в
секунду
• 1 тысяча Upgrade запросов в секунду
• в ходе оптимизаций количество памяти на одно соединение
сократилось с 60KB до 10KB (и это не все!)
PROFIT
github.com/gobwas/ws
github.com/gobwas/httphead
Сергей Камардин
MailRu Group
s.kamardin@corp.mail.ru
Спасибо!

Mais conteúdo relacionado

Mais de Ontico

ProxySQL Use Case Scenarios / Alkin Tezuysal (Percona)
ProxySQL Use Case Scenarios / Alkin Tezuysal (Percona)ProxySQL Use Case Scenarios / Alkin Tezuysal (Percona)
ProxySQL Use Case Scenarios / Alkin Tezuysal (Percona)Ontico
 
MySQL Replication — Advanced Features / Петр Зайцев (Percona)
MySQL Replication — Advanced Features / Петр Зайцев (Percona)MySQL Replication — Advanced Features / Петр Зайцев (Percona)
MySQL Replication — Advanced Features / Петр Зайцев (Percona)Ontico
 
Внутренний open-source. Как разрабатывать мобильное приложение большим количе...
Внутренний open-source. Как разрабатывать мобильное приложение большим количе...Внутренний open-source. Как разрабатывать мобильное приложение большим количе...
Внутренний open-source. Как разрабатывать мобильное приложение большим количе...Ontico
 
Подробно о том, как Causal Consistency реализовано в MongoDB / Михаил Тюленев...
Подробно о том, как Causal Consistency реализовано в MongoDB / Михаил Тюленев...Подробно о том, как Causal Consistency реализовано в MongoDB / Михаил Тюленев...
Подробно о том, как Causal Consistency реализовано в MongoDB / Михаил Тюленев...Ontico
 
Балансировка на скорости проводов. Без ASIC, без ограничений. Решения NFWare ...
Балансировка на скорости проводов. Без ASIC, без ограничений. Решения NFWare ...Балансировка на скорости проводов. Без ASIC, без ограничений. Решения NFWare ...
Балансировка на скорости проводов. Без ASIC, без ограничений. Решения NFWare ...Ontico
 
Перехват трафика — мифы и реальность / Евгений Усков (Qrator Labs)
Перехват трафика — мифы и реальность / Евгений Усков (Qrator Labs)Перехват трафика — мифы и реальность / Евгений Усков (Qrator Labs)
Перехват трафика — мифы и реальность / Евгений Усков (Qrator Labs)Ontico
 
И тогда наверняка вдруг запляшут облака! / Алексей Сушков (ПЕТЕР-СЕРВИС)
И тогда наверняка вдруг запляшут облака! / Алексей Сушков (ПЕТЕР-СЕРВИС)И тогда наверняка вдруг запляшут облака! / Алексей Сушков (ПЕТЕР-СЕРВИС)
И тогда наверняка вдруг запляшут облака! / Алексей Сушков (ПЕТЕР-СЕРВИС)Ontico
 
Как мы заставили Druid работать в Одноклассниках / Юрий Невиницин (OK.RU)
Как мы заставили Druid работать в Одноклассниках / Юрий Невиницин (OK.RU)Как мы заставили Druid работать в Одноклассниках / Юрий Невиницин (OK.RU)
Как мы заставили Druid работать в Одноклассниках / Юрий Невиницин (OK.RU)Ontico
 
Разгоняем ASP.NET Core / Илья Вербицкий (WebStoating s.r.o.)
Разгоняем ASP.NET Core / Илья Вербицкий (WebStoating s.r.o.)Разгоняем ASP.NET Core / Илья Вербицкий (WebStoating s.r.o.)
Разгоняем ASP.NET Core / Илья Вербицкий (WebStoating s.r.o.)Ontico
 
100500 способов кэширования в Oracle Database или как достичь максимальной ск...
100500 способов кэширования в Oracle Database или как достичь максимальной ск...100500 способов кэширования в Oracle Database или как достичь максимальной ск...
100500 способов кэширования в Oracle Database или как достичь максимальной ск...Ontico
 
Apache Ignite Persistence: зачем Persistence для In-Memory, и как он работает...
Apache Ignite Persistence: зачем Persistence для In-Memory, и как он работает...Apache Ignite Persistence: зачем Persistence для In-Memory, и как он работает...
Apache Ignite Persistence: зачем Persistence для In-Memory, и как он работает...Ontico
 
Механизмы мониторинга баз данных: взгляд изнутри / Дмитрий Еманов (Firebird P...
Механизмы мониторинга баз данных: взгляд изнутри / Дмитрий Еманов (Firebird P...Механизмы мониторинга баз данных: взгляд изнутри / Дмитрий Еманов (Firebird P...
Механизмы мониторинга баз данных: взгляд изнутри / Дмитрий Еманов (Firebird P...Ontico
 
Как мы учились чинить самолеты в воздухе / Евгений Коломеец (Virtuozzo)
Как мы учились чинить самолеты в воздухе / Евгений Коломеец (Virtuozzo)Как мы учились чинить самолеты в воздухе / Евгений Коломеец (Virtuozzo)
Как мы учились чинить самолеты в воздухе / Евгений Коломеец (Virtuozzo)Ontico
 
Java и Linux — особенности эксплуатации / Алексей Рагозин (Дойче Банк)
Java и Linux — особенности эксплуатации / Алексей Рагозин (Дойче Банк)Java и Linux — особенности эксплуатации / Алексей Рагозин (Дойче Банк)
Java и Linux — особенности эксплуатации / Алексей Рагозин (Дойче Банк)Ontico
 
Как построить кластер для расчета сотен тысяч high-CPU/high-MEM-задач и не ра...
Как построить кластер для расчета сотен тысяч high-CPU/high-MEM-задач и не ра...Как построить кластер для расчета сотен тысяч high-CPU/high-MEM-задач и не ра...
Как построить кластер для расчета сотен тысяч high-CPU/high-MEM-задач и не ра...Ontico
 
Отказоустойчивая архитектура фронтальной системы банка / Роман Шеховцов, Алек...
Отказоустойчивая архитектура фронтальной системы банка / Роман Шеховцов, Алек...Отказоустойчивая архитектура фронтальной системы банка / Роман Шеховцов, Алек...
Отказоустойчивая архитектура фронтальной системы банка / Роман Шеховцов, Алек...Ontico
 
libfpta — обгоняя SQLite и Tarantool / Леонид Юрьев (Positive Technologies)
libfpta — обгоняя SQLite и Tarantool / Леонид Юрьев (Positive Technologies)libfpta — обгоняя SQLite и Tarantool / Леонид Юрьев (Positive Technologies)
libfpta — обгоняя SQLite и Tarantool / Леонид Юрьев (Positive Technologies)Ontico
 
Синхронизация данных из PgSQL в Tarantool / Вениамин Гвоздиков (Calltouch)
Синхронизация данных из PgSQL в Tarantool / Вениамин Гвоздиков (Calltouch)Синхронизация данных из PgSQL в Tarantool / Вениамин Гвоздиков (Calltouch)
Синхронизация данных из PgSQL в Tarantool / Вениамин Гвоздиков (Calltouch)Ontico
 
Хранимые процедуры в NoSQL СУБД на примере Tarantool / Денис Линник (Mail.Ru)
Хранимые процедуры в NoSQL СУБД на примере Tarantool / Денис Линник (Mail.Ru)Хранимые процедуры в NoSQL СУБД на примере Tarantool / Денис Линник (Mail.Ru)
Хранимые процедуры в NoSQL СУБД на примере Tarantool / Денис Линник (Mail.Ru)Ontico
 
Оптимизации поисковой выдачи Яндекса / Иван Хватов, Сергей Ляджин (Яндекс)
Оптимизации поисковой выдачи Яндекса / Иван Хватов, Сергей Ляджин (Яндекс)Оптимизации поисковой выдачи Яндекса / Иван Хватов, Сергей Ляджин (Яндекс)
Оптимизации поисковой выдачи Яндекса / Иван Хватов, Сергей Ляджин (Яндекс)Ontico
 

Mais de Ontico (20)

ProxySQL Use Case Scenarios / Alkin Tezuysal (Percona)
ProxySQL Use Case Scenarios / Alkin Tezuysal (Percona)ProxySQL Use Case Scenarios / Alkin Tezuysal (Percona)
ProxySQL Use Case Scenarios / Alkin Tezuysal (Percona)
 
MySQL Replication — Advanced Features / Петр Зайцев (Percona)
MySQL Replication — Advanced Features / Петр Зайцев (Percona)MySQL Replication — Advanced Features / Петр Зайцев (Percona)
MySQL Replication — Advanced Features / Петр Зайцев (Percona)
 
Внутренний open-source. Как разрабатывать мобильное приложение большим количе...
Внутренний open-source. Как разрабатывать мобильное приложение большим количе...Внутренний open-source. Как разрабатывать мобильное приложение большим количе...
Внутренний open-source. Как разрабатывать мобильное приложение большим количе...
 
Подробно о том, как Causal Consistency реализовано в MongoDB / Михаил Тюленев...
Подробно о том, как Causal Consistency реализовано в MongoDB / Михаил Тюленев...Подробно о том, как Causal Consistency реализовано в MongoDB / Михаил Тюленев...
Подробно о том, как Causal Consistency реализовано в MongoDB / Михаил Тюленев...
 
Балансировка на скорости проводов. Без ASIC, без ограничений. Решения NFWare ...
Балансировка на скорости проводов. Без ASIC, без ограничений. Решения NFWare ...Балансировка на скорости проводов. Без ASIC, без ограничений. Решения NFWare ...
Балансировка на скорости проводов. Без ASIC, без ограничений. Решения NFWare ...
 
Перехват трафика — мифы и реальность / Евгений Усков (Qrator Labs)
Перехват трафика — мифы и реальность / Евгений Усков (Qrator Labs)Перехват трафика — мифы и реальность / Евгений Усков (Qrator Labs)
Перехват трафика — мифы и реальность / Евгений Усков (Qrator Labs)
 
И тогда наверняка вдруг запляшут облака! / Алексей Сушков (ПЕТЕР-СЕРВИС)
И тогда наверняка вдруг запляшут облака! / Алексей Сушков (ПЕТЕР-СЕРВИС)И тогда наверняка вдруг запляшут облака! / Алексей Сушков (ПЕТЕР-СЕРВИС)
И тогда наверняка вдруг запляшут облака! / Алексей Сушков (ПЕТЕР-СЕРВИС)
 
Как мы заставили Druid работать в Одноклассниках / Юрий Невиницин (OK.RU)
Как мы заставили Druid работать в Одноклассниках / Юрий Невиницин (OK.RU)Как мы заставили Druid работать в Одноклассниках / Юрий Невиницин (OK.RU)
Как мы заставили Druid работать в Одноклассниках / Юрий Невиницин (OK.RU)
 
Разгоняем ASP.NET Core / Илья Вербицкий (WebStoating s.r.o.)
Разгоняем ASP.NET Core / Илья Вербицкий (WebStoating s.r.o.)Разгоняем ASP.NET Core / Илья Вербицкий (WebStoating s.r.o.)
Разгоняем ASP.NET Core / Илья Вербицкий (WebStoating s.r.o.)
 
100500 способов кэширования в Oracle Database или как достичь максимальной ск...
100500 способов кэширования в Oracle Database или как достичь максимальной ск...100500 способов кэширования в Oracle Database или как достичь максимальной ск...
100500 способов кэширования в Oracle Database или как достичь максимальной ск...
 
Apache Ignite Persistence: зачем Persistence для In-Memory, и как он работает...
Apache Ignite Persistence: зачем Persistence для In-Memory, и как он работает...Apache Ignite Persistence: зачем Persistence для In-Memory, и как он работает...
Apache Ignite Persistence: зачем Persistence для In-Memory, и как он работает...
 
Механизмы мониторинга баз данных: взгляд изнутри / Дмитрий Еманов (Firebird P...
Механизмы мониторинга баз данных: взгляд изнутри / Дмитрий Еманов (Firebird P...Механизмы мониторинга баз данных: взгляд изнутри / Дмитрий Еманов (Firebird P...
Механизмы мониторинга баз данных: взгляд изнутри / Дмитрий Еманов (Firebird P...
 
Как мы учились чинить самолеты в воздухе / Евгений Коломеец (Virtuozzo)
Как мы учились чинить самолеты в воздухе / Евгений Коломеец (Virtuozzo)Как мы учились чинить самолеты в воздухе / Евгений Коломеец (Virtuozzo)
Как мы учились чинить самолеты в воздухе / Евгений Коломеец (Virtuozzo)
 
Java и Linux — особенности эксплуатации / Алексей Рагозин (Дойче Банк)
Java и Linux — особенности эксплуатации / Алексей Рагозин (Дойче Банк)Java и Linux — особенности эксплуатации / Алексей Рагозин (Дойче Банк)
Java и Linux — особенности эксплуатации / Алексей Рагозин (Дойче Банк)
 
Как построить кластер для расчета сотен тысяч high-CPU/high-MEM-задач и не ра...
Как построить кластер для расчета сотен тысяч high-CPU/high-MEM-задач и не ра...Как построить кластер для расчета сотен тысяч high-CPU/high-MEM-задач и не ра...
Как построить кластер для расчета сотен тысяч high-CPU/high-MEM-задач и не ра...
 
Отказоустойчивая архитектура фронтальной системы банка / Роман Шеховцов, Алек...
Отказоустойчивая архитектура фронтальной системы банка / Роман Шеховцов, Алек...Отказоустойчивая архитектура фронтальной системы банка / Роман Шеховцов, Алек...
Отказоустойчивая архитектура фронтальной системы банка / Роман Шеховцов, Алек...
 
libfpta — обгоняя SQLite и Tarantool / Леонид Юрьев (Positive Technologies)
libfpta — обгоняя SQLite и Tarantool / Леонид Юрьев (Positive Technologies)libfpta — обгоняя SQLite и Tarantool / Леонид Юрьев (Positive Technologies)
libfpta — обгоняя SQLite и Tarantool / Леонид Юрьев (Positive Technologies)
 
Синхронизация данных из PgSQL в Tarantool / Вениамин Гвоздиков (Calltouch)
Синхронизация данных из PgSQL в Tarantool / Вениамин Гвоздиков (Calltouch)Синхронизация данных из PgSQL в Tarantool / Вениамин Гвоздиков (Calltouch)
Синхронизация данных из PgSQL в Tarantool / Вениамин Гвоздиков (Calltouch)
 
Хранимые процедуры в NoSQL СУБД на примере Tarantool / Денис Линник (Mail.Ru)
Хранимые процедуры в NoSQL СУБД на примере Tarantool / Денис Линник (Mail.Ru)Хранимые процедуры в NoSQL СУБД на примере Tarantool / Денис Линник (Mail.Ru)
Хранимые процедуры в NoSQL СУБД на примере Tarantool / Денис Линник (Mail.Ru)
 
Оптимизации поисковой выдачи Яндекса / Иван Хватов, Сергей Ляджин (Яндекс)
Оптимизации поисковой выдачи Яндекса / Иван Хватов, Сергей Ляджин (Яндекс)Оптимизации поисковой выдачи Яндекса / Иван Хватов, Сергей Ляджин (Яндекс)
Оптимизации поисковой выдачи Яндекса / Иван Хватов, Сергей Ляджин (Яндекс)
 

Миллион WebSocket и pub/sub / Сергей Камардин (MailRu Group)

  • 1. Миллион WebSocket и pub/sub Сергей Камардин, MailRu Group.
  • 3. Состояние и события • Состояние – любая хранимая информация программы. • Событие – информация об изменении состояния.
  • 4. Скорость реакции на событие, как и ее отсутствие, имеет свою цену.
  • 5. Состояние в почте Какая информация является состоянием в любом почтовом сервисе? • письма в ящике • пометки о прочтении • срок жизни сессии • …
  • 6. События в почте Как пользователь узнает об изменении ящика? HTTP polling каждые 2 минуты:
  • 7. События в почте Как пользователь узнает об изменении ящика? HTTP polling каждые 2 минуты: • 3 миллиона запросов в минуту
  • 8. События в почте Как пользователь узнает об изменении ящика? HTTP polling каждые 2 минуты: • 3 миллиона запросов в минуту • 50 тысяч запросов в секунду
  • 9. Как пользователь узнает об изменении ящика? HTTP polling каждые 2 минуты: • 3 миллиона запросов в минуту • 50 тысяч запросов в секунду • 60% ответов - 304 Not Modified События в почте
  • 10.
  • 12. План Хочется, чтобы сервер сам посылал сообщение о том, что его состояние изменилось.
  • 13. Publisher/Subscriber Способ реагирования на изменение состояния при помощи шины событий. • Издатель отправляет множество событий в шину. • Подписчик интересуется подмножеством событий из шины.
  • 14. Как было +-----------+ +-----------+ +-----------+ | | | | | | | Storage | | API | | Browser | | | | | | | +-----------+ +-----------+ +-----------+
  • 15. Как было +-----------+ +-----------+ +-----------+ | | | | ◄-------+ | | | Storage | | API | HTTP | Browser | | | | | | | +-----------+ +-----------+ +-----------+
  • 16. Как было +-----------+ +-----------+ +-----------+ | | ◄-------+ | | ◄-------+ | | | Storage | | API | HTTP | Browser | | | | | | | +-----------+ +-----------+ +-----------+
  • 17. Как было +-----------+ +-----------+ +-----------+ | | ◄-------+ | | ◄-------+ | | | Storage | | API | HTTP | Browser | | | +-------► | | | | +-----------+ +-----------+ +-----------+
  • 18. Как было +-----------+ +-----------+ +-----------+ | | ◄-------+ | | ◄-------+ | | | Storage | | API | HTTP | Browser | | | +-------► | | +-------► | | +-----------+ +-----------+ +-----------+
  • 19. План +-------------+ +---------+ +-----------+ | Storage | | API | | Browser | +-------------+ +---------+ +-----------+ +---------------------------------+ | Bus | +---------------------------------+
  • 20. План +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ○-----------○ | Browser | +-------------+ +---------+ +-----------+ +---------------------------------+ | Bus | +---------------------------------+
  • 21. План +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ◄-----------○ | Browser | +-------------+ +---------+ +-----------+ +---------------------------------+ | Bus | +---------------------------------+
  • 22. План +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ○-----------○ | Browser | +-------------+ +---------+ +-----------+ +---------------------------------+ | Bus | +---------------------------------+ *
  • 23. План +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ○-----------○ | Browser | +-------------+ +---------+ +-----------+ | | ▼ +---------------------------------+ | Bus | +---------------------------------+ *
  • 24. План +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ○-----------○ | Browser | +-------------+ +---------+ +-----------+ +---------------------------------+ | Bus | +---------------------------------+ * *
  • 25. План +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ○-----------○ | Browser | +-------------+ +---------+ +-----------+ | | ▼ +---------------------------------+ | Bus | +---------------------------------+ * *
  • 26. План +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ○-----------○ | Browser | +-------------+ +---------+ +-----------+ ▲ | | +---------------------------------+ | Bus | +---------------------------------+ * *
  • 27. План +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ○-----------► | Browser | +-------------+ +---------+ +-----------+ +---------------------------------+ | Bus | +---------------------------------+ * *
  • 28. План +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ○-----------○ | Browser | +-------------+ +---------+ +-----------+ +---------------------------------+ | Bus | +---------------------------------+ * *
  • 29. План +-------------+ +---------+ | Storage | | API | +-------------+ +---------+ +-------------+ +---------+ +-----------+ | Storage | | API | | Browser | +-------------+ +---------+ +-----------+ +---------+ +---------+ +---------+ | Bus | | Bus | | Bus | +---------+ +---------+ +---------+
  • 30. План +-------------+ +---------+ | Storage | | API | +-------------+ +---------+ +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ○-----------○ | Browser | +-------------+ +---------+ +-----------+ +---------+ +---------+ +---------+ | Bus | | Bus | | Bus | +---------+ +---------+ +---------+
  • 31. План +-------------+ +---------+ | Storage | | API | +-------------+ +---------+ +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ◄-----------○ | Browser | +-------------+ +---------+ +-----------+ +---------+ +---------+ +---------+ | Bus | | Bus | | Bus | +---------+ +---------+ +---------+
  • 32. План +-------------+ +---------+ | Storage | | API | +-------------+ +---------+ +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ○-----------○ | Browser | +-------------+ +---------+ +-----------+ +---------+ +---------+ +---------+ | Bus | | Bus | | Bus | +---------+ +---------+ +---------+ *
  • 33. План +-------------+ +---------+ | Storage | | API | +-------------+ +---------+ +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ○-----------○ | Browser | +-------------+ +---------+ +-----------+ | | ▼ +---------+ +---------+ +---------+ | Bus | | Bus | | Bus | +---------+ +---------+ +---------+ *
  • 34. План +-------------+ +---------+ | Storage | | API | +-------------+ +---------+ +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ○-----------○ | Browser | +-------------+ +---------+ +-----------+ | +------+ ▼ +---------+ +---------+ +---------+ | Bus | | Bus | | Bus | +---------+ +---------+ +---------+ * *
  • 35. План +-------------+ +---------+ | Storage | | API | +-------------+ +---------+ +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ○-----------○ | Browser | +-------------+ +---------+ +-----------+ | +------------------+ ▼ +---------+ +---------+ +---------+ | Bus | | Bus | | Bus | +---------+ +---------+ +---------+ * **
  • 36. План +-------------+ +---------+ | Storage | | API | +-------------+ +---------+ +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ○-----------○ | Browser | +-------------+ +---------+ +-----------+ +---------+ +---------+ +---------+ | Bus | | Bus | | Bus | +---------+ +---------+ +---------+ * ***
  • 37. План +-------------+ +---------+ | Storage | | API | +-------------+ +---------+ +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ○-----------○ | Browser | +-------------+ +---------+ +-----------+ | +--------+ ▼ +---------+ +---------+ +---------+ | Bus | | Bus | | Bus | +---------+ +---------+ +---------+ * ***
  • 38. План +-------------+ +---------+ | Storage | | API | +-------------+ +---------+ +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ○-----------○ | Browser | +-------------+ +---------+ +-----------+ ▲ +------+ | +---------+ +---------+ +---------+ | Bus | | Bus | | Bus | +---------+ +---------+ +---------+ * ***
  • 39. План +-------------+ +---------+ | Storage | | API | +-------------+ +---------+ +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ○-----------► | Browser | +-------------+ +---------+ +-----------+ +---------+ +---------+ +---------+ | Bus | | Bus | | Bus | +---------+ +---------+ +---------+ * ***
  • 40. План +-------------+ +---------+ | Storage | | API | +-------------+ +---------+ +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ○-----------○ | Browser | +-------------+ +---------+ +-----------+ | +--------------------+ ▼ +---------+ +---------+ +---------+ | Bus | | Bus | | Bus | +---------+ +---------+ +---------+ * ***
  • 41. План +-------------+ +---------+ | Storage | | API | +-------------+ +---------+ +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ○-----------○ | Browser | +-------------+ +---------+ +-----------+ ▲ | | +---------+ +---------+ +---------+ | Bus | | Bus | | Bus | +---------+ +---------+ +---------+ * ***
  • 42. План +-------------+ +---------+ | Storage | | API | +-------------+ +---------+ +-------------+ +---------+ WebSocket +-----------+ | Storage | | API | ○-----------► | Browser | +-------------+ +---------+ +-----------+ +---------+ +---------+ +---------+ | Bus | | Bus | | Bus | +---------+ +---------+ +---------+ * ***
  • 46. Маршрутизация Стратегии: • flooding • gossiping } Много трафика, большая задержка
  • 47. Маршрутизация Стратегии: • flooding • gossiping • filtering } Много трафика, большая задержка
  • 48. Маршрутизация событий Filtering можно сделать по-разному • с помощью таблиц и деревьев в памяти • можно не париться и взять подходящую базу данных • издатели публикуют полный набор параметров • подписчики подписываются на любой набор параметров
  • 49. Маршрутизация событий Как применяем фильтры? • издатели публикуют полный набор параметров • подписчики подписываются на любой набор параметров
  • 50. Маршрутизация событий └──*──[ 1 ] │ ├──Email │ │ │ └──"bus@mail.ru"──[ 8 ] │ └──Publisher │ └──"storage"──[ 2 ] │ └──Event │ └──"change"──[ 3, 7 ] │ └──Email ├──"bus@mail.ru"──[ 4 ] └──"bar@mail.ru"──[ 5 ]
  • 51. Маршрутизация событий └──*──[ 1 ] │ ├──Email │ │ │ └──"bus@mail.ru"──[ 8 ] │ └──Publisher │ └──"storage"──[ 2 ] │ └──Event │ └──"change"──[ 3, 7 ] │ └──Email ├──"bus@mail.ru"──[ 4 ] └──"bar@mail.ru"──[ 5 ] [ ] storage:change { email: "bus@mail.ru" }
  • 52. Маршрутизация событий └──*──[ 1 ] │ ├──Email │ │ │ └──”bus@mail.ru”──[ 8 ] │ └──Publisher │ └──"storage"──[ 2 ] │ └──Event │ └──"change"──[ 3, 7 ] │ └──Email ├──"bus@mail.ru"──[ 4 ] └──"bar@mail.ru"──[ 5 ] [ ] storage:change { email: "bus@mail.ru" }
  • 53. Маршрутизация событий └──*──[ 1 ] │ ├──Email │ │ │ └──”bus@mail.ru”──[ 8 ] │ └──Publisher │ └──"storage"──[ 2 ] │ └──Event │ └──"change"──[ 3, 7 ] │ └──Email ├──"bus@mail.ru"──[ 4 ] └──"bar@mail.ru"──[ 5 ] [ 1 ] storage:change { email: "bus@mail.ru" }
  • 54. Маршрутизация событий └──*──[ 1 ] │ ├──Email │ │ │ └──”bus@mail.ru”──[ 8 ] │ └──Publisher │ └──"storage"──[ 2 ] │ └──Event │ └──"change"──[ 3, 7 ] │ └──Email ├──"bus@mail.ru"──[ 4 ] └──"bar@mail.ru"──[ 5 ] [ 1 ] storage:change { email: "bus@mail.ru" }
  • 55. Маршрутизация событий └──*──[ 1 ] │ ├──Email │ │ │ └──"bus@mail.ru"──[ 8 ] │ └──Publisher │ └──"storage"──[ 2 ] │ └──Event │ └──"change"──[ 3, 7 ] │ └──Email ├──"bus@mail.ru"──[ 4 ] └──"bar@mail.ru"──[ 5 ] [ 1 ] storage:change { email: "bus@mail.ru" }
  • 56. Маршрутизация событий └──*──[ 1 ] │ ├──Email │ │ │ └──"bus@mail.ru"──[ 8 ] │ └──Publisher │ └──"storage"──[ 2 ] │ └──Event │ └──"change"──[ 3, 7 ] │ └──Email ├──"bus@mail.ru"──[ 4 ] └──"bar@mail.ru"──[ 5 ] [ 1, 8 ] storage:change { email: "bus@mail.ru" }
  • 57. Маршрутизация событий └──*──[ 1 ] │ ├──Email │ │ │ └──"bus@mail.ru"──[ 8 ] │ └──Publisher │ └──"storage"──[ 2 ] │ └──Event │ └──"change"──[ 3, 7 ] │ └──Email ├──"bus@mail.ru"──[ 4 ] └──"bar@mail.ru"──[ 5 ] [ 1, 8 ] storage:change { email: "bus@mail.ru" }
  • 58. Маршрутизация событий └──*──[ 1 ] │ ├──Email │ │ │ └──"bus@mail.ru"──[ 8 ] │ └──Publisher │ └──"storage"──[ 2 ] │ └──Event │ └──"change"──[ 3, 7 ] │ └──Email ├──"bus@mail.ru"──[ 4 ] └──"bar@mail.ru"──[ 5 ] [ 1, 8 ] storage:change { email: "bus@mail.ru" }
  • 59. Маршрутизация событий └──*──[ 1 ] │ ├──Email │ │ │ └──"bus@mail.ru"──[ 8 ] │ └──Publisher │ └──"storage"──[ 2 ] │ └──Event │ └──"change"──[ 3, 7 ] │ └──Email ├──"bus@mail.ru"──[ 4 ] └──"bar@mail.ru"──[ 5 ] [ 1, 8, 2 ] storage:change { email: "bus@mail.ru" }
  • 60. Маршрутизация событий └──*──[ 1 ] │ ├──Email │ │ │ └──"bus@mail.ru"──[ 8 ] │ └──Publisher │ └──"storage"──[ 2 ] │ └──Event │ └──"change"──[ 3, 7 ] │ └──Email ├──"bus@mail.ru"──[ 4 ] └──"bar@mail.ru"──[ 5 ] [ 1, 8, 2 ] storage:change { email: "bus@mail.ru" }
  • 61. Маршрутизация событий └──*──[ 1 ] │ ├──Email │ │ │ └──"bus@mail.ru"──[ 8 ] │ └──Publisher │ └──"storage"──[ 2 ] │ └──Event │ └──"change"──[ 3, 7 ] │ └──Email ├──"bus@mail.ru"──[ 4 ] └──"bar@mail.ru"──[ 5 ] [ 1, 8, 2 ] storage:change { email: "bus@mail.ru" }
  • 62. Маршрутизация событий └──*──[ 1 ] │ ├──Email │ │ │ └──"bus@mail.ru"──[ 8 ] │ └──Publisher │ └──"storage"──[ 2 ] │ └──Event │ └──"change"──[ 3, 7 ] │ └──Email ├──"bus@mail.ru"──[ 4 ] └──"bar@mail.ru"──[ 5 ] [ 1, 8, 2, 3, 7 ] storage:change { email: "bus@mail.ru" }
  • 63. Маршрутизация событий └──*──[ 1 ] │ ├──Email │ │ │ └──"bus@mail.ru"──[ 8 ] │ └──Publisher │ └──"storage"──[ 2 ] │ └──Event │ └──"change"──[ 3, 7 ] │ └──Email ├──"bus@mail.ru"──[ 4 ] └──"bar@mail.ru"──[ 5 ] [ 1, 8, 2, 3, 7 ] storage:change { email: "bus@mail.ru" }
  • 64. Маршрутизация событий └──*──[ 1 ] │ ├──Email │ │ │ └──"bus@mail.ru"──[ 8 ] │ └──Publisher │ └──"storage"──[ 2 ] │ └──Event │ └──"change"──[ 3, 7 ] │ └──Email ├──"bus@mail.ru"──[ 4 ] └──"bar@mail.ru"──[ 5 ] [ 1, 8, 2, 3, 7 ] storage:change { email: "bus@mail.ru" }
  • 65. Маршрутизация событий └──*──[ 1 ] │ ├──Email │ │ │ └──"bus@mail.ru"──[ 8 ] │ └──Publisher │ └──"storage"──[ 2 ] │ └──Event │ └──"change"──[ 3, 7 ] │ └──Email ├──"bus@mail.ru"──[ 4 ] └──"bar@mail.ru"──[ 5 ] [ 1, 8, 2, 3, 7, 4 ] storage:change { email: "bus@mail.ru" }
  • 66. Маршрутизация событий [ 1, 8, 2, 3, 7, 4 ] storage:change { email: "bus@mail.ru" }
  • 68. WebSocket Бинарный двухсторонний протокол общения между браузером и сервером. • Стандартизован в 2011г. как RFC6455. • Поддерживается всеми современными браузерами.
  • 69. WebSocket Использует HTTP для Upgrade. После оперирует «фреймами» внутри соединения: • Контрольные: ping, pong, close • Пользовательские: text, binary
  • 70. WebSocket 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+
  • 71. 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+ WebSocket
  • 72. WebSocket 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+
  • 73. WebSocket 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+
  • 74. WebSocket 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+
  • 75. WebSocket Браузеры по-разному реализуют RFC6455. • Chrome не дожидается ответного Close фрейма и сразу закрывает соединение
  • 76. WebSocket Браузеры по-разному реализуют RFC6455. • Chrome не дожидается ответного Close фрейма и сразу закрывает соединение • Firefox периодически посылает Ping фреймы, ожидая в ответ Pong
  • 77. WebSocket Браузеры по-разному реализуют RFC6455: • Chrome не дожидается ответного Close фрейма и сразу закрывает соединение • Firefox периодически посылает Ping фреймы, ожидая в ответ Pong • IE периодически посылает.. Pong фреймы (но это не противоречит спецификации)
  • 78. Трудности • 3 миллиона «живых» соединений • время жизни соединения от нескольких секунд до нескольких дней • при разрыве соединений клиенты (браузер) переподключаются
  • 79. Go
  • 80. Go • Компилируемый, со строгой статической типизацией • Только структуры и интерфейсы • Управление памятью с помощью сборщика мусора • Каналы и «горутины» для конкурентного программирования
  • 82. Go net.Conn package "net" type Conn interface { Read(b []byte) (n int, err error) Write(b []byte) (n int, err error) ... }
  • 83. WebSocket Channel type Channel struct { conn net.Conn }
  • 84. WebSocket Channel type Channel struct { conn net.Conn out chan Packet // Output packet queue. }
  • 85. WebSocket Channel type Channel struct { conn net.Conn out chan Packet } func NewChannel(conn net.Conn) *Channel { c := &Channel{...} }
  • 86. WebSocket Channel type Channel struct { conn net.Conn out chan Packet } func NewChannel(conn net.Conn) *Channel { c := &Channel{...} go c.reader() }
  • 87. WebSocket Channel type Channel struct { conn net.Conn out chan Packet } func NewChannel(conn net.Conn) *Channel { c := &Channel{...} go c.reader() go c.writer() }
  • 88. type Channel struct { conn net.Conn out chan Packet } func NewChannel(conn net.Conn) *Channel { c := &Channel{...} go c.reader() // stack size += 4KB go c.writer() } WebSocket Channel
  • 89. type Channel struct { conn net.Conn out chan Packet } func NewChannel(conn net.Conn) *Channel { c := &Channel{...} go c.reader() // stack size += 4KB go c.writer() // stack size += 4KB } WebSocket Channel
  • 90. 24GB(4KB + 4KB) * 3M WebSocket Channel
  • 91. WebSocket Channel func (c *Channel) reader() { }
  • 92. WebSocket Channel func (c *Channel) reader() { buf := bufio.NewReader(c.conn) }
  • 93. WebSocket Channel func (c *Channel) reader() { buf := bufio.NewReader(c.conn) // heap size += 4KB }
  • 94. WebSocket Channel func (c *Channel) reader() { buf := bufio.NewReader(c.conn) for { } }
  • 95. WebSocket Channel func (c *Channel) reader() { buf := bufio.NewReader(c.conn) for { buf.Read() } }
  • 96. WebSocket Channel func (c *Channel) reader() { buf := bufio.NewReader(c.conn) for { buf.Read() // wait incoming bytes } }
  • 97. WebSocket Channel func (c *Channel) writer() { }
  • 98. WebSocket Channel func (c *Channel) writer() { buf := bufio.NewWriter(c.conn) }
  • 99. WebSocket Channel func (c *Channel) writer() { buf := bufio.NewWriter(c.conn) // heap size += 4KB }
  • 100. WebSocket Channel func (c *Channel) writer() { buf := bufio.NewWriter(c.conn) for packet := range c.out { } }
  • 101. WebSocket Channel func (c *Channel) writer() { buf := bufio.NewWriter(c.conn) for packet := range c.out { buf.Write(...) } }
  • 102. func (c *Channel) writer() { buf := bufio.NewWriter(c.conn) for packet := range c.out { // wait outgoing packets buf.Write(...) } } WebSocket Channel
  • 103. 48GB(4KB + 4KB) * 3M (4KB + 4KB) * 3M WebSocket Channel
  • 105. WebSocket Channel import "net/http" http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { }, )
  • 106. WebSocket Channel import “net/http" http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { // response write buffer; heap size += 4KB // request read buffer; heap size += 4KB }, )
  • 107. WebSocket Channel import “net/http" http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { }, )
  • 108. WebSocket Channel import "net/http" import "some/websocket" http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { // Upgrade HTTP conn to WebSocket somehow. conn := websocket.Upgrade(r, w) }, )
  • 109. 72GB(4KB + 4KB) * 3M (4KB + 4KB) * 3M (4KB + 4KB) * 3M WebSocket Channel
  • 110.
  • 112. epoll epoll - I/O event notification facility (c) man epoll.
  • 113. Как работает go runtime? func (c *Channel) reader() { for { conn.Read() // wait incoming bytes } }
  • 114. Как работает go runtime? func read(c *conn, p []byte) (int, error) { }
  • 115. Как работает go runtime? func read(c *conn, p []byte) (int, error) { n, err := syscall.Read(c.fd, p) }
  • 116. Как работает go runtime? func read(c *conn, p []byte) (int, error) { n, err := syscall.Read(c.fd, p) if err.IsTemp() { } }
  • 117. Как работает go runtime? func read(c *conn, p []byte) (int, error) { n, err := syscall.Read(c.fd, p) if err.IsTemp() { // EAGAIN || EWOULDBLOCK } }
  • 118. Как работает go runtime? func read(c *conn, p []byte) (int, error) { n, err := syscall.Read(c.fd, p) if err.IsTemp() { pollWait(c.descriptor, 'r') } }
  • 119. Как работает go runtime? • сокеты в Go неблокирующие • runtime_pollWait на Linux реализован с помощью Epoll • почему бы не использовать похожий подход?
  • 122. epoll ch := NewChannel(conn) epoll.Add(conn, func() { // EPOLLIN | EPOLLONESHOT })
  • 123. epoll ch := NewChannel(conn) epoll.Add(conn, func() { epoll.Resume(conn) })
  • 124. epoll ch := NewChannel(conn) epoll.Add(conn, func() { epoll.Resume(conn) }) func Receive(ch *Channel) { }
  • 125. epoll ch := NewChannel(conn) epoll.Add(conn, func() { epoll.Resume(conn) }) func Receive(ch *Channel) { buf := bufio.NewReader(ch.conn) buf.Read() }
  • 126. epoll ch := NewChannel(conn) epoll.Add(conn, func() { epoll.Resume(conn) }) func Receive(ch *Channel) { buf := bufio.NewReader(ch.conn) buf.Read() }
  • 127. epoll ch := NewChannel(conn) epoll.Add(conn, func() { Receive(ch) epoll.Resume(conn) }) func Receive(ch *Channel) { buf := bufio.NewReader(ch.conn) buf.Read() }
  • 128. Запись С отправкой пакетов дело обстоит проще – мы всегда знаем, когда хотим что-то записать в соединение. Единственный сложный момент – синхронизация записи из разных горутин.
  • 129. Запись func (ch *Channel) Send(p Packet) { }
  • 130. Запись func (ch *Channel) Send(p Packet) { if notSpawnedYet { } }
  • 131. Запись func (ch *Channel) Send(p Packet) { if notSpawnedYet { go ch.writer() } }
  • 132. Запись func (ch *Channel) Send(p Packet) { if notSpawnedYet { go ch.writer() } ch.out <- p }
  • 133. -48GB(4KB + 4KB) * 3M (4KB + 4KB) * 3M
  • 135. Контроль ресурсов Что, если вдруг все соединения решат отправить нам сообщение? Тогда реализация с epoll ничем не будет отличаться от изначальной idiomatic реализации.
  • 136. Goroutine pool p := pool.New(128) pool.Schedule(func() { // Do some work. }) pool.ScheduleTimeout(time.Second, func() { // Do some work or return error. })
  • 137. Goroutine pool epoll.Add(conn, func() { Receive(ch) epoll.Resume(conn) })
  • 138. Goroutine pool p := pool.New(128) epoll.Add(conn, func() { pool.Schedule(func() { Receive(ch) }) epoll.Resume(conn) })
  • 139. Запись func (ch *Channel) Send(p Packet) { if notSpawnedYet { go ch.writer() } ch.out <- p }
  • 140. Запись p := pool.New(128) func (ch *Channel) Send(p Packet) { if notSpawnedYet { p.Schedule(ch.writer) } ch.out <- p }
  • 142. Zero-copy upgrade GET /ws HTTP/1.1 Host: mail.ru Connection: Upgrade Sec-Websocket-Key: A3xNe7sEB9HixkmBhVrYaA== Sec-Websocket-Version: 13 Upgrade: websocket HTTP/1.1 101 Switching Protocols Connection: Upgrade Sec-Websocket-Accept: ksu0wXWG+YmkVx+KQR2agP0cQn4= Upgrade: websocket
  • 143. Zero-copy upgrade import "net/http" import "some/websocket" http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { conn := websocket.Upgrade(r, w) }, )
  • 144. Zero-copy upgrade import "net" ln := net.Listen("tcp", ":8080") for { conn := ln.Accept() }
  • 145. Zero-copy upgrade import "net" ln := net.Listen("tcp", ":8080") for { conn := ln.Accept() // How to upgrade raw bytes? }
  • 146. Время запилить свою либу В Go существуют две реализации WebSocket. И обе они не позволяют полностью контролировать работу с аллокацией памяти. Поэтому пришлось реализовать RFC6455 с более низкоуровневым API.
  • 147. github.com/gobwas/ws • работает с io.Reader, io.Writer • умеет zero-copy upgrade • позволяет кешировать фреймы • проходит autobahn test suite
  • 148. Zero-copy upgrade BenchmarkUpgradeHTTP 5156 ns/op 8576 B/op 9 allocs/op BenchmarkUpgradeTCP 973 ns/op 0 B/op 0 allocs/op В случае простого Upgrade без сохранения значений заголовков. Код бенчмарков: http://bit.ly/2svISpr.
  • 150. Все вместе При использовании goroutine pool можно вообще не принимать соединения, если ресурсов больше нет. Это решает многие проблемы DDOS в случае нештатных ситуаций.
  • 151. import "net" import "github.com/gobwas/ws" ln := net.Listen("tcp", ":8080") for { }
  • 152. import "net" import "github.com/gobwas/ws" ln := net.Listen("tcp", ":8080") for { err := pool.ScheduleTimeout( ) }
  • 153. import "net" import "github.com/gobwas/ws" ln := net.Listen("tcp", ":8080") for { err := pool.ScheduleTimeout( time.Millisecond, func() { }, ) }
  • 154. import "net" import "github.com/gobwas/ws" ln := net.Listen("tcp", ":8080") for { err := pool.ScheduleTimeout( time.Millisecond, func() { conn := ln.Accept() ws.UpgradeConn(conn) }, ) }
  • 155. import "net" import "github.com/gobwas/ws" ln := net.Listen("tcp", ":8080") for { err := pool.ScheduleTimeout( time.Millisecond, func() { conn := ln.Accept() ws.UpgradeConn(conn) }, ) if err != nil { } }
  • 156. import "net" import "github.com/gobwas/ws" ln := net.Listen("tcp", ":8080") for { err := pool.ScheduleTimeout( time.Millisecond, func() { conn := ln.Accept() ws.UpgradeConn(conn) }, ) if err != nil { time.Sleep(time.Millisecond) } }
  • 157. Все вместе • При большом количестве бездействующих соединений лучше использовать epoll и отказаться от читающей горутины • При большом количестве соединений отказаться от пишущей горутины • Goroutine pool позволяет зафиксировать количество максимально потребляемой памяти • Goroutine pool позволяет ограничить количество обрабатываемых соединений
  • 159. Итого Событийная модель позволяет: • реализовать действительно интерактивный сервис • предлагать пользователям множество дополнительных функций • снизить сопряженность сервисов и упростить взаимодействие
  • 160. Цифры • 3 миллиона “живых” соединений • 30 тысяч уведомлений в секунду от storage • 9 тысяч из которых доходят до пользователей ежесекундно • 75 тысяч событий EPOLLIN (доступность для чтения) в секунду • 1 тысяча Upgrade запросов в секунду • в ходе оптимизаций количество памяти на одно соединение сократилось с 60KB до 10KB (и это не все!)
  • 161. PROFIT