Pull to refresh
545.27
Яндекс
Как мы делаем Яндекс

Опыт применения Go в продакшене Яндекса

Reading time 7 min
Views 74K
Хочу поделиться опытом использования языка Go в продакшн-системах Яндекса. Вообще мы здесь довольно консервативно относимся к тому, какие языки использовать для реальных систем. И это лишь добавляет полезности тому опыту, который мы получили в этот раз.

Мы начали разрабатывать на Go летом прошлого года. Тогда появился фреймворк Go для облачной платформы Cocaine. До этого приложения серверного API Браузера писались в основном на C++ и Python. Серверный API в это время как раз переходил на облачную платформу, и мы по большей части только определялись с тем, какие технологии использовать в будущем для него. API выполняет следующие функции: получить данные, обработать, отправить во внутренний сервис Яндекса, ещё раз обработать, отдать назад Браузеру. Набор простых приложений.



Недостатком C++ для нас был явный оверкилл для наших целей, на разработку уходила уйма времени, также большой проблемой для нас было то, что плюсовый фреймворк для Кокаина не представлял никакой возможности работать асинхронно, кроме как с помощью коллбэков. У нас было много обращений к различным сервисам, поэтому в результате скоро весь код стал одной большой лапшой из коллбэков. Масштабировать и отлаживать ее было очень сложно.

У Python были проблемы несколько иного плана. Во-первых, это скорость работы была очень низкая даже с PyPy, во-вторых, динамическая типизация потенциально могла приводить к ошибкам. Нужно было писать тесты даже там, где без этого можно было бы обойтись. Хотя в целом стоит отметить, что на Python как раз разрабатывать подобные приложения было достаточно просто. Разработка шла быстрее, чем на плюсах и в целом проще. Не было коллбэков, фреймворк поддерживал генераторы, можно было писать асинхронный код как синхронный.

И тут мы как-то решили попробовать в деле Go, ознакомившись с ним предварительно. Go — компилируемый, многопоточный язык программирования, со строгой статической типизацией (duck typing для интефейсов) и garbage-коллектором. Разработан язык компанией Google. Первоначальная разработка Go началась в сентябре 2007 года, а его непосредственным проектированием занимались Роберт Гризмер, Роб Пайк и Кен Томпсон. Официально язык был представлен в ноябре 2009 года.

У нас троих: меня, Вячеслава Бахмутова и Антона Тюрина  было предположение, что Go будет работать лучше. Забегая вперед, скажу, что ожидания наши подтвердились. Теперь перейдем подробнее к тому, что же все-таки стало лучше и почему.

Скорость разработки


На Go маленькие программы можно писать быстрее чем на C++, примерно так же быстро, как на Python. И ощущения от языка примерно такие же.

У Go есть хорошая стандартная библиотека, в которой есть почти все, что нужно. Достаточно редко приходится использовать внешние библиотеки, но когда до этого все же доходит, в большинстве случаев можно просто сделать go get github.com/library/lib, и она установится.

Go позволяет очень просто писать асинхронный код, он выглядит так же, как синхронный, но go runtime выполняет его асинхронно. Никаких коллбэков, понятное дело, нет. Все это сделано поверх goroutines. Роб Пайк описывает goroutines как «что-то вроде тредов, но более легковесное». Нечто сходное с тем, что в других языках часто называют “green threads” или “fibers”.

Для Go есть много достаточно хороших IDE и плагинов для них. Я лично пользуюсь плагином для IntelliJ IDEA, другие используют Sublime. Товарищ, который делает Go, при мне достаточно успешно использовал vim.



Вот так выглядит типичный код на Go, почти каждый метод, вызываемый тут на самом деле делает асинхронную работу. region.GetRegionByLbs асинхронно ходит в серсис геобазы и Lbs, findLanguage в langdetect, а третий метод со длинным называнием ходит через urlfetcher в яндексовский саджест. Как видите, код выглядит синхронным, его удобно писать и отлаживать.

Удобство тестирования и обнаружения ошибок


Мы стараемся сильно покрывать код тестами, иногда пишем тесты перед тем, как написать код, но такое все же бывает достаточно редко. Здесь Go показывает себя с очень хорошей стороны. Из коробки работает тестирование, code coverage в тестах. Причем последний представляет удобно в виде html те места, которые не покрыты тестами в коде. Соответственно, можно получить общий code coverage по модулям. Над общей инфраструктурой тестирования можно использовать и стороннюю библиотеку с более широкой функциональностью, мы так и делаем.



Обычно чтобы запустить тесты выполняется команда следующего рода: go test suggest… Это инструкция позволяет протестировать все модули, которые лежат в модуле suggest.

Профайлинг также работает из коробки, позволяя через встроенный веб-сервис посмотреть граф вызовов функций и время их исполнения. Граф выглядит так же, как и в google performance tools.



Присутствует встроенный thread sanitizer. Go как раз разрабатывает один из товарищей, который делает sanitizer в Google. Есть возможность получать стек-трейс ошибок, не используя rocket science.

В Go не может быть ошибок памяти, это сильно помогает, так как не всегда и не всем удается быть внимательным. У нас есть приложение suggest-data, оно при старте загружает в себя 300 мегабайт данных. Когда оно было написано на плюсах, то временами падало, что вызывало легкий дискомфорт у нашего админа. В первый раз когда оно падало, это было из-за фреймворка Кокаина. Это пофиксили, но потом падения продолжились, во второй раз мы до конца не разобрались в причинах, возможно, была проблема и в том, что мы что-то не так написали. В результате решили не заморачиваться и переписать его на Go (там было всего 200 строк кода). Падения сразу исчезли. Проблема еще осложнялась тем, что часто стек был покоррапчен, и тяжело было найти причину падения. То есть можно было, но сложно. После перехода на Go память больше не корраптится, а если будут обращения к нулевому указателю, то мы увидем все это в виде стек трейса в логах.

Вот так вот выглядит лог:

Wrong format of region (lr)
/home/lamerman/work/omnibox/.../inside.go:109       (*Inside).getRegionData
/home/lamerman/work/omnibox/.../inside.go:194       (*Inside).Call
/home/lamerman/work/omnibox/.../main/main.go:57     *Inside.Call·fm
/usr/local/go/src/pkg/net/http/server.go:1221                                   HandlerFunc.ServeHTTP
/home/lamerman/work/go/src/github.com/.../httpreq.go:124 func·006
/home/lamerman/work/go/src/github.com/.../worker.go:219  func·015
/usr/local/go/src/pkg/runtime/proc.c:1394                                       goexit

Производительность


Для того, чтобы не тратить без дела деньги на серверы, язык должен быть достаточно быстрым. Вот здесь сравнение Go с C++ и Python в стандартных тестах. Результат для Python:



Как можно видеть, в среднем Go в десятки раз быстрее. То же самое в сравнении с C++:



В среднем Go в два-три раза медленнее. С учетом того, что язык молодой можно думать, что в будущем он может еще серьезно ускориться.

Также хотел бы поделиться и собственным наблюдениями по поводу скорости работы у нас. В Кокаине есть сервис для получения контента по url, называется urlfetcher. Мы из некоторых соображений пока что пользуемся собственной его версией и он у нас представлен в двух экземплярах pyurlfetcher и gofetcher. Как можно нетрудно догадаться, разница в языке, на котором они написаны. Реализуют они один и тот же интерфейс. Попробуем пострелять в них. За 10000 единиц обращений gofetcher потратил 2.52 секунды процессорного времени, pyurlfetcher тратит на это 19.5 секунды, справедливости ради стоит отметить, что под PyPy это работает ровно в два раза быстрее, то есть 10 секунд. В итогде получается, что Go работает в 4 раза быстрее, чем Python под PyPy и в 8 раз быстрее, чем cpython. Ну то есть если использовать cpython, то нужно построить в 8 раз больше дата-центров.

Сравнить с C++ можно также на одном из наших приложений — suggest. Приложение показывает умную строку в браузере, забирая данные из яндексовского саджеста и колдунщиков, то есть в основном тут идет обработка json и хождение во всякие сетевые сервисы.

На 1000 запросов к suggest на Go тратится 1.10 секунд процессорного времени, на C++ — 0.57 секунд, то есть можно видеть, что Go ровно в два раза медленнее, чем C++ на этом приложении. Само приложение порядка 6 тысяч строк кода.

Память


Вот так выглядит картина использования памяти нашими ручками на продакшен-сервере:

7855 cocaine   20   0  262m 9620 3744 S    1  0.0 249:35.82 barnavig
7855 cocaine   20   0  262m 9620 3744 S    1  0.0 249:35.82 barnavig
8590 cocaine   20   0  324m  11m 3604 S    1  0.0  87:05.82 umaproto

Можно видеть, что памяти в среднем потребляется не очень много, порядка 10 мегабайт на ручку в случае, если нет memory leak или кешей. Утечки памяти отностельно легко отлаживаются внутренним инструментом.

Сравним два одинаковых приложения на плюсах и Go. Оба приложения хранят в себе много данных. Как видно, потребление почти идентично в самом начале работы приложения. У каждого по 300 мегабайт данных:

14742 cocaine   20   0 1071m 388m 3376 S    0  0.3  26:04.85 suggestdata (suggest data на Go)
2734 cocaine   20   0  825m 345m 3388 S    0  0.5  23:47.80 suggest-data-pr (suggest data на c++)

Память также можно профилировать в случае утечек, подробнее про профайлинг можно почитать здесь.

Остальное


Отлаживать приложения можно с помощью gdb и всего того прекрасного, что он дает людям. У нас не было проблем с отладкой этих программ под gdb, все вроде как работает.

Еще стоит добавить, что все все приложения Go собираются вместе со всеми используемыми библиотеками в один большой бинарник (статическая линковка), это чаще всего удобно при деплое приложений на сервер, не нужно думать о зависимостях, также можно легко деплоить на любую систему, тот же самый бинарник можно запихнуть и на lucid и на precise, при этом неважно, какой набор пакетов там установлен. Единственная зависимость, которая есть, насколько я помню, это libc. У этого подхода также есть и некоторые недостатки.

Заключение

Думаю, что Go в первую очередь может быть полезен тем, кто пишет на Python, но недоволен скоростью работы приложений. Писать на Go можно так же просто, как и на Python, но можно сохранить много ресурсов машин. Для людей, пишущих на C++, Go может быть полезен там, где нужно писать простые приложения. Лично у меня после такого перехода продуктивность сильно увеличилась.

Go, конечно, не идеален. Есть вероятность, что я просто не до конца его понял. То, чего не хватает в первую очередь мне — это generics. Их планируют ввести в каком то будущем, но до конца их перспектива еще не ясна. Сам Роб Пайк сказал по этому поводу следующее:  «В Go есть generics, там они называются интерфейсами». Действительно, какой-то generic code можно писать с использованием интерфейсов, но в части случаев этого не хватает. Отсутствие generics частично компенсируется наличием reflection. Но faq и Пайк уверяют, что generics будут.

У нас приложения на Go работают под Кокаином в продакшене уже около года, и каких-то фатальных вещей не происходило ни разу. Go работает, и по-моему работает хорошо.

Go активно развивается, проходит много конференций по языку и регулярно выходят новые версии, которые улучшают производительность. Go используется внутри Google, Facebook, Docker, disqus.com (http://blog.disqus.com/post/51155103801/trying-out-this-go-thing) и многих других крупных компаниях. Список можно посмотреть здесь.
Tags:
Hubs:
+132
Comments 123
Comments Comments 123

Articles

Information

Website
www.ya.ru
Registered
Founded
Employees
over 10,000 employees
Location
Россия
Representative