Современный веб практически немыслим без медиаконтента: смартфоны есть практически у каждой нашей бабушки, все сидят в соцсетях, и простои в обслуживании дорого обходятся компаниям. Вашему вниманию расшифровка рассказа компании Badoo о том, как она организовала отдачу фотографий с помощью аппаратного решения, с какими проблемами производительности столкнулась в процессе, чем они были вызваны, ну и как эти проблемы были решены с помощью софтового решения на основе Nginx, обеспечив при этом отказоустойчивость на всех уровнях (видео). Благодарим авторов рассказа Олега Sannis Ефимова и Александра Дымова, которые поделились своим опытом на конференции Uptime day 4.
— Начнем с небольшого введения о том, как мы храним и кэшируем фотографии. У нас есть слой, на котором мы их храним, и слой, где мы фотографии кэшируем. При этом, если мы хотим добиваться большого хитрейта и снижать нагрузку на стораджи, нам важно, чтобы каждая фотография отдельного пользователя лежала на одном кэширующем сервере. Иначе нам пришлось бы ставить во столько раз больше дисков, во сколько у нас больше серверов. Хитрейт у нас в районе 99%, то есть мы в 100 раз снижаем нагрузку на наши storage, и для того, чтобы это сделать, еще 10 лет назад, когда все это строилось, у нас было 50 серверов. Соответственно, для того, чтобы эти фотографии отдавать, нам нужно было по сути 50 внешних доменов, которые эти серверы обслуживают.
Естественно, сразу встал вопрос: а если у нас один сервер упадет, будет недоступен, какую часть трафика мы теряем? Мы посмотрели, что есть на рынке, и решили купить железку, чтобы она решила все наши проблемы. Выбор пал на решение компании F5-network (которая, кстати, не так давно купила NGINX, Inc): BIG-IP Local Traffic Manager.
Что эта железка (LTM) делает: это железный роутер, который делает железное redundancy своих внешних портов и позволяет роутить трафик, основываясь на топологии сети, на каких-то настройках, делает health-check’и. Нам было важно то, что эту железку можно программировать. Соответственно, мы могли описать логику того, как фотографии определенного пользователя отдавались с какого-то конкретного кэша. Как это выглядит? Есть железка, которая смотрит в интернет по одному домену, одному ip, делает ssl offload, разбирает http-запросы, из IRule выбирает номер кэша, куда пойти, и пускает туда трафик. При этом она делает health-cheсk’и, и в случае недоступности какой-то машины мы сделали на тот момент так, чтобы трафик шел на один резервный сервер. С точки зрения конфигурирования есть, конечно, некоторые нюансы, но в целом все довольно просто: мы прописываем карту, соответствие какого-то числа нашему IP в сети, говорим, что слушать мы будем на 80-м и 443-м портах, говорим, что, если сервер недоступен, то нужно пускать трафик на резервный, в данном случае 35-й, и описываем кучу логики, как эту архитектуру надо разбирать. Единственная проблема была в том, что язык, которым программируется железка, это язык Tcl. Если кто вообще такой помнит… язык этот больше write-only, чем язык, удобный для программирования:
Что мы получили? Мы получили железку, которая обеспечивает высокую доступность нашей инфраструктуры, роутит весь наш трафик, обеспечивает health-cheсk’и и просто работает. Причем работает довольно долго: за последние 10 лет к ней не было никаких нареканий. К началу 2018 года мы уже отдавали около 80k фотографий в секунду. Это где-то примерно 80 гигабит трафика с обоих наших дата-центров.
Однако…
В начале 2018 года мы на графиках увидели некрасивую картину: время отдачи фотографий явно выросло. И оно перестало нас устраивать. Проблема в том, что такое поведение было видно только в самый пик трафика — для нашей компании это ночь с воскресенья на понедельник. Но все остальное время система вела себя как обычно, никаких признаков поломки.
Тем не менее, проблему надо было решать. Мы определили возможные узкие места и начали их ликвидировать. В первую очередь, конечно, мы расширили uplinks внешние, провели полную ревизию внутренних uplinks, нашли все возможные узкие места. Но очевидного результата всё это не дало, проблема не исчезла.
Еще одним возможным узким местом была производительность самих фото-кэшей. И мы решили, что, возможно, проблема упирается в них. Что ж, расширили производительность — в основном, сетевые порты на фотокэшах. Но снова никакого явного улучшения не видели. В конце концов, обратили пристальное внимание на производительность самого LTM-а, и вот тут увидели на графиках печальную картину: загрузка всех CPU начинает идти плавно, но потом резко упирается в полку. При этом LTM перестает адекватно реагировать на health-check'и uplink'ов и начинает их случайным образом выключать, что ведет к серьезной деградации производительности.
То есть мы определили источник проблемы, определили узкое место. Осталось решить, что же мы будем делать.
Первое, само напрашивающееся, что мы могли предпринять, — это как-то модернизировать сам LTM. Но тут есть свои нюансы, потому что это железо достаточно уникальное, ты не пойдешь в ближайший супермаркет и не купишь. Это отдельный контракт, отдельный контракт на лицензию, и это займет немало времени. Второй вариант — начать думать самому, придумать свое решение на своих компонентах, желательно с использованием программы с открытым доступом. Осталось только решить, что именно мы выберем для этого и сколько времени потратим на решение этой проблемы, ведь пользователи недополучали фотографий. Стало быть, надо делать все это очень-очень быстро, можно сказать — вчера.
Так как задача звучала как «сделать что-то максимально быстро и используя то железо, которое у нас есть», первое, что мы подумали, — это просто снять с фронта какие-нибудь не самые мощные машины, поставить туда Nginx, с которым мы умеем работать, и попробовать реализовать всю ту самую логику, которую раньше делала железка. То есть фактически мы оставляли нашу железку, ставили еще 4 сервера, которые должны были сконфигурировать, делали для них внешние домены по аналогии с тем, как это было 10 лет назад… Мы немного теряли в доступности в случае падения этих машин, но, тем не менее, решали локально проблему наших пользователей.
Соответственно, логика остается той же самой: мы ставим Nginx, он умеет делать SSL-offload, мы можем на конфигах как-то спрограммировать логику роутинга, health-check'и и просто продублировать ту логику, которая у нас была до этого.
Садимся писать конфиги. Сначала казалось, что все было очень просто, но, к сожалению, под каждую задачу найти мануалы очень сложно. Поэтому не советуем просто гуглить «как сконфигурировать Nginx для фотографий»: лучше обратиться к официальной документации, которая покажет, какие настройки стоит трогать. Но конкретный параметр лучше подбирать самим. Ну, а дальше все просто: описываем серверы, которые у нас есть, описываем сертификаты… Но самое интересное — это, собственно, сама логика роутинга.
Поначалу нам казалось, что мы просто описываем наш локейшн, матчим в нем номер нашего фотокэша, руками либо генератором описываем, сколько нам нужно апстримов, в каждом апстриме указываем сервер, на который должен идти трафик, и бэкапный сервер — в случае, если основной сервер недоступен:
Но, наверное, если бы все было бы так просто, мы просто разошлись бы по домам и ничего не рассказывали. К сожалению, с дефолтными настройками Nginx, которые, в общем-то, сделаны за долгие годы разработки и не совсем под этот кейс… конфиг выглядит так: в случае, если у какого-то апстрим-сервера происходит ошибка запроса либо таймаут, Nginx всегда переключает трафик на следующий. При этом после первого фейла в течение 10 секунд сервер также будет выключен, причем и по ошибке, и по таймауту — это даже нельзя никак конфигурировать. То есть если мы уберем или сбросим в апстрим-директиве опцию таймаут, то, хотя Nginx и не будет обрабатывать этот запрос и ответит какой-нибудь не очень хорошей ошибкой, сервер будет выключаться.
Чтобы этого избежать, мы сделали две вещи:
а) запретили делать это Nginx'у руками — и к сожалению, единственный способ сделать это — просто задать настройки max fails.
б) вспомнили, что мы в других проектах используем модуль, который позволяет делать фоновые health-check'и — соответственно, мы сделали довольно частые health-check'и, чтобы простой в случае аварии у нас был минимальным.
К сожалению, это тоже не все, потому что буквально первые две недели работы этой схемы показали, что TCP health-check — тоже штука ненадежная: на апстрим-сервере может быть поднят не Nginx, или Nginx в D-state, и в этом случае ядро будет принимать соединение, health-check будет проходить, а работать не будет. Поэтому мы сразу же заменили это на health-check http'шный, сделали определенный, который если уж отдает 200, то все работает в этом скрипте. Можно делать дополнительную логику — например, в случае кэширующих серверов проверять, что правильно смонтирована файловая система:
И нас бы это устроило, за исключением того, что на данный момент схема полностью повторяла то, что делала железка. Но мы-то хотели сделать лучше. Раньше у нас был один резервный сервер, и, наверное, это не очень хорошо, потому что если серверов у вас сто, то когда падает сразу несколько, один резервный сервер вряд ли справится с нагрузкой. Поэтому мы решили резервирование распределить по всем серверам: сделали просто еще один отдельный апстрим, записали туда все сервера с определенными параметрами в соответствии с тем, какую нагрузку они могут обслуживать, добавили те же самые health-check'и, которые у нас были до этого:
Так как внутри одного апстрима нельзя ходить в другой апстрим, нужно было сделать так, чтобы в случае недоступности основного апстрима, в котором просто записывали правильный, нужный фотокэш, мы просто через error_page шли на fallback, откуда шли на резервный апрстрим:
И буквально добавив четыре сервера, мы вот что получили: заменили часть нагрузки — сняли с LTM на эти сервера, реализовали там ту же логику, используя стандартное железо и софт, сразу же получили бонусом, что эти сервера можно масштабировать, потому что их можно просто поставить столько, сколько нужно. Ну и единственный минус — мы потеряли высокую доступность для внешних пользователей. Но на тот момент пришлось этим пожертвовать, потому что надо было незамедлительно решить проблему. Итак, часть нагрузки мы сняли, это около 40% на тот момент, LTM'у стало хорошо, а буквально через две недели после начала проблемы мы стали отдавать не 45k запросов в секунду, а 55k. По сути, мы выросли на 20% — это явно тот трафик, который мы недоотдавали пользователю. И после этого начали думать, как решить оставшуюся проблему — обеспечить высокую внешнюю доступность.
У нас была некоторая пауза, во время которой мы обсуждали, какое решение мы будем для этого использовать. Были предложения обеспечивать надежность с помощью DNS, с помощью каких-то самописных скриптов, протоколов динамической маршрутизации… вариантов было много, но уже стало ясно, что для по-настоящему надежной отдачи фотографий нужно ввести еще один слой, который будет за этим следить. Мы назвали эти машины photo directors. В качестве программного обеспечения, на которое мы опирались, был выбрал Keepalived:
Для начала — из чего Keepalived состоит. Первое — это протокол VRRP, широко известный сетевикам, расположен на сетевом оборудовании, обеспечивающем отказоустойчивость внешнего IP-адреса, на который соединяются клиенты. Вторая часть это — IPVS, IP virtual server, для балансировки между фото-роутерами и обеспечения отказоустойчивости на этом уровне. И третье — health-check'и.
Начнем с первой части: VRRP — как это выглядит? Есть некий virtual IP, на который есть запись в dns badoocdn.com, куда подключаются клиенты. В какой-то момент времени у нас IP-адрес присутствует на каком-то одном сервере. Между серверами бегают по протоколу VRRP keepalived-пакеты, и в случае если мастер пропадает с радаров — сервер перезагрузился или еще что-нибудь, то бэкапный сервер автоматически поднимает этот IP адрес у себя — не надо делать никаких ручных действий. Отличаются мастер и бэкап, в основном priority: чем оно выше, тем больше шансов, что машина станет мастер. Очень большое достоинство, то что не надо конфигурировать IP-адреса на самом сервере, достаточно описать их в конфиге, и если при этом IP-адресам необходимы какие-то кастомные правила маршрутизации, это описывается прямо в конфиге, тем же синтаксисом, как это описывается в пакете VRRP. Никаких незнакомых вещей вам не встретится.
Как это выглядит на практике? Что происходит, если один из серверов уходит в отказ? Как только мастер пропадает, у нас бэкап прекращает получать адвертисменты и автоматически становится мастером. Через какое-то время мы починили мастер, перезагрузили, подняли Keepalived — приходят адвертисменты с бОльшим приоритетом, чем у бэкапа, и бэкап автоматически превращается обратно, снимает с себя IP-адреса, никаких ручных действий при этом делать не надо.
Таким образом, отказоустойчивость внешнего IP-адреса мы обеспечили. Следующая часть — это с внешнего IP-адреса как-то балансировать трафик на фото-роутеры, которые уже терминируют его. С протоколами балансировки все достаточно ясно. Это либо простой round-robin, либо немножко более сложные вещи, wrr, list connection и так далее. Это в принципе описано в документации, ничего такого особого нет. А вот метод доставки… Тут остановимся поподробнее — почему выбрали один из них. Это NAT, Direct Routing и TUN. Дело в том, что мы сразу закладывались на отдачу 100 гигабит трафика с площадок. Это если прикинуть, нужно 10 гигабитных карточек, правильно? 10 гигабитных карточек в одном сервере — это уже выходит за рамки, по крайней мере, нашего понятия «стандартное оборудование». И тут мы вспомнили, что мы не просто отдаем какой-то трафик, мы отдаем фотографии.
В чем особенность? — Колоссальная разница между входящим и исходящим трафиком. Входящий трафик очень маленький, исходящий очень большой:
Если посмотреть на эти графики, то видно, что в данный момент на директора поступает порядка 200 Мб в секунду, это самый обычный день. Отдаем же мы обратно 4,500 Мб в секунду, соотношение у нас примерно 1/22. Уже понятно, что нам для полного обеспечения исходящего трафика на 22 сервера рабочих достаточно одного, который принимает это соединение. Тут нам на помощь приходит как раз алгоритм direct routing, алгоритм маршрутизации.
Как это выглядит? Фото-директор у нас согласно своей таблице передает соединения на фото-роутеры. Но обратный трафик фото-роутеры отправляют уже напрямую в интернет, отправляют клиенту, он не проходит обратно через фото-директор, таким образом, минимальным количеством машин мы обеспечиваем полную отказоустойчивость и прокачку всего трафика. В конфигах это выглядит следующим образом: мы указываем алгоритм, в нашем случае это простой rr, обеспечиваем метод директ-роутинг и дальше начинаем перечислять все реальные сервера, сколько их у нас есть. Которые будут этот трафик детерминировать. В случае, если у нас там появляются еще один-два, несколько серверов, возникает такая необходимость — просто дописываем эту секцию в конфиге и особо не паримся. Со стороны реальных серверов, со стороны фото-роутера такой метод требует самого минимального конфигурирования, он прекрасно описан в документации, и подводных камней там нет.
Что особенно приятно — такое решение не подразумевает кардинальной переделки локальной сети, для нас это было важно, нам надо было решить это минимальными затратами. Если посмотреть на вывод команды IPVS admin, то мы увидим, как это выглядит. Вот у нас есть некий виртуальный сервер, на 443 порту, слушает, принимает соединение, идет перечисление всех рабочих серверов, и видно, что connection — он, плюс-минус, одинаковый. Если мы посмотрим статистику, на том же виртуальном сервере, — у нас есть входящие пакеты, входящие соединения, но абсолютно отсутствуют исходящие. Исходящие соединения идут напрямую клиенту. Хорошо, разбалансировать мы смогли. Теперь, что будет, если у нас один из фото-роутеров уходит в отказ? Ведь железо есть железо. Может уйти в kernel panic, может сломаться, может сгореть блок питания. Что угодно. Для этого и нужны health-check'и. Они могут быть как самыми простыми — проверка на то, как порт у нас открыт, — так и какая-то более сложными, вплоть до каких-то самописных скриптов, которые будут даже бизнес-логику будут проверять.
Мы остановились где-то посередине: у нас идет https-запрос на определенный location, вызывается скрипт, если он отвечает 200-м ответом, мы считаем, что с этим сервером все нормально, что он живой и его совершенно спокойно можно включать.
Как это, опять же, выглядит на практике. Выключили сервер допустим на обслуживание — перепрошивка BIOS, например. В логах у нас тут же происходит таймаут, видим первая строчка, потом после трех попыток помечается как «зафейлен», и он удаляется просто из списка.
Возможен еще второй вариант поведения, когда просто VS выставляется в ноль, но в случае отдачи фотографии это работает плохо. Сервер поднимается, запускается там Nginx, тут же health-check'и понимают, что коннект проходит, что все отлично, и сервер появляется у нас в списке, и на него тут же автоматически начинает подаваться нагрузка. Никаких при этом ручных действий от дежурного админа не требуется. Ночью сервер перезагрузился — отдел мониторинга нам по этому поводу ночью не звонит. В известность ставят, что такое было, все нормально.
Итак, достаточно простым способ, с помощью небольшого количества сервером мы проблему внешней отказоустойчивости решили.
Осталось сказать, что все это, конечно же, нужно мониторить. Отдельно нужно отметить, что Keepalivede, как очень давно написанный софт, имеет кучу способов его замониторить, как с помощью проверок через DBus, SMTP, SNMP, так и стандартным Zabbix'ом. Плюс, он сам по себе умеет писать письма практически на каждый чих, и честно говоря, мы в какой-то момент думали даже выключить, потому что пишет он очень много писем на любое переключение трафика, включения, на каждый IP-шник и так далее. Конечно, если серверов много, то можно этими письмами себя завалить. Стандартными способами мониторим nginx на фото-роутерах, и никуда не делся мониторинг железа. Мы бы, конечно, советовали еще две вещи: это во-первых, внешние health-check'и доступности, потому что даже если все работает, на самом деле, возможно, пользователи фотографии не получают из-за проблем с внешними провайдерами или чего-то более сложного. Всегда стоит держать где-нибудь в другой сети, в amazon или еще где-то, отдельную машину, которая сможет снаружи пинговать ваши сервера, и также стоит использовать либо anomaly detection, для тех, кто умеет в хитрый machine learning, либо простой мониторинг, хотя бы для того, чтобы отслеживать, если реквесты резко упали, либо наоборот, выросли. Тоже бывает полезно.
Подведем итоги: мы, по сути, заменили железное решение, которое в какой-то момент перестало нас устраивать, довольно простой системой, которая делает все тоже самое, то есть обеспечивает терминирование HTTPS трафика и дальнейший умный роутинг с нужными health-check'ами. Мы увеличили стабильность этой системы, то есть на каждый слой у нас все также высокая доступность, плюс мы бонусом получили то, что на каждом слое довольно просто все это масштабировать, потому что это стандартное железо со стандартным софтом, то есть тем самым мы упростили себе диагностику возможных проблем.
Что мы в итоге получили? Проблема у нас была в январские праздники 2018-го. За первые полгода пока мы вводили эту схему в строй, расширяли уже на весь трафик, чтобы весь трафик снять с LTM, мы выросли только по трафику в одном дата-центре с 40 гигабит до 60 гигабит, и при этом за весь 2018-й год смогли отдавать практически в три раза больше фотографий в секунду.
Комментарии (11)
scruff
23.08.2019 13:27Объяснили бы для начала, что такое LTM для тех кто
в танкене в теме.mayorovp
23.08.2019 14:38Как я понял, это Local Traffic Manager от F5_Networks. Гуглится по ключевым словам "ltm network"
fessmage
23.08.2019 21:30+2Расскажу про то, как у меня решена похожая задача. Картинки, в день исходящий трафик больше 14 ТБ, пиковая скорость — до 8 Гбит/с.
На AWS Route 53 создан alias для домена, указывающий на 16 адресов кэширующих серверов. По днс запросу домена отдаются multiple A-records, 8 адресов, ttl 60s, порядок меняется с каждым запросом. Для каждого адреса кэширующего сервера в Route 53 настроен health check порта 443/tcp (можно и http на liveness url, но будет чуть дороже). В случае недоступности порта на каком-то адресе, health check реагирует на это за 30-60 сек и выкидывает адрес из днс выдачи у домена. Еще до 60 сек (помним про ttl алиаса) нужно чтобы старая днс запись у клиента протухла и он сходил за новой. Таким образом время недоступности домена у 1/16 клиентов наткнувшихся на упавший адрес составит от 30 до 120 сек максимум. И то, только если ПО клиента не умеет доставать следующие адреса из днс ответа самостоятельно (браузеры например умеют, мобильные приложения насколько я знаю — тоже). Дальше, на этих 16 кэширующих серверах (8 на ДЦ) настроена в nginx балансировка по 2 стораджам из своего ДЦ + 2 стораджа из второго ДЦ указаны с опцией backup. Если ответ с ошибкой или кончился таймаут (специально поставлен небольшим) — отправляем запрос на другой сторадж. Стораджи отдают в день до 4 ТБ за счет кэширующего слоя, а клиентам уходят 14 ТБ.
В такой схеме спокойно живется как при отказе любого сервера, так и при недоступности одного ДЦ. Небольшая часть клиентов при сбоях увидит или небольшое повышение времени ответа, либо недоступность не более 1-2 мин. Ручные действия не требуются, в PagerDuty присвоен низкий приоритет на алерты. Используемое ПО — только бесплатный nginx, за Route 53 — несколько десятков $ в месяц.Sannis
24.08.2019 15:12Еще до 60 сек (помним про ttl алиаса) нужно чтобы старая днс запись у клиента протухла и он сходил за новой.
Зря вы в это верите, многие провайдеры не смотрят так пристально на TTL.
crocodile2u
26.08.2019 10:56+1Одному мне не хватает в статье двух-трех схем? Типа "Вот так сеть была устроена" и "Вот так сеть теперь устроена".
Sannis
26.08.2019 14:41-1Если честно, полностью про расшифровке восстановить такой доклад будет проблематично и пара лишних слайдов (в оригинале из 68) имхо не спасут ситуацию. Погляди лучше видео, пользы больше :-)
Px2
26.08.2019 15:01-2Интереснее было бы прочитать про ботов и схемах обмана пользователей вашего сайта. Пилите пост, ждем.
AlexOnBeta
Хорошим пруфом к такой статье было бы использование собственного хранилища вместо habrastorage
4umak
А трафика столько на одну конкретную статью нагнать слабо, чтобы почувствовать разницу?:) А то пока вот например чуть больше тысячи просмотров с момента публикации. Что, в общем-то, капля в море озвученных в статье нагрузок:)
MaxALebedev
Через некоторое время после публикации статьи картинки автоматически копируются на хабрасторадж и ссылки меняются. По крайней мере с моими статьями было именно так.