Go for IT. Часть первая

в 20:23, , рубрики: golang, highload, Веб-разработка, высокая производительность, метки: ,

Неуловимый Go.

Помните анекдот про неуловимого Джо? Именно восклицанием «Да кому он нужен!», прозвучавшим в форме вопроса "ЗАЧЕМ?", был встречен на Хабре релиз первой стабильной версии GO 1.

Именно на этот вопрос я хочу ответить циклом статей, оформленных в необычном для Хабра формате — в виде пошаговой совместной разработки действующего веб-проекта — с живым обсуждением и добавлением функционала. А чтобы вдвойне оправдать внесение цикла ещё и в хаб «Высокая производительность», мы поставим перед собой задачу создать не просто «хомяка», а проект, который наглядно продемонстрирует habri et orbi способность выдерживать значительные естественные нагрузки.

Вместо аперитива: реализация простейшего динамического веб-приложения на языке Go работает в 5-20 раз быстрее аналогичной Python-реализации. И всего в два раза уступает скорости отдачи статики Nginx-ом.

В рамках этого проекта, помимо самого языка Go, мы косвенно затронем и другие (относительно новые) технологии веб-разработки — HTML5, CSS3, Redis, MongoDB. Также я постараюсь вытащить из закутков долговременной памяти некоторые из трюков в области безопасности и экономии на спичках, коих накопилось много за полтора десятка лет работы в этой области. Устраивайтесь поудобнее, запасайтесь терпением и кофе — под катом «много букв», а ведь это только вводная часть :)

Прежде всего, хочу поблагодарить администрацию Хабра за быстрый отклик и создание хаба, посвящённому языку Go. Признание любимого предмета всегда приятно, так что именно появление такого хаба и возникший интерес аудитории побудил меня впервые за два года присутствия на Хабре «взять в руки шашки».

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

Goрдость и предубеждение

О вкусах, как известно, не спорят. Поэтому я вынужден оставить за кадром свои восклицания о прекрасном синтаксисе, простоте и изяществе языка Go. Меньше всего мне бы хотелось увидеть в комментариях очередную битву тупоконечников и остроконечников. Исходя из этого, предлагаю говорить исключительно о материях, подлежащих простому и понятному измерению. Но даже сравнивая цифры, мне придётся сравнивать с другими языками, что также может быть воспринято как casus belli. Долго думал как бы выкрутиться из этой щекотливой ситуации, и решение мне кажется вполне соломоновым. Сравнивать я буду с другим своим любимым языком, с Python. Такой подход, как мне кажется, избавит меня от обвинений в необъективности. Так и запишем, чёрным по белому и жирными буквами: я тоже люблю Python!

Не хочу я повторять и банальные абзацы из документации, описывая сам язык, его философию и возможности. Буду исходить из того, что вы уже заинтересовались и при этом способны самостоятельно прочесть документацию.

К барьеру, Goспода! К барьеру!

Начнём со сравнения, которым я запугивал вас в предыдущем параграфе. Напишем два примитивнейших динамических приложения, которые:

  1. запускают вебсервер
  2. имеют роутинг и обработчик
  3. берут из контекста переменную
  4. подставляют её в обработчике и выдают персонализированное приветствие 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

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js