Починка электронного правительства Объединенного Королевства посредством языка Go

в 23:39, , рубрики: перевод, метки:

Это первая ласточка в серии публикаций о новом динамическом HTTP маршрутизаторе поддерживающем GOV.UK. Это письмо проливает свет на наш порыв, объясняет решимость и подитоживает приобретенный опыт.

Зачем это нам сдалось.

GOV.UK это уникальный правительственный домен, предоставляющий сервисы и информацию сотен правительственных подразделений в одном месте. Это специально, чтобы скрыть армию департаментов и коллегий подъедающихся на оказании незамысловатых услуг. Разумеется за фасадом, GOV.UK — это не монолитная огромная программа, а конгломерат небольших приложений предназначенных для выполнения одной задачи, в духе UNIX. С целью представить эти приложения как единый вебсайт нам необходим сгусток технологий способный препроводить запросы пользователей к надлежащим сервисам: HTTP маршрутизатор.
Когда мы начинали, в октябре 2012, задачу маршрутизации исполняли три единообразно настроенных экземпляра Varnish на передовой линии всего хозяйства. Varnish заслуживает самых высоких похвал, но мы вероятно подошли к границе его возможностей. Само собой на настроечные скрипты VCL нельзя было смотреть без содрогания

if (req.url ~ "^/autocomplete(?.*)?$|^/preload-autocomplete(?.*)?$|^/sitemap[^/]*.xml(?.*)?$") {
  <%= set_backend('search') %>
} else if (req.url ~ "^/when-do-the-clocks-change([/?.].*)?$|^/bank-holidays([/?.].*)?$|^/gwyliau-banc([/?.].*)?$") {
  <%= set_backend('calendars') %>
} else if (req.url ~ "^/(<%= @smartanswers.join("|") %>)([/?.].*)?$") {
  <%= set_backend('smartanswers') %>
} else if (req.url ~ "^/child-benefit-tax-calculator([/?.].*)?$") {
  <%= set_backend('calculators') %>
} else if (req.url ~ "^/stylesheets|^/javascripts|^/images|^/templates|^/favicon.ico(?.*)?$|^/humans.txt(?.*)?$|^/robots.txt(?.*)?$|^/fonts|^/google[a-f0-9]{16}.html(?.*)?$|^/apple-touch(.*)?.png$") {
  <%= set_backend('static') %>
} else if (req.url ~ "^/(designprinciples|service-manual|transformation)([/?.].*)?$") {
  <%= set_backend('designprinciples') %>
...
} else {
  <%= set_backend('frontend') %>
}

Если не бесчинства с регулярными выражениями в условиях, от которого кровью наливаются глаза у битого жизнью системного администратора, то что же не так с этим обустройством?
Основные напасти понудившие нас переосмыслить HTTP маршрутизатор таковы:
1. Ремонтопригодность: аккумулирование списка всех маршрутов в одном файле влечет за собой нежелательно частые шаманские танцы с бубнами. Когда приложение требует изменить URL за который отвечает, мы вынуждены обновлять VCL скрипты вместе с приложением, операция требующая известной изворотливости. Хуже того, необходимость обновления единого файла насыщенного мистическим синтаксисом и велосипедами выводит на сцену новые риски при изменении любого URL, что заметно нас затормаживает.
2. Производительность труда: Varnish стойко сносит наши надругательства над своим конфигурационным языком, но заключительные строки помянутого VCL скрипта вызывают у нас неотступную мигрень.
Фортели производительности на самом деле послужили толчком. GOV.UK скрывает за фасадом десяткам тысяч URL и нам не уперлось бдеть над всем этим богатством в конфигурации Varnish. Нет худа без добра, по большей части этот сонм URL обслуживается всего двумя приложениями, frontend и whitehall, которые соответственно преподносят гражданам главное — контент (вроде browse pages) и government corporate publishing pages. Эти приложения в свою очередь получают контент через contentAPI, внутренний интерфейс к базе данных, которая хранит всю первозданую мудрость GOV.UK. А значит, любой запрос к фасаду после свистопляски с вереницей условностей VCL может породить за сценой следующие запросы
1. Varnish -> frontend
2. frontend -> contentAPI(«нет ли у вас этакой странички и хорошо бы в доступном мне формате»)
3. Если frontend не владеет контентом он возвращает 404 и наш вебсервер nginx попытает счастья с приложением whitehall.
4. whitehall -> contentAPI
Очуметь? Ага. Даже если все запросы обрабатываются быстро (и «быстро», вы думаю заметили, в Rail приложении это не то, чтобы стремительно), мы все еще говорим о, по меньшей мере, 200ms для 404 или 150ms, чтобы вернуть любую страницу whitehall. Пожалуй самое досадное то, что для каждого успешного запроса к whitehall мы делаем два запроса к серверам приложений возвращающих 404. Каждый сервер приложений имеет ограниченное число потоков обработки (мы ставим unicorn перед Ruby приложением), как следствие, пока 404 обреченный запрос уныло следует к финалу, никакой другой запрос не может быть обработан тем же процессом.
Нужно что то менять.

Прототип нового маршрутизатора.

Тогда в апреле я потратил несколько дней прикидывая, а что собственно улучшенный маршрутизатор должен улучшить. Я принял решение использовать Go. Простота языка и гарантии компилятора Go позволяют ему прекрасно уживаться с базовыми компонентами нашей HTTP инфраструктуры и несколько беглых экспериментов с замечательным пакетом net/http укрепили меня в этом решении. В частности, модель многозадачности реализованная в языке Go делает до смешного простым создание высокопроизводительных приложений завязанных на I/O, каковым приложением безусловно и должен быть правильно построенный маршрутизатор.

Оборудование префиксного дерева trie

Первым делом встал вопрос, как хранить и искать записи в таблице маршрутов. GOV.UK содержит отличные URL отражающие логическую структуру сайта. (Например, главная страница Министерства Здравоохранения живет на /government/organisations/department-of-health, дочерняя страница списка департаментов и учреждений проживающего на /government/organisations). Ввиду древовидной структуры URL и, по сути своей, префиксной маршрутизации(поскольку, например, все включающее /government обслуживается одним приложением), естественным выбором стало префиксное дерево, trie.
Реализовать префиксное дерево на Go оказалось парой пустяков. Результатом (который, как и все упоминающееся в этом письме, доступен на GitHub) стала структура данных способная отражать срезы массивов slice строк ([]string{«government», «organisations»}) на произвольный тип (interface{}, говоря на языке Go). Встроенная в язык поддержка тестирования придавала процессу особую негу. Несмотря на то, что это был прототип, написание тестов не потребовало чрезмерных усилий, так 80 строк пакета trie обернулись не более чем 200 строками тестов с данными(data-driven-test, DDT).

Поддержка HTTP

Следующим шагом стало использование префиксного дерева в качестве таблицы маршрутизации. Go располагает самобытной (бесспорно прекрасно спроектированной) HTTP библиотекой, net/http, в которой во главу угла поставлен обработчик(handler), или http.Handler. Типом http.Handler является интерфейс. Ширина полей не позволяет пуститься в толкование системы типов Go и место в ней типа интерфейс, но правомерным будет сказать: если вы можете реализовать метод ServeHTTP(ResponseWriter, *Request) на своем типе, то этот ваш тип можно использовать как http.Handler.
Именно в этом и состоит предназначение пакета triemux. Mux, сокращение от multiplexer, коммутатор, это термин используемый Go для обозначения компонента принимающего и маршрутизирующего запросы в разных направлениях для дальнейшей обработки на основании свойств(например URL) этих запросов. Другими словами это HTTP маршрутизатор. Поскольку triemux удовлетворяет http.Handler он является HTTP маршрутизатором и может использоваться наравне с предопределенным ServeMux из стандартной библиотеки Go. Наше поделие добавляет толику защищенности параллельному доступу к таблице маршрутизации(замок чтение-запись), что позволяет динамически обновлять таблицу при этом не прерывая обслуживание текущих запросов.
Элегантности набившему оскомину шаблону http.Handler добавляет тот факт, что коммутатор(mux) сам вполне http.Handler, не более чем способ перенаправить трафик к другим http.Handler обработчикам. triemux не строит предположений относительно конструкции обработчиков, ему одинаково куда коммутировать. Здесь на паркет выходит пакет router.

Динамическая загрузка маршрутов.

Чтобы действительно решить проблемы очерченные в первых строках письма мы должны подгружать маршруты из некоего хранилища, которое может обновляться приложениями при их развертывании. Мы вовсю используем MongoDB под капотом GOV.UK и пакет router являет связующее звено triemux с базой данных Mongo. Маршруты загружаются в память при запуске системы и трафик перенаправляется к одному из движков обработки (тоже определенных в базе данных) посредством встроенной reverse proxy factory. Такая компоновка привносит ряд приятностей. А именно, мы можем динамически загружать маршруты при развертывании приложений и подменять таблицу маршрутов атомарно, не роняя запросов при этом. Если дела пойдут неважно при перезагрузке маршрутов(например не складывается разговор с Mongo), мы легко можем зарегистрировать отложенную процедуру восстановления гарантирующую бесперебойное продожение маршрутизации.

Маршрутизатор, исполнено.

К началу работ над новым маршрутизатором для GOV.UK за мной не числилось практически ничего работающего, написанного на Go. Однако весь этот джаз занял всего два с половиной дня, причем результат трудов превзошел по производительности наш существующий производственный конвейер на несколько порядков.(Фактически у меня не вышло замерить отзывчивость маршрутизатора. Всю дорогу выходили замеры тестовых серверов за спиной маршрутизатора, а не самого маршрутизатора как такового.)
Впоследствии я стал называть это «необузданной эффективностью Go»(с разрешения Eugene Wigner). Go компактный язык, целиком умещающийся в голове, что по большей части и позволило мне стать эффективным в крайне сжатые сроки. Но размер языка обманчив в отношении его выразительности, качества стандартных библиотек и поразительной легкости, с которой достаточно сложные сущности производятся из немудреных запчастей (в нашем случае, trie -> triemux -> router).
Со всей уверенностью могу заявить, я вынес самые приятные ощущения из своего похождения в края прототипов, но от работающего прототипа до боевого строя путь неблизкий, в особенности касательно смыслообразующих компонент GOV.UK. Последующие посты растолкуют, как мои коллеги проведут куда как более тяжелую работу по тестированию и разворачиванию нового маршрутизатора перед национальным ресурсом.
Оригинал

Автор: uvelichitel

Источник


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


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