Неуловимый Go.
Помните анекдот про неуловимого Джо? Именно восклицанием «Да кому он нужен!», прозвучавшим в форме вопроса "ЗАЧЕМ?", был встречен на Хабре релиз первой стабильной версии GO 1.
Именно на этот вопрос я хочу ответить циклом статей, оформленных в необычном для Хабра формате — в виде пошаговой совместной разработки действующего веб-проекта — с живым обсуждением и добавлением функционала. А чтобы вдвойне оправдать внесение цикла ещё и в хаб «Высокая производительность», мы поставим перед собой задачу создать не просто «хомяка», а проект, который наглядно продемонстрирует habri et orbi способность выдерживать значительные естественные нагрузки.
Вместо аперитива: реализация простейшего динамического веб-приложения на языке Go работает в 5-20 раз быстрее аналогичной Python-реализации. И всего в два раза уступает скорости отдачи статики Nginx-ом.
В рамках этого проекта, помимо самого языка Go, мы косвенно затронем и другие (относительно новые) технологии веб-разработки — HTML5, CSS3, Redis, MongoDB. Также я постараюсь вытащить из закутков долговременной памяти некоторые из трюков в области безопасности и экономии на спичках, коих накопилось много за полтора десятка лет работы в этой области. Устраивайтесь поудобнее, запасайтесь терпением и кофе — под катом «много букв», а ведь это только вводная часть :)
Прежде всего, хочу поблагодарить администрацию Хабра за быстрый отклик и создание хаба, посвящённому языку Go. Признание любимого предмета всегда приятно, так что именно появление такого хаба и возникший интерес аудитории побудил меня впервые за два года присутствия на Хабре «взять в руки шашки».
Помимо этого, заранее приношу свои извинения за несколько старомодный стиль изложения, проскакивающие бородатые термины, некоторое косноязычие и проскакивающие перефразировки крылатых фраз на давно мёртвом языке — т.е. за привычки, которые я унаследовал из своего юридического прошлого.
Goрдость и предубеждение
О вкусах, как известно, не спорят. Поэтому я вынужден оставить за кадром свои восклицания о прекрасном синтаксисе, простоте и изяществе языка Go. Меньше всего мне бы хотелось увидеть в комментариях очередную битву тупоконечников и остроконечников. Исходя из этого, предлагаю говорить исключительно о материях, подлежащих простому и понятному измерению. Но даже сравнивая цифры, мне придётся сравнивать с другими языками, что также может быть воспринято как casus belli. Долго думал как бы выкрутиться из этой щекотливой ситуации, и решение мне кажется вполне соломоновым. Сравнивать я буду с другим своим любимым языком, с Python. Такой подход, как мне кажется, избавит меня от обвинений в необъективности. Так и запишем, чёрным по белому и жирными буквами: я тоже люблю Python!
Не хочу я повторять и банальные абзацы из документации, описывая сам язык, его философию и возможности. Буду исходить из того, что вы уже заинтересовались и при этом способны самостоятельно прочесть документацию.
К барьеру, Goспода! К барьеру!
Начнём со сравнения, которым я запугивал вас в предыдущем параграфе. Напишем два примитивнейших динамических приложения, которые:
- запускают вебсервер
- имеют роутинг и обработчик
- берут из контекста переменную
- подставляют её в обработчике и выдают персонализированное приветствие Hello, %s.
Ничего сложного, но это уже не статичный контент, пусть и с некоторым лукавством. Я предвижу множество возражений на тему того, что главными тормозами всё равно останутся дисковые операции и работа с БД. И с этим я не спорю. Но давайте вспомним про анонсированное содержание статьи. До оптимизации i/o и db мы ещё доберёмся, а пока посмотрим на цифры чистой скорости ответов.
1. Код приложения для Webapp2.py — весьма простого и быстрого минифреймворка на базе Paste:
import webapp2 class Hello(webapp2.RequestHandler): def get(self, name): self.response.write('Hello, %s!' % name) app = webapp2.WSGIApplication([ ('/(w+)', Hello), ], debug=False) def main(): from paste import httpserver httpserver.serve(app, host='127.0.0.1', port='8080') if __name__ == '__main__': main()
Код запуска: python habr.py
2. Код на языке Go:
package main import ( "fmt" "net/http" ) func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:]) } func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) }
Код запуска:go run habr.go
Ничего сложного, верно? Обратите внимание, насколько прост и понятен код на Go, по крайней мере для столь простой задачи. Оба листинга показывают один и тот же алгоритм. Слушаем порт, направляем все GET /(*) обработчику, который превращает (*) в выводимое имя после Hello. Полагаю, код должен быть понятен всем, кто работал с такой схемой.
5. Для сравнения добавим ещё и nginx, отдающий статическую страницу с текстом «Hello, habr». Внимание! Nginx запущен с настройками «из коробки», идущими в пакете nginx-light. Запас для разгона остаётся огромным, но об этом поговорим позже.
4. Тестовый стенд (не поворачивается язык назвать это сервером) следующей конфигурации:
Lenovo B460E / RAM:2G / CPU: Celeron Dual-Core T3500 @2.10Ghz
OS: Lubuntu 12.04 x86, kernel: 3.2.0-20-generic
5. Замеры будем производить примитивнейшим образом, утилитой ab из apache2-utils. Если хотите, можете сами проверить с помощью siege или другого привычного инструмента.
6. Результаты забегов
Py @ ab -c10 -n1000 http://localhost:8080/habr
(при большем значении "-n" Python версия уже не справляется)
Time taken for tests: 0.987 seconds
Requests per second: 1012.67 [#/sec] (mean)
Go @ ab -c10 -n1000 http://localhost:8080/habr
Time taken for tests: 0.197 seconds
Requests per second: 5080.24 [#/sec] (mean)
Nginx @ ab -c10 -n1000 http://localhost/
Time taken for tests: 0.101 seconds
Requests per second: 9895.50 [#/sec] (mean)
Усложним задачу.
Py @ ab -c100 -n1000 http://localhost:8080/habr
Time taken for tests: 1.045 seconds
Requests per second: 956.57 [#/sec] (mean)
Go @ ab -c100 -n1000: http://localhost:8080/habr
Time taken for tests: 0.199 seconds
Requests per second: 5021.14 [#/sec] (mean)
Nginx @ ab -c100 -n1000 http://localhost/
Time taken for tests: 0.110 seconds
Requests per second: 9054.77 [#/sec] (mean)
При "-c" >1000, python версия тест не проходит, Nginx держится огурцом, а вот Go «радует» совершенно непредсказуемыми результатами, показывая от 50 до 4000 requests per second. Объяснить этот феномен я не могу, поскольку столкнулся с ним впервые именно сегодня, на Ubuntu 12.04 и в версии Go 1. На ранних версиях (r60.3) Go держался стабильно. А может дело в ноутбуке, всё же комплектация явно не для HighLoad сервера :)
Подозреваю, что кого-то не устроит методика тестирования, настройки по умолчанию или отсутствие реализации на других языках. С удовольствием выслушаю ваши предложения и повторю тесты. Я прогнал много различных тестов и с разными настройками за два года существования (разработки) Go. И все тесты убедили меня в том, что Go является оптимальным инструментом для моего круга задач. Проводить и приводить их все — занятие неблагодарное, ведь всё равно опять окажутся недовольные.
Запрашивая веру на слово, приведу коротенькую выдержу по результатам из памяти.
Java иногда оказывается быстрее на 10-30%, но за счёт значительно большего потребления памяти (как самой VM, так и процессами). Опять же, приведённый пример (без использования фреймворков, только стандартные библиотеки) вполне наглядно демонстрируют минимум кода реализации. А чем меньше кода, тем меньше ошибок.
То же самое могу сказать о Net/Mono C# реализации.
C/C++ вне всяких сомнений позволяют создавать фантастически быстрые web-приложения с минимальными требованиями к ресурсам. Но сильно страдает скорость разработки.
Python и Ruby реализации проигрывают в скорости и создаваемой нагрузке приблизительно одинаково, PHP терпит сокрушительное поражение.
Анонс следующей части:
- Разбираем по косточкам код из примера Hello, Habr!
- Усложняем несколькими обработчиками
- Компилируем и запихиваем несколько Go-серверов под Nginx.
Благодарю за внимание. Надеюсь, что хоть кто-нибудь осилил введение и нашёл для себя что-нибудь интересное.
С нетерпением жду дискуссии, желательно цивилизованной.
Автор: Pirro