Предлагаю вашему вниманию перевод гостевого поста из блога Go от лица Yang Zhou, в данный момент занимающего позицию инженера в Qihoo 360.
Qihoo 360 является лидирующим поставщиком антивирусных продуктов для интернета и мобильных устройств в Китае, контролирует крупную платформу дистрибьюции мобильных приложений для Android (магазин приложений). На конец июня 2014 года Qihoo пользовались 500 миллионов активных пользователей ПК в месяц и свыше 640 миллионов пользователей мобильных устройств. У Qihoo также имеется свой браузер и поисковый движок, оба не менее популярны среди китайцев.
Моя команда, отдел push-сообщений, предоставляет фундаментальный сервис обмена сообщениями для более чем 50 приложений среди продуктов компании (как для ПК, так и для мобильных устройств), а также тысячам сторонних приложений, использующих нашу открытую платформу.
Наш «роман» с Go берёт своё начало в 2012 году, когда мы пытались наладить работу push. Самый первый вариант представлял из себя связку nginx + lua + redis, но производительность под серьёзной нагрузкой не подошла под наши требования. Во время очередного поиска правильного стека наше внимание привлёк свежий релиз Go 1.0.3. Мы изготовили прототип буквально за несколько недель, во многом благодаря горутинам (легковесные потоки) и го-каналам (типизированные очереди), которые являются примитивами языка.
Изначально наша гофернутая система была развёрнута на 20 серверах, обслуживая в общем 20 миллионов активных соединений. Она справлялась с отправкой всего 2 миллионов сообщений в день. Сейчас же система развёрнута на 400 серверах, поддерживает 200+ миллионов активных соединений и обеспечивает отправку более чем 10 миллиардов сообщений ежедневно.
Параллельно со стремительным ростом бизнеса и повышением требований к сервису push-сообщений изначальная система на Go быстро упёрлась в лимиты: размер кучи достигал 69G, паузы GC составляли по 3-6 секунд. Более того, мы перезагружали наши сервера еженедельно для освобождения памяти. Если честно, думали даже избавиться от Go и переписать всё ядро на C. Однако, вскоре планы поменялись: затык произошел во время переноса бизнес логики. Одному человеку (мне) невозможно было осилить поддержку системы на Go, параллельно обеспечивая портирование бизнес логики на C.
Так что я принял решение остаться с Go (и на мой взгляд — это самое мудрое из всех, что я мог тогда принять) и качественный прогресс пришёл довольно скоро.
Вот несколько приёмов и оптимизаций:
- Повторно использовать TCP соединения (пул соединений), во избежание создания новых объектов и буферов во время взаимодействия;
- Повторно использовать объекты и выделенную память, для уменьшения нагрузки на GC;
- Использовать группы продолжительно живущих горутин для обработки очередей задач или сообщений, полученных от
горутин подключений. Т.е. классический pub/sub вместо порождения множества горутин, по одной на каждый входящий запрос; - Мониторить и контролировать количество горутин в процессе. Отсутствие контроля может привести к появлению непосильной нагрузки на GC из-за скачка в количестве создаваемых горутин. Например, если допустить бесконтрольное создание горутин для обработки запросов извне, то блокировка только что созданных вследствие обращения к внутренним сервисам, например, приведёт к созданию новых горутин и так далее;
- Не забудьте указать дедлайны на чтение и на запись для сетевых соединений при работе с мобильной сетью, иначе горутины могут заблокироваться. Однако, применяйте их осторожно с LAN сетью, иначе эффективность RPC-взаимодействия может пострадать;
- Использовать при необходимости Pipelining для вызовов (при доступности Full Duplex для TCP).
В результате получилось три итерации нашей архитектуры, две итерации RPC фреймворка, даже с ограниченным количеством людей для реализации. Я бы отнёс это достижение к удобству разработки на Go. Привожу свежую диаграмму нашей архитектуры:
Результат непрерывных улучшений и доработок в виде таблицы:
И, что не менее круче, заодно мы разработали платформу для профилирования Go программ в реальном времени (Visibility Platform). Теперь мы имеем доступ к статусу системы и диагностической информации, предвидя любые неполадки.
Скриншоты:
Из замечательного: с этим инструментом мы можем симулировать подключение и поведение миллионов пользователей, используя модуль Distributed Stress Test Tool (также написан на Go), наблюдать за результатами в реальном времени и с визуализацией. Это позволяет нам изучать эффективность любого нововведения или оптимизации, предотвращая любые проблемы с производительностью в боевых условиях. Практически любая возможная оптимизация системы уже была опробована нами. И мы с нетерпением ждём хороших новостей от команды, отвечающей за GC в Go, они могли бы избавить нас от лишней работы с оптимизацией кода под GC в дальнейшем. Я также допускаю, что наши ухищрения скоро станут попросту рудиментарными, поскольку Go продолжает развиваться.
Я хочу завершить эту историю благодарностью за возможность участвовать в Gopher China. Это было торжественное мероприятие, позволившее нам узнать многое, поделиться своими знаниями, проникнуться популярностью и успехом Go в Китае. Большое количество команд в Qihoo уже успели ознакомиться с Go и даже попробовать. Я убеждён, что скоро ещё больше китайских интернет-компаний присоединятся к тренду и перепишут свои системы на Go, таким образом усилия команды, стоящей за Go, принесут пользу огромному количеству разработчиков и компаний в ближайшем будущем.
Комментарии (21)
Pryada
07.07.2015 16:53+2Я убеждён, что скоро ещё больше китайских интернет-компаний присоединятся к тренду и перепишут свои системы на Go
Уже сейчас на гитхабе много китайских репозиториев с Го.
А учитывая их специфику, что не проект — то хайлоад, может получиться очень интересно.Xlab Автор
07.07.2015 17:11+4Там в Китае с Go такой хайп, вот пост на HN — Why is Golang popular in China?
mukizu
08.07.2015 08:27Ну, даже два популярных (если не самых популярных) web-фреймворков — китайские.
dblokhin
08.07.2015 07:30Пару примеров бы конкретных оптимизаций, паттернов и тех рудиментов, которые решают какие-то проблемы с памятью.
AterCattus
08.07.2015 13:16+1Сейчас же система развёрнута на 400 серверах, поддерживает 200+ миллионов активных соединений и обеспечивает отправку более чем 10 миллиардов сообщений ежедневно.
Сначала возник вопрос, зачем так много машин для такого небольшого суточного объема.
Но тут стало все понятно: не очень как-то они хорошо сделали, с Go можно сделать сильно лучше.
размер кучи достигал 69G, паузы GC составляли по 3-6 секунд
QtRoS
Последние статьи так и подбивают ознакомиться с этим чудо-языком!
nwalker
Познакомьтесь лучше с Erlang. По мере знакомства нездоровые желания плавно сойдут на нет.
cy-ernado
Нездоровые желания изучать Erlang? ;)
nwalker
Ну, если вы сторонник hype-driven development…
Если говорить по теме — даже просто отказавшись от го в пользу эрланга, они бы решили проблему stop-the-world gc и сильно облегчили бы себе жизнь в плане работы с памятью.
neolink
ага и писали бы свои сервисы лет по пять
по поводу gc уже в августе должен выйти 1.5 в котором всё будет немного по другому
dmbreaker
Я бы сказал «сильно по другому». И лучше.
AterCattus
Даже в Go 1.3/1.4, где еще StW GC, на реальном использовании время работы GC меньше 1%. Трудно представить сферу применения (для рассматриваемых ЯП), когда эта величина критично высока.
А в 1.5 вообще обещают красоту.
dmbreaker
На Go добиться stop the world проще простого — создаете несколько миллионов указателей и приложение на Go начинает тупить каждые две минуты. Решение одно — избавляться в данных от указателей везде, где это возможно. Не факт, что 1.5 решит эту проблему, а не «размажет ее ровным слоем».
AterCattus
С нормальным кодом в Go сложно упереться в GC. Понятно, что если хочется, то легко, но это в любом ЯП можно наговнокодить именно с учетом конкретных слабых мест.
У меня в приложеньке постоянно создаваемые десятки миллионов короткоживующих указателей (и данных, на которые они указывают). Про GC даже не приходится задумываться.
dmbreaker
Я как раз о том, что упереться в GC проще простого. Достаточно в большом кол-ве долгоживущих данных иметь указатели.
Пока вы больших данных в памяти не держите — у вас нет проблем. Как только много данных, то GC стучит в дверь.
Причем вы не можете легко избавиться от указателей. Потому что они везде :) В слайсах, мапах, строках и интерфейсах.
AterCattus
Я просто как раз решал на Go задачу, крайне похожую на описанную в посте. И эти жуткие десятки гигабайт хипа и паузы на GC под секунду (последствия) мне кажутся дикими. Разве что они долго хранили (зачем?) отсылаемые данные.
Xlab Автор
Указатели нужно применять с умом. В документации нет пояснения, когда разумнее использовать именно указатель, поэтому большинство использует их по-умолчанию для консистентности сигнатур методов.
Как только программист натыкается на GC, то начинаются оптимизиации, но это всё в соответствии с задачами, которые решаются.
dmbreaker
Да, но в документации нет упоминаний о том, что указатели зло. Поэтому все натыкаются, а потом уже начинаются оптимизации :)
cy-ernado
Просто не все пишут приложения на 60G кучи :)
У badoo точно такие же проблемы были, эта проблема обсуждена в 2013, вот и Qihoo теперь на английском закрепили.
Теперь будет легче. Так развивается сообщество.
koshak
golang.org/doc/faq#methods_on_values_or_pointers
Xlab Автор
Читал несколько раз, когда изучал язык, собственно про impact on GC там ничего не сказано, это меня и смущало. Только синтаксические особенности, косметические и совсем очевидное про тяжеловесов.