[Перевод] Go с точки зрения PHP программиста
Предлагаю вашему вниманию перевод статьи Go from PHP engineer’s perspective с сайта sobit.me.
Будучи PHP программистом, задумывались ли вы об идее изучения других языков программирования?
Уже много лет выбор многих компаний падает на PHP для создания полноценных монолитных приложений. Более того, за последние 5 лет фреймворки (Symfony, Laravel, Zend), инструменты (Composer, Monolog) и стремительно растущее сообщество (PHP-FIG) помогли многим разработчикам в создании программного обеспечения на уровне предприятий. Многие компании, такие как Facebook, Yahoo!, Wikipedia, Wordpress, Tumblr, начинали свою историю с PHP, и это не помешало им стать успешными в последующие годы.
Однако, успешный бизнес развивается, а с ним растет и необходимое количество разработчиков для поддержания успешного роста. Организационная структура дает понять, что было бы неплохо разбить существующее монолитное приложение. В определенный момент стратегия начинает стабилизироваться и команды сосредотачиваются на независимых сервисах.
В этой статье мы попытаемся понять, как далеко мы сможем зайти, имея в арсенале только PHP, и где может вступить Go, чтобы помочь решить проблемы, с которыми нам предстоит столкнуться.
PHP и микросервисы
Каждый PHP разработчик знает, что, если ему удасться довести время инициализации большого монолитного приложения до 30 мс, это отличный результат! Добавить к этому 50–100 мс для обработки самого запроса, и перед нами поразительное общее время ответа.
Так как мы планируем разбить наш монолит, что произойдет, если мы решим придерживаться подобной архитектуры и для наших сервисов? Простые вычисления, и мы уже тратим 300 мс только на инициализацию, если сервисы обратились друг к другу 10 раз в течение единого запроса. Неприятный пользовательский опыт!
В то время, как PHP сияет в мире веб-разработки, его возможности в микросервисных архитектурах еще не созрели. Естественные термины — такие как обработка времени ожидания, проверки здоровья, сбор метрик, прерыватели — зачастую незнакомы PHP программисту. Многие из них можно найти в различных фреймворках во многих других языках прямо из коробки. Поддержка же их в экосистеме PHP слишком мала, если присутствует вообще.
Встречайте Go
Из Википедии:
Go (часто также Golang) — компилируемый, многопоточный язык программирования, разработанный компанией Google. Первоначальная разработка Go началась в сентябре 2007 года, а его непосредственным проектированием занимались Роберт Гризмер, Роб Пайк и Кен Томпсон занимавшиеся до этого проектом разработки операционной системы Inferno. Официально язык был представлен в ноябре 2009 года.
Go был разработан в Google. Это не было веселым 20%-ным проектом, также он не предназначался добиться что-то, недоступное другим. Его создали всего лишь из-за разочарования от сложности, которую создавали очень большие команды, работая над очень большими частями приложения на языках с большим набором возможностей.
Несмотря на такие замечательные черты, как легкий параллелизм или быстрая компиляция, основная черта, делающая язык особенным, — это невероятная простота. К слову, количество ключевых слов в Go — 25, в то время, как в PHP их насчитывается 67.
Go появился примерно в то же время, когда PHP получил поддержку пространства имен (2009). Спустя лишь 6 лет на нем созданы дюжины великолепных проектов, среди прочих Docker, Kubernetes, etcd, InfluxDB. Множество компаний, таких как Cloudflare, Google, Dropbox, SoundCloud, Twitter, PayPal, доверяют ему при написании своих бэкенд систем.
Возвращаясь к микросервисам
Давайте пройдемся по тем проблемам, о которых мы недавно говорили, и попытаемся понять, чего же мы можем добиться с использованием Go.
Go приложения быстро компилируются в машинный код. Многие люди твердят, что время компиляции настолько быстрое, что за то время, пока они переключаются в браузер и нажимают «Обновить», приложение уже перекомпилировано, перезапущено и обрабатывает запрос. Даже в больших базах кода присутствует ощущение, что работаешь с интерпретируемым языком, прямо как с PHP.
Традиционный «Hello, World!» API укладывается в 10 строк кода и отвечает быстрее миллисекунды. Возможности использования всех ядер процессора позволяет разработчикам параллельно запускать сложные части приложения. Экосистема позволяет выбрать любой транспортный протокол, например JSON по HTTP, gRPC, Protocol Buffers или Thrift. Запросы легко прослеживаются в Zipkin. Метрики экспортируются в различные бэкенды, от statsd до Prometheus. Есть возможность ограничить пропускную способность запросов с помощью ограничителей и защитить общение клиент-сервис с помощью прерывателей.
Больше о Go
Язык
Go — язык сильной типизации, что заставляет указывать тип переменной при ее объявлении:
var name string = "sobit"
Переменные могут определять тип по присваемому значению. Код выше аналогичен упрощенной версии:
var name = "sobit"
// или еще проще:
name := "sobit"
Теперь давайте вспомним, как мы меняем значения двух переменных в PHP:
И эквивалент в Go:
first, second = second, first
Это возможно, благодаря множественным возвращаемым значениям. Пример с функцией:
func getName() (string, string) {
return "sobit", "akhmedov"
}
first, last = getName()
В Go коде вы не найдете привычных foreach
, while
и do-while
. Они объединены в единый оператор for
:
// foreach ($bookings as $key => $booking) {}
for key, booking := range bookings {}
// for ($i = 0; $i < count($bookings); $i++) {}
for i := 0; i < len(bookings); i++ {}
// while ($i < count($bookings)) {}
for i < len(bookings) {}
// do {} while (true);
for {}
Несмотря на то, что в Go нет так называемых «классов» или «объектов», в нем присутствует тип переменной, который соответствует тому же определению, где структура данных объединяет код и поведение. В Go это называется «структурой». Проиллюстрируем на примере:
type rect struct { // объявляем структуру
width int
height int
}
func (r *rect) area() int { // объявляем функцию структуры
return r.width * r.height
}
r := rect{width: 10, height: 15} // инициализация
fmt.Print(r.area())
Иногда бывает полезным создавать и сложные структуры. Например:
type Employee struct {
Name string
Job Job
}
type Job struct {
Employer string
Position string
}
// и инициализиция
e := Employee{
Name: "Sobit",
Job: {
Employer: "GetYourGuide",
Position: "Software Engineer",
},
}
Есть множество других особенностей, о которых мне бы хотелось рассказать, но это было бы дублированием отличного документа Effective Go с официального веб-сайта и нескончаемой статьей. Но я хочу воспользоваться моментом и представить вам параллельность в Go, которая, на мой взгляд, является одной из самых интересных тем об этом языке. Перед тем, как мы начнем, вы должны знать две вещи о параллельности в Go — горутины и каналы.
Горутина имеет простую модель: это функция, выполняемая параллельно с другими горутинами в едином адресном пространстве. Когда вызов завершается, горутина тихо выходит. Простейший пример — создать функцию сердцебиения, которая выводит на экран сообщение «Я жив» каждую секунду:
func heartbeat() {
for {
time.Sleep(time.Second)
fmt.Println("I'm still running...")
}
}
Как нам теперь запустить ее в фоновом режиме, чтобы при этом иметь возможность продолжать выполнять другие задачи? Ответ проще, чем вы могли себе представить, просто добавьте префикс go
:
go heartbeat()
// keep doing other stuff
Такой подход сравним с инициацией событий в стиле «запусти-и-забудь». Но что, если нам не нужно «забывать», и мы заинтересованы в результате выполнения функции? Именно в этом случае на помощь приходят каналы:
func getName(id int, c chan string) {
c <- someHeavyOperationToFindTheName(id)
}
func main() {
c := make(chan string, 2) // выделить канал
go getName(1, c) // запустить
go getName(2, c) // запустить снова, не дожидаясь
// продолжить выполнение других задач
fmt.Printf("Employees: %s, %s", <-c, <-c) // объединить
}
Подводя итог, мы только что запустили одну и ту же функцию дважды, при этом позволили приложению работать над другими задачами, и запросили результаты в тот момент, когда они понадобились нам.
Инструменты
Go был спроектирован, повторюсь, с простотой в уме. Авторы предприняли радикальный подход к решению повседневных действий разработчиков. Попрощайтесь с бесконечными спорами «таб или пробел» и стандартами программирования от групп сообщества, которые пытаются хоть как-то облегчить обмен кодом. Go содержит в себе инструмент go fmt
, который берет на себя ответственность за стиль вашего кода. Не нужно больше посылать друг другу файлы конфигураций IDE, не нужно пытаться запомнить, надо ли ставить открывающую скобку на той же строке или следующей.
Документацию библиотеки можно прочитать с помощью go doc
, в то время, как go vet
поможет найти потенциальные проблемы в коде. Установка сторонник библиотек происходит простой командой go get github.com/[vendor]/[package]
, а тесты запускаются с помощью go test [package]
. Как видите, большинство инструментов, включаемые разработчиками в каждое приложение в каждом языке, уже тут, прямо из коробки.
Развертка
Развертка приложения — необходимое действие и не зависит от языка программирования и того, что пишут с его помощью. Будучи PHP программистом, сколько Capistrano или Envoy конфигураций вы написали за свою профессиональную карьеру? Сколько файлов, измененных вручную, вам приходилось переносить на хостинг-провайдеры во времена FTP?
Вот так выглядит наиболее распространенная и простая развертка PHP приложения:
- Выгрузить последний код на целевой сервер в новую папку релиза
- Скопировать закэшированные зависимости и установить измененные
- Скопировать конфигурационные файлы среды
- Запустить все скрипты для разогрева приложения
- Направить ссылку текущего релиза на новую папку релиза
- Перезапустить PHP-FPM
Некоторые команды используют более продвинутый подход:
- Выгрузить последний код на сервер создания билдов
- Создать «билд» (установить зависимости, разогреть кэши, и т.д.)
- Создать дистрибутивный «артефакт» (архивированный
tar.gz
файл) - Перенести артефакт на целевой сервер
- Разархивировать в новую папку релиза
- Направить ссылку текущего релиза на новую папку релиза
- Перезапустить PHP-FPM
Конечно, помимо этого, вам надо убедиться, что на целевом и сервере создания билдов установлены хотя бы PHP-FPM и PHP, и лучше бы им совпадать по версиям как между собой, так и с версией, что разработчики используют локально.
А теперь поговорим о процессе развертки Go приложения:
- Выгрузить последний код на сервер создания билдов
- Создать билд (заметьте отсутствие кавычек)
- Перенести артефакт (снова без кавычек) на целевой сервер
- Перезапустить запущенное приложение
И все. Единственная разница между потенциально простой и продвинутой версией заключается в том, есть ли для этой задачи автономный сервер или нет.
Прелесть в том, что даже не нужно устанавливать Go на целевые сервера, и даже если они работают на разных операционных системах или построены на разных архитектурах процессора, вы, тем не менее, можете подготовить артефакты для них всех (даже для Windows) с одного и того же сервера билдов или любой другой машины.
Вывод
Никто никогда не рекомендовал разбивать систему на микросервисы до того, как она созреет достаточно для этого. Будучи небольшим, бизнес должен оставаться гибким и быстро реагировать на удобные случаи на рынке. Это — не идеальная среда для создания микросервисов, и можно получить больше пользы от монолитного приложения. В этом случае PHP с его экосистемой идеально подходит под эту стратегию.
Когда организация стабилизируется и в ней становится легко чертить границы ее контекстов, строить микросервисы на PHP для их поддержки может быть болезненным делом. Существуют разные инструменты и техники во многих других языках в помощь этой архитектуре. Вы рискуете тем, что либо создадите свои собственные инструменты, либо вовсе откажетесь от них — оба варианта могут дорого обойтись вашей организации.
С другой стороны, создание микросервисов на Go стоит рассмотрения. Язык прост для написания и понимания. Кривая обучения не настолько крута, как в случае Java или Scala, а производительность не сильно отстает от C. Время компиляции помогает разработчикам оставаться эффективными, в то время, как поддержка многоядерных и сетевых машин позволяет создавать мощные приложения.
Дополнительные материалы
Лучшее место для новоиспеченного гофера, на мой взгляд, раздел обучения на официальном веб-сайте. Он включает в себя интерактивное введение в Go, где можно опробовать его прямо в браузере, а также документ лучших практик, который поможет в написании чистого, идиоматического кода.
Если этого недостаточно, есть также собрание инициатив сообщества, чтобы помочь отточить полученные навыки.
Если вы любите IDE от JetBrains так же, как и я, стоит посмотреть в сторону плагина Go для IntelliJ, который был создан в основном теми же JetBrains разработчиками. Он может быть установлен почти на любую из их IDE, включая PhpStorm. Для других IDE и текстовых редакторов стоит посетить вики страницу.
Когда вы почувствуете, что готовы создать свой первый микросервис на Go для продакшна, я бы посоветовал взглянуть на Go kit. Это набор инструментов программирования, который пытается взять под контроль общие проблемы дистрибутивных систем, позволяя разработчикам сосредоточиться на бизнес-логике.
Менее значимое — это возможность создавать клиентские JavaScript приложения с помощью GopherJS, а также полностью нативные или SDK приложения для iOS и Android.