В облака: путь архитектора программного обеспечения

В 1991 году, в том же году, когда зародилась всемирная паутина, трое из нас основали компанию. Наше видение состояло в том, чтобы создать систему программного обеспечения для работы банкоматов и обработки дебетовых и кредитных карт. Нашим уникальным преимуществом было то, что он будет работать в открытых системах, как тогда называлась Unix. Это было большим делом, потому что в то время все аналогичные системы работали либо на мэйнфреймах, либо на специальном отказоустойчивом оборудовании от Tandem или Stratus, поэтому наше решение снизило стоимость, работая на стандартном оборудовании.

Были веские причины, по которым наши конкуренты использовали специальное оборудование. Подумайте об эксплуатационных характеристиках банкомата: он должен быть открыт 24 часа в сутки, 7 дней в неделю, поэтому он должен быть устойчивым к сбоям компонентов. Клиенты рассчитывают получить свои деньги быстро, поэтому они должны всегда реагировать. В одни часы дня он будет очень загружен, в другие - тихо, поэтому он должен справляться с сильно меняющейся и непредсказуемой нагрузкой. О, и он не может потерять транзакцию ни при каких обстоятельствах. Это серьезный набор требований, и общепринятое мнение гласило, что программное обеспечение открытых систем не может его взломать.

Смокинг: ранняя распределенная система

Одним из наших секретных орудий был Tuxedo, «монитор» транзакций, разработанный Unix Systems Labs для обеспечения высокой доступности и исключительной масштабируемости для поддержки приложений, требующих тысячи транзакций в секунду в общедоступных распределенных системах.

Tuxedo предлагал несколько основных функций распределенного фреймворка. Самым важным было разделение функциональности на службы. Ранее мы пытались оторваться от монолитной разработки, используя множественные процессы, взаимодействующие через IPC (межпроцессное взаимодействие), но сервисы предлагали более общий подход. (Между прочим, в те дни мы кодировали на простом старом C; второе издание The C ++ Programming Language только что было опубликовано, но, честно говоря, мы еще не разобрались с объектной ориентацией) .

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

Еще одной отличительной особенностью Tuxedo был внешний файл конфигурации, в котором были определены серверы, службы и т. Д. Это было автоматически распространено на все узлы, на которых работала система. Это позволяло изменять параметры среды выполнения без перекомпиляции и повторного развертывания программного обеспечения.

Третья особенность Tuxedo, которую мы активно использовали, - это полевые буферы. В отличие от сообщений фиксированной длины или разделенных разделителями, к которым мы привыкли, полевые буферы больше походили на сообщения XML или JSON, с парами имя-значение и рядом поддерживаемых типов данных. Они были скомпилированы для повышения эффективности, и даже существовал предметно-ориентированный язык для опроса и управления содержимым этих сообщений.

Когда мы начали использовать фреймворк, быстро стало ясно, что синхронные вызовы - антипаттерн в этом типе системы. Они слишком долго удерживают ресурсы (службы) связанными, а асинхронный, управляемый событиями подход позволяет добиться гораздо большего распараллеливания и, следовательно, пропускной способности. Tuxedo включал возможность очереди сообщений для поддержки этой модели взаимодействия.

Хотя Tuxedo включал в себя своего рода базу данных, мы никогда ее не использовали, и после неудачной попытки использовать дешевый механизм базы данных мы остановились на Informix. Это была хорошая база данных в своем роде с такими функциями, как резервное копирование в режиме онлайн, но она оставалась единственной точкой отказа в распределенной системе.

Распространение базы данных

Перенесемся на несколько лет в начало нулевых. Мы продали нашу компанию, Java стала лингва-франка, а я работал в крупной межбанковской клиринговой палате, которая только что переходила от пакетной обработки к платежам в реальном времени. Это был настоящий сдвиг парадигмы для команды разработчиков, которая использовала для управления наборами данных из 50 миллионов транзакций с помощью хранимых процедур, для обработки одного платежа за раз с помощью обмена сообщениями в памяти.

Мы наткнулись на небольшую компанию, у которой была новаторская идея. Они использовали два узла обработки, каждый со своей собственной независимой базой данных Oracle, и написали программное обеспечение для выполнения двунаправленной синхронизации между ними. Это был первый шаг к действительно распределенной базе данных. К сожалению, теорема CAP не была широко известна в то время, поэтому мы никогда не могли понять, почему результаты всегда так или иначе не соответствовали результатам.

Пройдя еще несколько лет вперед, меня соблазнили аргументы Джо Армстронга в пользу Erlang как основы систем, которые никогда не останавливаются. Одна из его менее известных особенностей - это база данных под названием Mnesia (противоположность амнезии - типичного компьютерного юмора). Для меня это было две большие достопримечательности; во-первых, он предназначен для распространения; во-вторых, как часть дистрибутива Erlang, он ничего не стоит. Мне не удалось усвоить урок о том, что когда дело доходит до баз данных, дешево не обязательно означает хорошее!

Мы начали с Mnesia, работающей на одном узле, и функционально она вела себя точно так, как указано. Мы провели несколько тестов базовой производительности, а затем начали добавлять узлы, чтобы увидеть, насколько хорошо он масштабируется. Результаты были странными - чем больше узлов мы добавляли, тем хуже становилась производительность! С помощью одного из первых коммиттеров мы отследили проблему вплоть до распределенной блокировки. У нас было несколько записей в базе данных, где много конфликтов, и поскольку несколько узлов пытались заблокировать одну и ту же запись, им приходилось отступать и ждать. Чем больше узлов, тем дольше ожидание - отрицательная масштабируемость! Единственным решением было перепроектировать приложение и модель данных, чтобы избежать конфликтов и отдать предпочтение локальным узлам, а не распределенным блокировкам.

До сих пор нет универсального решения для требования строгой согласованности в распределенных системах, а теорема CAP утверждает, что этого никогда не может быть. Корнелия Дэвис отмечает, что некоторые облачные подходы не подходят для случаев, когда требуется высокая согласованность, и распределенные базы данных - одна из них. Большинство известных облачных баз данных, таких как Cassandra и Dynamo, обеспечивают конечную согласованность, чего недостаточно для финансовых транзакций. Согласованность можно улучшить с помощью квору чтения и записи кворума, но это снижает производительность.

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

Актеры, сообщения и события

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

Несмотря на замечательные возможности Erlang, его среда разработки была откровенно примитивной. Разработчики получали атавистическое удовольствие от использования редакторов vi или emacs вместо IDE, а количество доступного программного обеспечения с открытым исходным кодом было ничтожным по сравнению с Java. Мы боролись с базовыми функциями, такими как синтаксический анализ XML. Если бы только существовала среда Java, предлагающая аналогичные функции ...

Рассмотрим Akka - фреймворк, который явно заимствует у Erlang, но работает в JVM. Он соответствует модели супервизора Erlang и превращает gen_server в актера. Акторы объединены в системы акторов, которые образуют узлы в кластере. Кластер поддерживает прозрачность местоположения и обнаружение сбоев. Akka также объединяет сообщения и события, позволяя участникам создавать и использовать события с помощью модели pub-sub.

Как и gen_servers в Erlang, у акторов Akka есть встроенный шаблон для обработки конечных автоматов, который является отличным способом оркестровки событий структурированным и легко понятным способом - гораздо больше, чем шаблон хореографии, который приобрела популярность с появлением микросервисов. Еще во времена программирования на C мы реализовали жестко запрограммированные конечные автоматы, но в настоящее время мы моделируем конечные автоматы в UML и генерируем код из модели.

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

логирование

Еще во времена C и Tuxedo мы хотели регистрировать происходящее в целях отладки. Мы разработали собственные библиотеки журналов и вскоре начали генерировать мегабайты журналов, распределенных по нескольким серверам. Это затрудняло отслеживание выполнения транзакции, которая могла проходить через несколько серверов в течение своего жизненного цикла. Что еще хуже, мы на собственном горьком опыте обнаружили, что Unix stderr не буферизуется, поэтому высокие уровни отладки катастрофически сказываются на производительности.

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

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

Все функции, которые мы создали вручную, теперь доступны в виде инструментов с открытым исходным кодом: logback, Logstash, ElasticSearch и Kibana (стек ELK).

DevOps

У нас была отличная линия непрерывной интеграции, работающая с командами, использующими Scrum для доставки кода каждые две недели. Все вышло из строя, когда мы хотели развернуть в так называемых контролируемых средах - производственной, предварительной и тестовой системной интеграции. Была специальная команда по развертыванию, которая настаивала на пошаговом наборе инструкций в документе Word, которому они неизменно не следовали должным образом. Несмотря на то, что мы предоставили сценарии развертывания, они отказались использовать их, потому что «хотели контролировать ситуацию».

Хуже того: они также заставили нас перекомпилировать исходный код для каждой отдельной среды. Они так привыкли к жестко запрограммированным параметрам среды, что не могли понять идею системы, в которой код остается тем же, а в разных средах изменяется только файл конфигурации. Мои объяснения, что проверять одно, а потом воплощать в жизнь другое - безумие, были встречены полным непониманием.

В моем следующем проекте (все еще обрабатывающем платежи в реальном времени!) Мы начали гораздо лучше. Команды развертывания не было, и мы упаковали приложение в контейнеры Docker и использовали Ansible для развертывания по сценарию локально или в AWS. Мы используем файлы конфигурации Lightbend (ex-Typesafe), подключенные извне к контейнерам Docker, которые напрямую поддерживают импорт переменных конфигурации среды.

Вишенка на торте - Kubernetes, который дает нам независимый от среды инструмент для управления контейнерами. Помимо динамического масштабирования и перезапуска после сбоя на уровне актора, теперь у нас есть те же возможности на уровне контейнера.

Собственное облако

Есть много определений того, что представляет собой облачное приложение, например 12 факторов, и в этой статье были выделены несколько определяющих характеристик. Я чувствую себя немного похожим на персонажа Мольера, который с удивлением обнаруживает, что вот уже сорок лет я говорю прозой, не зная об этом - я создавал приложения которые все больше соответствуют критериям нативности облака, без (до недавнего времени) явного нацеливания на развертывание в облаке. Как это может быть?

Еще в 1991 году системы обработки карт были одним из немногих типов приложений, столкнувшихся с серьезной проблемой одновременного обеспечения высокой доступности, высокой отказоустойчивости, низкой задержки, высокой пропускной способности и гарантированной целостности. В девяностые годы большая часть программного обеспечения была сосредоточена на пакетной обработке и системах клиент-сервер, а в нулевые - на обслуживании динамических веб-страниц. Только в этом десятилетии крупномасштабные «постоянно работающие» системы, требующие стабильно низкого времени отклика, стали скорее правилом, чем исключением, повышая внимание к разработке соответствующих инструментов и методов.

Итак, пока я иду по тропинке, ведущей к облакам, мне кажется, что облака также дрейфуют ко мне. Теперь новички могут приземлиться прямо в точке, на достижение которой у меня ушло столько лет, но, как сказал певец Дрейк: «Иногда путешествие многому учит вас о вашем пункте назначения». Я определенно чувствую, что многому научился на своем пути, и я рад поделиться этими знаниями с другими, направляющимися к тому же пункту назначения. И я тоже все еще учусь.