Архитектура высоких нагрузок для web приложений

Thank you for reading this post, don't forget to subscribe!

Вы нико­гда не зна­е­те, что про­изой­дет с при­ло­же­ни­ем зав­тра. Воз­мож­но, коли­че­ство ваших поль­зо­ва­те­лей уве­ли­чить­ся в 5 раз. Воз­мож­но, рез­ко нач­нет наби­рать попу­ляр­ность вто­ро­сте­пен­ная функ­ция. И она создаст новые про­бле­мы. Чем боль­ше будет ста­но­вить­ся систе­ма, тем более слож­ным (= менее эффек­тив­ным) будет ста­но­вить­ся дол­го­сроч­ное планирование.

Успеш­ность рабо­ты над круп­ным при­ло­же­ни­ем под­ра­зу­ме­ва­ет вовсе не деталь­ное пла­ни­ро­ва­ние всех аспек­тов. Основ­ное уси­лие долж­но быть направ­ле­но на обес­пе­че­ние гиб­ко­сти систе­мы. Гиб­кость поз­во­лит быст­ро вно­сить изме­не­ния. Это наи­бо­лее важ­ное свой­ство любой быст­ро­рас­ту­щей системы.

Постепенный рост

Не пытай­тесь спро­гно­зи­ро­вать объ­ем ауди­то­рии на год впе­ред. То же самое каса­ет­ся и архи­тек­ту­ры при­ло­же­ния. Осно­ва успеш­ной раз­ра­бот­ки — посте­пен­ные реше­ния. Это при­ме­ни­мо и к про­грамм­ной и к аппа­рат­ной части.

Если Вы запус­ка­е­те новое при­ло­же­ние, нет смыс­ла сра­зу обес­пе­чи­вать инфра­струк­ту­ру, кото­рая спо­соб­на выдер­жать мил­ли­о­ны посе­ти­те­лей. Исполь­зуй­те обла­ка для хостин­га новых про­ек­тов. Это поз­во­лит сни­зить затра­ты на сер­ве­ра и упро­стить их управление.

Мно­гие облач­ные хостин­ги предо­став­ля­ют услу­ги при­ват­ной сети. Это поз­во­лит без­опас­но исполь­зо­вать несколь­ко сер­ве­ров пря­мо в обла­ке. Таким обра­зом, вы смо­же­те выпол­нять пер­вые шаги в мас­шта­би­ро­ва­нии без пере­ез­да на физику.

Простые решения

Про­стые реше­ния раз­ра­ба­ты­вать крайне слож­но. Тем не менее, луч­ше потра­тить вре­мя и уси­лия на упро­ще­ние реше­ний (как для раз­ра­бот­ки так и для поль­зо­ва­те­лей). Гиб­кая систе­ма не быва­ет сложной.

Архитектурные решения

Мас­шта­би­ро­ва­ние любо­го Web при­ло­же­ния — это посте­пен­ный про­цесс, кото­рый включает:

  1. Ана­лиз нагрузки.
  2. Опре­де­ле­ние наи­бо­лее под­вер­жен­ных нагруз­ке участков.
  3. Выне­се­ние таких участ­ков на отдель­ные узлы и их оптимизация.
  4. Повтор пунк­та 1.

1. Простая архитектура Web приложения

Новое при­ло­же­ние обыч­но запус­ка­ет­ся на одном сер­ве­ре, на кото­ром рабо­та­ют и Web сер­вер и база дан­ных и само приложение:

Это разум­но, т.к. это эко­но­мит вре­мя и день­ги на запуск. Исполь­зуй­те имен­но такой под­ход для стар­та. Если бои­тесь не выдер­жать стар­то­вую нагруз­ку — возь­ми­те мощ­ный сер­вер в арен­ду. Толь­ко в исклю­чи­тель­ных ситу­а­ци­ях, когда вы абсо­лют­но точ­но уве­ре­ны в боль­шой стар­то­вой нагруз­ке, пере­хо­ди­те сра­зу к тому, что опи­са­но ниже.

2. Отделение базы данных

Чаще все­го, пер­вым узлом, кото­рый ока­зы­ва­ет­ся под нагруз­кой, явля­ет­ся база дан­ных. Это понят­но. Каж­дый запрос от поль­зо­ва­те­ля к при­ло­же­нию — это обыч­но от 10 до 100 запро­сов к базе данных:

Выне­се­ние базы дан­ных на отдель­ный сер­вер поз­во­лит уве­ли­чить ее про­из­во­ди­тель­ность и сни­зить ее нега­тив­ное вли­я­ние на осталь­ные ком­по­нен­ты (PHP, Nginx и т.п.). Для под­клю­че­ния к MySQL на отдель­ном сер­ве­ре исполь­зуй­те IP адрес это­го сервера:

10.10.0.2 — IP адрес MySQL во внут­рен­ней сети

Пауза в работе при переносе

Пере­нос базы дан­ных на дру­гой сер­вер может стать про­бле­мой для рабо­та­ю­ще­го при­ло­же­ния, т.к. зай­мет какое-то вре­мя. Вы можете:

  • Исполь­зо­вать про­стое реше­ние — раз­ме­стить объ­яв­ле­ние о пла­но­вых рабо­тах на сай­те и сде­лать пере­нос. Луч­ше это делать глу­бо­кой ночью, когда актив­ность ауди­то­рии минимальна.
  • Исполь­зо­вать репли­ка­цию для син­хро­ни­за­ции дан­ных с одно­го сер­ве­ра на дру­гой. В этом слу­чае масте­ром будет ста­рый сер­вер, а слей­вом новый. После настрой­ки доста­точ­но поме­нять IP адрес базы в при­ло­же­нии на новый сер­вер. А далее — выклю­чить ста­рый сер­вер. Читай­те как настро­ить MySQL репли­ка­цию на рабо­та­ю­щем сер­ве­ре без простоев.

После выде­ле­ния MySQL на отдель­ный сер­вер, убе­ди­тесь в его опти­маль­ной настройке.

Мас­шта­би­ро­ва­ние баз дан­ных — одна из самых слож­ных задач во вре­мя роста про­ек­та. Суще­ству­ет очень мно­го прак­тик — денор­ма­ли­за­ция, репли­ка­ции, шар­динг и мно­гие дру­гие. Читай­те мате­ри­а­лы по мас­шта­би­ро­ва­нию БД.

3. Отделение Web сервера

Далее на оче­ре­ди Web сер­вер. Его выде­ле­ние на отдель­ный узел поз­во­лит оста­вить боль­ше ресур­сов для при­ло­же­ния (в при­ме­ре — PHP):

В этом слу­чае Вам при­дет­ся настро­ить deployment при­ло­же­ния и на сер­вер Nginx и на сер­вер с PHP. Сер­вер PHP обыч­но назы­ва­ют бекен­дом. Тогда Nginx будет отда­вать фай­лы ста­ти­ки само­сто­я­тель­но, а PHP сер­вер будет занят толь­ко обра­бот­кой скрип­тов. Nginx поз­во­ля­ет под­клю­чать­ся к бекен­ду по IP адресу:

[codesyntax lang="php"]

[/codesyntax]

Все фай­лы ста­ти­ки Nginx будет отда­вать без обра­ще­ния на бекенд

Если Вы исполь­зу­е­те загруз­ку фай­лов, вам нуж­но будет выде­лить фай­ло­вое хра­ни­ли­ще на отдель­ный узел (об этом — ниже).

4. Несколько PHP бекендов

Когда нагруз­ка рас­тет, Web при­ло­же­ние посте­пен­но начи­на­ет рабо­тать все мед­лен­нее. В какой-то момент при­чи­на будет лежать уже в самой реа­ли­за­ции. Тогда сто­ит уста­но­вить несколь­ко PHP бекендов:

Все бекен­ды важ­но иметь оди­на­ко­вой кон­фи­гу­ра­ции. Nginx уме­ет балан­си­ро­вать нагруз­ку меж­ду ними. Для это­го Вам необ­хо­ди­мо выде­лить спи­сок бекен­дов в upstream и исполь­зо­вать его в конфигурации:

[codesyntax lang="php"]

[/codesyntax]

 Nginx будет рав­но­мер­но рас­пре­де­лять нагруз­ку меж­ду ука­зан­ны­ми бекендами

Вы може­те исполь­зо­вать веса бекен­дов, если одни из них мощ­нее, чем другие:

[codesyntax lang="php"]

[/codesyntax]

Из каж­дых 16 запро­сов, пер­вый бекенд обра­бо­та­ет 10, вто­рой — 2, а тре­тий — 4

Сессии

Как толь­ко вы нач­не­те исполь­зо­вать несколь­ко бекен­дов, запро­сы от одно­го поль­зо­ва­те­ля будут попа­дать на раз­ные сер­ве­ра. Это потре­бу­ет исполь­зо­ва­ния еди­но­го хра­ни­ли­ща для сес­сий, напри­мер Memcache.

5. Кэширование

Под­клю­че­ние сер­ве­ров кэши­ро­ва­ния — одна из самых про­стых задач:

Memcache обес­пе­чи­ва­ет исполь­зо­ва­ние несколь­ких сер­ве­ров в стан­дарт­ной поставке:

[codesyntax lang="php"]

[/codesyntax]

Под­клю­ча­ем Memcache к несколь­ким сер­ве­рам сразу

Memcache само­сто­я­тель­но рас­пре­де­лит нагруз­ку меж­ду исполь­зу­е­мы­ми сер­ве­ра­ми. Для это­го он исполь­зу­ет алго­ритм посто­ян­но­го хеши­ро­ва­ния. Вам пона­до­бит­ся сле­дить за вытес­не­ни­я­ми и вовре­мя добав­лять новое оборудование.

6. Очереди задач

Оче­ре­ди задач поз­во­ля­ют выпол­нять тяже­лые опе­ра­ции асин­хрон­но, не замед­ляя основ­но­го приложения.

Сер­вер оче­ре­ди при­ни­ма­ет зада­чи от при­ло­же­ния. Worker-сер­ве­ра обра­ба­ты­ва­ют зада­чи. Их коли­че­ство сле­ду­ет уве­ли­чи­вать, когда сред­нее коли­че­ство задач в оче­ре­ди будет посте­пен­но расти.

7. Балансировка DNS

DNS под­дер­жи­ва­ет балан­си­ров­ку на осно­ве Round Robin. Это поз­во­ля­ет ука­зать несколь­ко IP адре­сов при­ни­ма­ю­щих Web сер­ве­ров (назы­ва­ют­ся фронтендами):

Для исполь­зо­ва­ния это­го меха­низ­ма, необ­хо­ди­мо уста­но­вить несколь­ко оди­на­ко­вых фрон­тен­дов. Тогда в DNS сле­ду­ет ука­зы­вать такие А записи:

[codesyntax lang="php"]

[/codesyntax]

Исполь­зу­ем несколь­ко IP адре­сов для одной А записи

В этом слу­чае DNS будет отда­вать раз­ные IP адре­са раз­ным кли­ен­там. Таким обра­зом будет про­ис­хо­дить балан­си­ров­ка меж­ду фронтендами.

8. Файловые хранилища

Загруз­ка и обра­бот­ка фай­лов обыч­но про­ис­хо­дит на бекен­де. Когда бекен­дов несколь­ко, это неудобно:

  • При­дет­ся пом­нить, на какой бекенд был загру­жен файл.
  • Загруз­ка и обра­бот­ка фай­лов (напри­мер, видео или фото) может силь­но сни­жать про­из­во­ди­тель­ность бекенда.
  • При­дет­ся исполь­зо­вать сер­ве­ра с боль­ши­ми дис­ка­ми, в чем обыч­но нет необ­хо­ди­мо­сти для бекендов.

Пра­виль­ным реше­ни­ем будет исполь­зо­ва­ние отдель­ных сер­ве­ров для загруз­ки, хра­не­ния и обра­бот­ки файлов:

На прак­ти­ке это реа­ли­зу­ет­ся так:

  1. Выде­ля­ет­ся отдель­ный суб­до­мен для сер­ве­ра файлов.
  2. На сер­ве­ре раз­во­ра­чи­ва­ет­ся Nginx и неболь­шое при­ло­же­ние, кото­рое уме­ет сохра­нять (и обра­ба­ты­вать, если нуж­но) файлы.
  3. Мас­шта­би­ро­ва­ние про­ис­хо­дит путем добав­ле­ния новых сер­ве­ров и суб­до­ме­нов (images1, images2, images3 и т.п.).

Загрузка файлов

Загруз­ку удоб­но пере­кла­ды­вать на кли­ент­скую сто­ро­ну. Тогда фор­ма будет отправ­лять запрос на кон­крет­ный сервер:

[codesyntax lang="php"]

[/codesyntax]

Доме­ны мож­но гене­ри­ро­вать слу­чай­ным обра­зом из уже существующих:

AJAX загрузка

Не забы­ва­ем про CORS:

Это поз­во­лит отправ­лять AJAX запро­сы с доме­на test.com на доме­ны загрузки