Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

170 views

Published on

РИТ++ 2017, Backend Conf
Зал Кейптаун, 6 июня, 17:00

Тезисы:
http://backendconf.ru/2017/abstracts/2748.html

Большое количество живых соединений с сервером требует решения интересных и порой неоднозначных задач. Речь будет идти о том, как в условиях доставки тысяч уведомлений в секунду миллионам пользователей иметь возможность управлять потребляемыми ресурсами, производить безболезненные рестарты и при этом иметь "план Б" на случай непредвиденных проблем.

Доклад расскажет историю запуска системы уведомлений между сервисами mail.ru и их пользователями.
...

Published in: Engineering
  • Be the first to comment

  • Be the first to like this

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

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

×