Компания
870,77
рейтинг
24 сентября 2014 в 15:33

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

Хочу поделиться опытом использования языка 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) и многих других крупных компаниях. Список можно посмотреть здесь.
Автор: @lamerman
Яндекс
рейтинг 870,77

Похожие публикации

Комментарии (123)

  • +2
    А вы смотрели в сторону Erlang? Если да, то интересно ваше мнение.

    Кажется, что Erlang бы тут подошел ничуть не хуже, чем Go. Erlang компилируемый и асинхронный. Учится вряд ли дольше, чем Go. Да он динамически типизирован, но предусмотрены спеки, которые как раз позволяют не писать тесты на тривиальные кейсы.
    • +3
      К сожалению, я достаточно поверхностно знаком с Erlang, не могу ничего сказать по этому поводу.
    • +15
      Есть множество причин, по которым не использовался Erlang. Но самая главная — это время поиска людей, которые бы хотели/могли писать на Erlang. Даже Ericsson, создатель Erlang теперь пишет на Go =)
      • +2
        В Ericsson пишут на большом количестве различных языков, я бы не сказал, что Erlang там основной.
        • 0
          Это понятно, в огромной корпорации есть место любым извращениям. Я о другом, для некоторых задач Go подходит лучше, например для управления облаками.
          • +1
            OpenStack вполне себе неплохо бегает на Python, или я просто немного не понимаю, о чем вы.
            • 0
              К сожалению, я не имел опыта разворачивания и управления OpenStack. Но я могу рассказать чем Go лучше Python.
              • +1
                Точно так же, как и чем Python лучше Go =)
                • +1
                  Когда любишь, то уже не замечаешь недостатков =)
                  • 0
                    Я к тому, что и там и там они есть.
                    • +3
                      Да, согласен, в Go куча недостатков, они у меня все записаны в отдельный блокнот.
                      • +15
                        Оформите это в статью. Интересно почитать будет.
                        • +1
                          Хорошо, я попробую. Но наверное это будет скорее об «особенностях», чем недостатках =)
                • 0
                  Напомнило из какого-то фильма: «Это доказывает, что Бога не существует… Если господин пожелает, я могу доказать обратное.»
              • +5
                Я сейчас пишу и на том, и на том; к Go у меня смешанные чувства. Пока что я его вижу как отличный язык для написания небольших демонов, работающих с сетью. В остальных отношениях от него постоянно возникает неприятное ощущение недоделанности, непродуманности какой-то, даже не знаю, как лучше выразиться.

                По уровням абстракции он очень несбалансированный: например, сопрограммы реализованы довольно хорошо и удобно, а вот кроссплатформенное получение кода возврата от процессов, запущенных через exec.Command, уже заставляет писать несколько файлов под разные ОС. Довольно простая работа с http, но какая-нибудь другая область применения — и вот уже танцуешь с syscall. Обрабатываешь очередной назойливый err, и кажется, что просто взяли старый-добрый C, и натянули на него какое-то хипстерское шмотье.

                Никак не могу отделаться от ощущения, что это какой-то DSL для написания всяких сетевых штук.
                • 0
                  Я вас понимаю, Go действительно скорее недоделан, чем переделан. И многое в нём навязывается. Но он развивается очень быстро, в том числе и силами уже не маленького сообщества, которое очень отзывчиво. Возможно для ваших задач нужно что-то уже более зрелое и выразительное.
                • +1
                  Я не знаком (пока) с Go, поэтому хочется спросить — а почему неудобность api это проблема Go?

                  В нём не принято использовать библиотеки?
                  Ну взять (или написать) либу с другим exec.command, впихать туда те несколько файлов под разные ОС, выложить в публичный репозиторий чтбоы подключалась к проекту одной строчкой.

                  Если проблема только в наличии библиотек, то не язык виноват, а просто готовых библиотек нету, не?
                  • +5
                    Эта проблема, безусловно, решается, но все-таки хотелось бы иметь продуманную полноценную стандартную библиотеку. Сейчас она хороша только пока вы работаете с сетью, в остальном складывается ощущение, будто написана довольно хаотично и «чтобы было». Если посмотреть tech talk об истории языка, то не возникает ощущения, будто над многими вещами там и правда заморачивались.

                    Сторонние библиотеки пока сыры и плохо документированы. Обычные библиотеки документированы лучше, но чаще всего приходится лезть в исходники, чтобы понять, как оно на самом деле работает (например, редко когда по документации можно понять, как разобрать ошибку из кортежа). Но это — теоритически — еще можно поправить. А вот чего нельзя поправить, так это то, что со своими структурами зачастую нельзя работать как с теми, что идут вместе с языком. Хороший пример — это итерация.

                    В Go 4 разных синтаксиса для определения цикла for. Если вы итерируете стандартный срез, массив или мэп, то все ок. Но вот использовать те же конструкции для своих структур уже вряд ли получится.

                    Давайте пример:

                    type TreeNode struct {
                    	Value interface{}
                    	Left  *TreeNode
                    	Right *TreeNode
                    }
                    
                    type BinaryTree struct {
                    	Root *TreeNode
                    }
                    


                    Думаю, понятно, что здесь происходит. Структуры определили, давайте теперь попробуем прицепить к ним методы, чтобы можно было делать вот так:

                    for node := range tree.InOrder() {
                    	fmt.Println(node)
                    }
                    
                    for node := range tree.PreOrder() {
                    	fmt.Println(node)
                    }
                    


                    Думаю, понятно, что здесь должно происходить. Так вот, так делать нельзя. Вы не можете использовать range таким образом. Приходится явным образом доставать итератор и итерировать его вот так:

                    iterator := tree.PreOrderIterator()
                    for iterator.HasNext() {
                    	fmt.Println(iterator.Extract())
                    }
                    


                    Или функция make. Отличная функция, инициализирующая неявные структуры, с которыми работают срезы или словари. О, мы только что написали свою суперкрутую структуру, довольно сложную в инициализации, хочется создавать ее тем же образом, что и тот же словарь (например, написали свой Set). Ничего подобного сделать нельзя.

                    Поэтому со всеми бибилиотеками контейнеров не получится работать всеми средствами языка, инициализировать аналогичным с теми же срезами или картами образом не получится. И это только один пример. А ведь еще есть отсутствие генериков, принуждающее (!!!) копипастить, еще есть абсолютно наколеночный go get, про который во времена PyPI, Gem'ов или npm'ов вообще говорить стыдно.

                    Какое в Go стандартное средство для dependency management'а? Так ведь нет его фактически. Роб Пайк сказал, что им и этого хватает, пускай коммьюнити пишет. Комьюнити написало Godep, GPM, еще какие-то поделки есть. Но единого способа, единообразной экосистемы в Go просто не существует. Зато есть вот такой костылина.
                    • +2
                      Подпишусь под каждым словом. Именно невозможность сделать что-то типа __iter__ в Python одна из вещей, которая мне в Go не нравится.
                      • –1
                        Вот за это __govna_piroga__ я и не люблю Python. Уверен, не я один. С моей точки зрения зарезервированных имён методов быть не должно. Имя функции — это полностью дело программиста. Имя не должно само по себе влиять на семантику, это просто идентификатор. По мне, так лучше ввести дополнительные ключевые слова-модификаторы, чем писать вот такое.
                        • 0
                          Ну давайте еще С++ поругаем за перегрузку операторов…
                          • 0
                            А я и ругаю и считаю перегрузку злом, делающим код совершенно не читаемым. Но речь не о том. Сама функция-оператор в C++ описывается достаточно изящно, а не костылями типа __rshift__ и __plus__ для
                            • 0
                              … для >> и +
                        • 0
                          «зарезервированных имён методов быть не должно» — oh, really? Скажите это C++ кодерам с их вездессущим int main(). Зарезервированное имя функции? Да. Все привыкли и уже перестали обращать внимание на «неудобства» (которых нет)? Да. Абсолютно то же самое и в Python.
                          • 0
                            Это не неудобство, просто не эстетично.
                        • +2
                          А чем Go лучше? В нем для реализации интерфейсов достаточно всего-навсего иметь методы с теми же сигнатурами. И все, считается, что структура реализует интерфейс. То есть никаких дополнительных телодвижений не нужно. Просто считайте, что наличие __iter__, например, говорит о том, что класс теперь Iterable. В этом случае, так как и там, и там в отношении подобных вещей duck typing, разницы принципиальной нет.
                    • +2
                      Как я понимаю, философия создателей языка состоит в том, чтобы «не перегружать».

                      Да, очень много придумано в современных языках, но как я читал, они хотят оставить все настолько простым, насколько возможно, поэтому каждая новая фишка долго и тщательно продумывается.

                      Возможно в будущем это все введут, а на данный момент есть что есть.

                      Я сейчас полностью пишу на Go и знаю, как не хватает многих вещей, но в тоже время это каким-то странным образом позволяет решать задачи более простыми и элегатными способами, не создавая лишних «сущностей».
                      • +2
                        Не могу согласиться. go get настолько непродуман, что сообщество изобретает велосипеды один офигительнее другого. Зачем-то есть разница между работой со срезами (фундаментально, та же структура из интов), картами (тоже представляется в виде структуры) и своими struct'ами. Библиотеки для работы с текстом наводнены разными Do и DoString. Генерики имеют все шансы повторить историю своих «коллег» из Java. Мне сложно согласиться, что он тщательно продумывается, стандартная библиотека хоть и большая, но крайне неоднородная, часто приходится делать такие вещи, которые в 2014 уже не ожидаешь от нового языка.

                        А про недостаток синтаксического сахара: мой болевой порог тоже позволяет писать на Go, но при этом я не могу отделаться от мысли, что такая простота «хуже воровства».
                    • +1
                      for node := range tree.PreOrder() {
                      fmt.Println(node)
                      }

                      Так сделать можно. Если нет необходимости прервать итерирование в процессе — верните канал и поднимите горутину, которая будет в этот канал по одному объекту класть.

                      Впрочем для таких случаев я бы скорее написал что нибудь а-ля tree.foreach.
                      • +2
                        Безусловно, сделать так, чтобы формально код заработал, несложно. Можно вернуть канал, можно вернуть срез. Я не про то: большая часть методов языка работает с интерфейсами. Использовать такой интерфейс, чтобы можно было итерировать свой объект нельзя. Использовать такой интерфейс, чтобы можно было, грубо говоря, использовать [] тоже. Квадратные скобки де-факто введены только для срезов и словарей. Интерфейс для работы с make отсутствует.

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

                        Если бы язык был консистентный, то там был бы примитив, который можно итерировать, и работали бы с ним как с данностью. Все. Это было бы нормально. И массивы бы итерировали с помощью какого-нибудь .At(index int), и из словаря бы брали с помощью .Get(key interface{}). Язык был бы шумный, но не возникало бы подобного вопроса.
                        • +1
                          По поводу «неконсистентный» я даже спорить не буду, благо ровно тем-же самым словом не так давно этот язык и описывал.
                          Правда по другой причине (работа с каналами, возврат нескольких значений из функции), но ощущение такое-же.
                    • 0
                      По поводу make и range. Насколько я могу судить, изначальная идея была не вводить в язык синтаксический сахар, завязанный на пользовательские интерфейсы. С другой стороны, разработчики не хотели делать встроенные объекты собственно объектами с методами. Объекты и интерфейсы находятся как бы уровнем выше, вне ведения базовых конструкций. Отсюда и такое поведение.
    • 0
      Я думаю подошел бы идеально, как разработчик на Erlang говорю:)
      • 0
        В середине статьи решил, что напишу комент про Erlang, но первый камент к статье уже был про Erlang =(

        Поэтому приведу видео:
        www.youtube.com/watch?v=jYrHjS8Z_XU
  • +5
    Интересно узнать, почему не рассматривали Java? Тем более, что Java в Яндексе давно применяется.

    Судя по тестам от benchmarksgame.alioth.debian.org, Java работает несколько быстрее Go.
    • +6
      Вообще именно в нашем отделе java традиционно не использовали и этот вопрос как-то особенно не рассматривался. :)
      Java действительно кажется не медленнее чем Go по тестам и по моим собственным воспоминаниям о работе с ней, имеет обширную стандартную библиотеку и много сторонних либ. Из недостатков по сравнению с Go можно отметить отсутствие lightweight threads из коробки и очень большой memory footprint одного запущенного инстанса, что для облачного приложения может быть критично. Еще один правда незначительный недостаток, то что нужно тащить за собой java runtime, на выходе у Go же получается статически слинкованный бинарник зависящий только от libc.
      • 0
        По lightweight threads в java есть akka, которая ну ни чуть не менее популярная чем гоу как платформа, особенно ели брать скала, где мало какое приложение сервереное работает без него, и есть quasar.
        • 0
          akka это всё-таки не lightweight threads, а тред пул с тяжеловесными тредами. Разница, например, в том, что нельзя залокаться в середине обработки сообщения на IO — надо освобождать тред
    • +6
      После Java захочется Scala, а Scala нельзя =)
      • 0
        А почему Scala нельзя, если не секрет?
        • +4
          Не секрет, язык не простой, сложно искать людей.
          • +2
            По секрету могу сказать вам, что некоторые небольшие штуки в Яндексе пишутся и на Scala.
            • 0
              Да, кто-то даже на racket, но я говорю про команду хотя бы из 5 человек. Я знаю многих, кто хотел бы писать на Scala, но пока не получается.
              • 0
                Я писал даже коммерческий сайт на Scala и больше всего бился головой об sbt.
          • +1
            Вот реально не могу понять, что там сложного? Наоборот все просто и очевидно… Ну да, синтаксис немного другой.

            P.S. Вот почему никто не жалуется, что C# сложный? Ведь он содержит больше language-фич, чем скала, имхо. А на нем пишет примерно столько же людей, сколько и на джаве… Неужели никто не ропщет только потому, что выбора толком нет?
  • 0
    Позвольте поинтересоваться как вам удается иметь
    regionId := region.GetRegionByLbs(req, this.lbs, this.geobase) асинхронным?
    Мне чтобы добиться асинхроности приходиться передавть в функцию chan
    и ожидать результата с помощью select.
    Спасибо.
    • 0
      Ну, может он внутри создает кучу потоков, но результат синхронизирует, так что снаружи метод выглядит синхронным.
    • 0
      Этот метод внутри себя обращается к сервису Кокаина через go-framework. А обращения к нему как раз реализованы через channels. То есть управление останавливается там, где оно доходит до чтение из канала фреймворка и, как только он записывает туда ответ, исполнение продолжается.
      • +2
        «управление останавливается» звучит для меня как синхронный подход! ;)
        В любом случаем большое спасибо, Кокаин один из лучших framework с которым
        я знаком, есть повод глянуть на его Go библиотеку.
        • 0
          В фреймворке Go Кокаина не все идеально сейчас, мы планировали серьезный рефакторинг в ближайшее время. Он работает хорошо и надежно, но код и интерфейсы стоило бы причесать, плюс добавить тестов. Но посмотреть, конечно, можно :) github.com/cocaine/cocaine-framework-go
  • 0
    .
  • 0
    Нормальный (рабочий) пакетный менеджер для Go, который позволяет брать из гита определённые коммиты библиотек, уже запилили?
    • +3
      Go deps умеет многое
  • –2
    А почему не нода, например? Из-за динамической типизации?
    • +11
      Производительность ноды примерно на уровне питона или даже ниже, плюс проблемы с утечками памяти.
      Зачем шило на мыло менять? :)
      • –2
        Это вы откуда взяли?
        • 0
          Можно множество бенчмарков в интеренете найти, местами нода быстрей, но PyPy решает эту проблему. Единственный большой плюс перед питоном — асинхронность, если сравнивать с веткой 2.x.
          Ну и синтаксис после питона в ноде сильно на любителя. Авторы ведь с этим языком сравнивали тоже.
          Все тесты где нода выигрывает в основном связаны с вебом и сетевым взаимодействием, что неудивительно.
    • +6
      Наверное, из-за того, что генераторы в ноде только на походе. А писать хочется без колбэков уже сейчас.
      • 0
        Ну нити вроде есть отдельными пакетами, хотя я с ними не работал, не знаю, подходят ли.
    • +1
      NodeJS, кажется, не очень хорошо работает на многоядерных конфигурациях. Плюс иногда очень важно знать, что данные в памяти хранятся именно так, как вы указали в программе.
    • +1
      Javascript с v8 работает быстро, но как то не прижился именно у нас. Меня когда я еще делал скрипты для веб страниц сильно смущало обилие коллбэков. Насколько я знаю в ноде уже есть генераторы или нечто подобное чтобы писать аля синхронно, но как то не довелось все это попробовать.
      Ну и да динамическая типизация для подобного проекта думаю будет не нужна, придется писать больше тестов.
      • 0
        а разве промисы не решают проблему обилия коллбэков?
        • +1
          Они лишь уменьшают их вложенность, размазывая по коду.
      • 0
        TypeScript поможет?
  • +1
    Надеюсь через год-два этот язык станет ещё популярней. Жду когда появится часть нужных мне библиотек от комьюнити, пока что многие из них имеют незаконченный вид.
    Мне, как питонисту, с постоянными проблемами производительности некоторых вычислений, этот язык кажется идеальным вариантом в качестве второго.
    • +2
      А numpy не решает проблемы вычислений?
  • +1
    Позвольте попридираться к формулировкам, а тот тут в каментах уже задают вопросы. Я вот про этот утверждение «region.GetRegionByLbs асинхронно ходит в серсис геобазы и Lbs». Думаю правильней сказать, что этот метод все-таки синхронный (т.е. пока он не отработает, последующий за ним код не начнет выполняться), но он неблокирующий, в том смысле, что он не блокирует поток ОС за счет использования goroutines.
    • 0
      Если до конца придираться, то он может и системный тред заблокировать на блокирующих syscall вызовах. Просто планировщик Go перебросит остальные Go-рутины на другой тред и приложение этого даже не узнает.
      • 0
        Естественно может. Но я надеюсь что приведенный метод написан хорошо и не вызывает блокиурющих методов :) Иначе другие горутины тоже могут блокировать поток, короче все будет как в обычном многопоточном приложении без горутин.
        • +2
          Я думаю там используется netpoller и ничего не блокируется. Если дойдут руки и Дмитрий Вьюков мне поможет, то попробую написать подробную статью про внутренности планировщика в текущей реализации.
  • +6
    И еще вопрос: не смотрели в сторону Rust?
    • +7
      Обязательно посмотрим, когда он зарелизится.
    • +5
      Да смотрели и предварительно он кажется очень интересным, мы хотим его тоже попробовать.
  • 0
    В статье несколько раз сравнили C++ и Go по различных параметрам. А есть понимание, сколько строк кода C++ соответствует одной строке кода на Go (или наоборот) в среднем?
    • +2
      Примерно одинаково, только что взял крошечное приложение, которое есть и на одном и на другом языке, на плюсах было 180 строк кода, на Go 220.
      • +1
        На таких размерах приложения результат на мой взгляд не репрезентативный. Нет данных по приложениям хотя бы на несколько тысяч строк?
      • +11
        Лишние 40 строк видимо выглядят как
        if err != nil {
  • 0
    Расскажите, как вы тестируете go-код. Насколько я помню, в ответах после доклада на схожую тему рассказывали про testify и билд-сервер. Можете добавить подробностей: как собираете и получаете зависимости, что тестируете, используете ли эти билды при деплое?
    • 0
      С зависимостями решение как у всех: всё складывается в вендор директорию, подключаемую как дополнительный путь в GOPATH. Пока всё работает — ничего не обновляется. Если есть проблема или нужна функциональность в новой версии библиотеки — вручную обновляется и тщательно проверяются деградации.
      На тестовые стенды деплой проходит через вагрант/openstack виртуалки у разработчиков, где в манифест заносится вся информация о билде (версии библиотек итд). На продакшен всё гораздо сложнее, несколько уровней проверки и постепенная выливка с помощью администраторов.
  • +2
    А GC проблем/тормозов не доставляет?
    • 0
      У нас пока не было проблем с GC ни в одном из приложений, по крайней мере мы их не замечали.
    • +2
      Кстати, в 1.5 GC собираются сделать намного более предсказуемым, чем есть сейчас. При идеальном раскладе возможно слегка просядет производительность, зато не будет внезапных stop the world.
  • +1
    Суровый брутальный обзор, но как-то так оно и есть, да. Продуктивности Go прибавляет с лихвой.
    • +3
      Скоро под андроид будем писать на Go, вот тогда повеселимся =)
  • –1
    Rust не рассматривали при выборе языка? Если нет, то почему, если да, то почему отпал.
    • 0
      Потому что это еще не стабильный язык, который меняется от версии к версии?
  • 0
    Facebook использует D, Yandex использует Go — будущее ближе, чем кажется?
    • +1
      Facebook использует Александреску. А Александреску использует то, что ему больше по душе.
    • +1
      Facebook, как и любая большая компания использует всё подряд, в том числе и Go github.com/facebookgo
  • 0
    Не уверен насчёт сравнения с Python. Я последний так и не осилил, например. Больше, чем пишешь логику, ломаешь голову над тем, как сделать это поизящнее. А с Go правильное решение, как правило, самое очевидное.
    • +1
      Да ну… Пример в студию, пожалуйста.
      PS. Zen of Python:
      There should be one-- and preferably only one --obvious way to do it.
      • –1
        import random
        import string
        
        ALPHABET = string.ascii_letters + string.digits
        
        def first(number_of_letters):
            random_string = ""
            for _ in range(number_of_letters):
                letter = ALPHABET[random.randint(0, len(ALPHABET) - 1)]
                random_string += letter
            return random_string
        
        def second(number_of_letters):
            return "".join(random.sample(ALPHABET, number_of_letters))
        
        print first(10)
        print second(10)
        


        Всегда есть больше 1 способа :)
        • +1
          Правда в вашем случае first и second делают разные вещи — в частности, first может вернуть строку, в которой буквы повторяются.
        • 0
          Да, но нормальный один =). В этом случае чем больше символов, тем больше разница в скорости.
        • +2
          Сложение строк в цикле конкатенацией это плохой способ почти в любом языке, как мне кажется.
  • +1
    «На Go маленькие программы можно писать быстрее чем на C++, примерно так же быстро, как на Python. И ощущения от языка примерно такие же.» — это Вы, видимо, JSON не пробовали парсить. После Python или любого другого языка с динамической типизацией, все эти адовые структуры и\или приведения типов на каждый чих — это ад. Код для парсинга простейшего JSON-объекта получается раз в 10-20 больше, чем на Python. Что еще не нравится в Go? Ошибки по возврату функции. Вот это реально вредная штука. Если Вы везде пишете обработчик ошибок, то почему бы просто не сделать выбрасывание Exception'а по-умолчанию? Ну а если Вы не проверяете возвращаемые значения, то тогда Ваш код — это грязная ядерная бомба, которая взорвется в самый неподходящий момент.

    В целом, если бы не мощная поддержка Гугла, Go так и остался бы мало кому известным еще_одним_языком. Лучше бы Гугл допилил D, вот где действительно шикарная замена C++.
    • 0
      Мне кажется если вы не поняли как писать на языке, это не значит что язык чем-то плох.
      Json в Go парсятся просто великолепно, описываете структуру и вперёд. Не хотите описывать структуру? Есть библиотеки, позволяющие легко получить данные по пути /one/two[2]. В Go вы точно знаете что получили нужную структуру и данные, в python вам придётся всё проверять, в любом случае.
      Ошибки в Go это просто обычные значения, хотите правильный код — обрабатывайте, не хотите — будьте внимательнее в дальнейшем.
      Вы пытаетесь натянуть шкурку одного животного на другое и удивляетесь, что вместо носа клюв.
    • +1
      Мое личное мнение, что json в коммуникациях machine-to-machine с фиксированным протоколом это вообще вещь достаточно вредная. Если данные действительно динамические и посылается совсем что угодно, то json — хорошее решение. Но там где протокол постоянен лучше использовать нечто вроде protobuf. Json, msgpack это некая структура данных где они могут быть а могут и не быть и это нужно постоянно проверять, в то время как в protobuf и подобных это реализовано на уровне протокола и его пользователь уже имеет некоторые гарантии того, что данные представляют из себя то что он ожидает.
      Go же в тех случаях где с json приходится работать на уровне протокола старается хоть как то жестко связать его в структуру, что по моему хорошо. Кроме того никто не запрещает работать в Go с json также как в javascript или python, можно также не анмаршалить его в структуру, а просто обращаться по какому-то пути к элементам json.
      • +1
        Кстати в стандартной библиотеке Go для этот есть gob, нативный бинарный формат сериализации Go объектов.
        • 0
          Gob хорошая вещь, но работает только в Go. :(
    • 0
      Имхо такая обработка ошибок как там неудобна и лучше бы они сделали исключения, в этом соглашусь. Я вообще не думаю что Go какой-то очень крутой язык, по моему достаточно средний даже. Совсем простые вещи там пишутся легко, но если требуется что-то более сложное, то языка на это явно не хватает. Для наших задач Go хорош и мы его любим за это, простые вещи можно разрабатывать и тестировать быстро.
      Думаю Google сыграл большую роль в популяризации языка, без него ему было бы намного хуже.
      • +4
        Меня по началу тоже ломало отсутствие исключений в Go. Позже, поразмыслив, счел что в этом есть определенный резон…
        Проблема исключений в том, что они в рамках идеологии многопоточности Go не универсальны. Они могут работать лишь в пределах одного goroutine. А учитывая, что конкурентность в Go это основная фишка, исключения оказываются бесполезны чуть менее, чем полностью…
        А главное, я соглашусь с Робом Пайком, что ошибки в современной сетевой среде — это не исключения. Это обычная ситуация, которую надо явно учитывать и обрабатывать в логике программы.
        А бросание исключения в надежде, что кто-то там его поймает, это довольно безответственный подход. Для таких товарищей наверняка в аду стоит отдельный котёл…
        • 0
          Так никто же не говорит бросать в надежде на что-то. Есть же checked exceptions, которые в обязательном порядке должны быть обработаны.
          • 0
            Использование исключений для управления нормальным (не исключительным) control flow — очень спорный подход. Например, такой код довольно сложно анализировать, что приводит к практической невозможности его заJIT'ить.
            • 0
              Смотря где и в каких ситуациях. В том же Erlang — это единственный способ не разводя кучу вложеных case-сов или монструозных конструкций прервать выполнение и вернуть результат.
              • 0
                Термин checked exception намекает на Java, в котором сказанное мной справедливо. Если говорить про Erlang или какой-нибудь Scala/Akka, то там используются куда более дешевые механизмы.
            • 0
              Welcome to raise_StopIteration_Python_World =)
    • +1
      В Go есть panic/recover, если что. А классические исключения провоцируют использование их для обработки основной логики, что не правильно.
  • 0
    Недостатком C++ для нас был явный оверкилл для наших целей, на разработку уходила уйма времени, также большой проблемой для нас было то, что плюсовый фреймворк для Кокаина не представлял никакой возможности работать асинхронно, кроме как с помощью коллбэков.

    При том, насколько легко честные легковесные fiber'ы реализуются в C (для C++, возможно, понадобится чуть допилить проброс исключений) прямо «на коленке», эту фразу корректнее переписать: «Нам хотелось чего-то нового и предлогом послужило...»
    • +2
      Да, сделать на коленке, допилить проброс исключений, форкнуть кокаиновый фреймворк и сделать так чтобы он со всем этим работал. Очень тривиальная задача.
  • 0
    В Qt C++ работать асинхронно и тестить асихнронные вызовы так же приятно, ИМХО.

    Я рассмотрел эту возможность здесь на примере Yandex Dictionary Api.

    P.S.: это не реклама, просто тема эта мне явно близка.
    • 0
      В статье ни слова как это всё работает внутри Qt. Может раскроете тему?
      • +1
        Отличная мысль, если это востребовано.
        Попробую в ближайшее время написать про это.
      • 0
        Работает на основе сигналов и слотов, схема похожая на работу с коллбэками, но гораздо приятнее. Лично меня асинхронный метод, который выглядит как синхронный в Go сбивает с толку.
        • 0
          Сигналы и слоты это просто интерфейс, вы можете их сделать хоть в php. Что там внутри, вот что интересно.
  • 0
    А какой версией go вы пользуетесь? Я вот что-то уже два дня имею головную боль от попытки отладить go 1.3 (или 1.3.1) с помощью gdb 7.7 (или 7.8).
    • 0
      1.3.1
    • 0
      Мне кажется это плохая идея, если вы не знакомы с устройством runtime'а или не являетесь разработчиком оного runtime'а… Сломаете себе мозг. Лучше используйте отладочные логи и/или TDD.
      • 0
        Насколько я помню, отладчик достаточно сносно работает, я правда не пробовал его в 1.3.x. Но до этого серьезных проблем не было.
        • 0
          Ну, в сносности я не сомневаюсь… Просто, если приложение многопоточное с кучей взаимодействия между потоками, то большую часть времени вы будете дебажить рантайм, чем свой код… Я в этом смысле.
      • 0
        Так я отлаживался в gdb, чтобы понять почему у меня логи не пишутся)). Не пишутся, потому что в log4go bug.

        А главная проблема при отладке — это при очередном вызове gdb next, программа начинает выполняться без остановки до следующей точки останова или, если точки нет, до конца. Я вот думал, может это из-за того что в 1.3 поменялась модель роста стека и gdb начинает казаться что он в другом фрейме. Но если у остальных людей все работает, значит это просто у меня руки кривые (.

        Ну и info goroutines выдает Python Exception <class 'gdb.error'> Attempt to extract a component of a value that is not a (null).
    • 0
      По-моему, отлаживать код на Go отладчиком — вообще не самая удачная затея.
  • +1
    Кстати, мы ищем человека к нам в команду в Санкт-Петербурге company.yandex.ru/job/vacancies/devbrows_go.xml :)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка