Управление микросервисами или распределенными системами сопряжено со многими проблемами, и у Uber есть решения для многих из них.

Uber — это небольшая стартап-компания, вы возможно слышали о них раньше. Они предоставляют множество инструментов с открытым исходным кодом, которые создает их команда инженеров. И позвольте мне сказать вам, что они не шутники, они кажутся очень компетентными разработчиками.

Cadence — это распределенный, масштабируемый, надежный и высокодоступный механизм оркестрации — Uber GitHub Repository

Вы когда-нибудь пробовали Каденс? Если нет, я могу обещать, что вас ждет угощение. Cadence — это фреймворк для разработки распределенных систем.

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

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

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

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

Заранее хочу извиниться за очень длинную статью, обязательно возьмите с собой кофе

Давайте начнем учиться использовать этот удивительный инструмент. Полный код того, что мы будем строить, можно найти на GitHub.

Настройка сервера Cadence с помощью Docker-Compose

Первое, что нам нужно, это запустить сервер Cadence. Самый простой способ сделать это — использовать Docker-Compose. В Cadence Github есть несколько примеров файлов docker-compose, разные файлы используют разные решения для постоянного хранения. В моем случае я буду использовать MySQL.

Я использую WSL 2 в своей среде разработки, поэтому мне пришлось внести изменения в Compose, чтобы он работал. Кажется, есть проблемы с композицией для монтирования файлов, вы можете посмотреть проблему с git здесь.

Мы также хотим, чтобы постоянное хранилище было выбрано для хранения данных при перезапусках, это делается путем добавления монтирования в состав, в этом случае я использую MySQL. Мы добавим монтирование для Mysql и Prometheus, чтобы сохранить данные.

Настройка Cadence CLI — Управление с помощью терминала

Для управления каденс-сервером вы будете использовать каденцию CLI. Вы можете запустить CLI cadence либо с помощью Docker, либо собрав двоичный файл. Я думаю, что использовать docker очень просто и быстро, если вы просто хотите попробовать его, но я все же рекомендую собрать двоичный файл CLI.

Это просто: оформите заказ или клонируйте репозиторий Cadence GitHub. В репозитории есть файл make, который мы запустим, но перед этим есть несколько зависимостей, которые, возможно, потребуется разрешить.

У вас должен быть установлен Go. Если нет, перейдите по этой ссылке, чтобы скачать его.

В проекте несколько бинарников, каждый из которых управляет своей частью системы. Нас пока интересует только CLI.

git clone https://github.com/uber/cadence.git
cd cadence && make bins
#Verify it works
./cadence --help 

Это выведет несколько двоичных файлов, которые мы можем использовать для управления Cadence в папке, в которой вы работаете. Я рекомендую переместить эти двоичные файлы в папку bin и добавить ее к вашему пути.

Настройка проекта

Мы будем создавать приложение, которое подключается к Cadence и начинает добавлять вещи по ходу дела. Я рекомендую скопировать docker-compose, который мы хотим запустить, в новую папку проекта. В своих примерах я буду использовать Mysql в качестве внутреннего постоянного хранилища. Cadence поддерживает несколько других, по умолчанию Cassandra.

Нам также понадобятся конфигурации Prometheus. Я создам проект под названием Tavern, потому что мы его создаем. Скопируйте нужный файл и папку Prometheus.

Вот как сейчас должна выглядеть структура вашего проекта.

Запуск сервера Cadence

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

docker-compose up

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

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

Вероятно, будет легче понять, если мы начнем использовать домен.

Мы создадим домен с именем tavern с помощью CLI. В команде мы будем использовать --do для указания имени домена. Мы сообщим CLI domain register, чтобы он запускал реестр домена.

cadence --do tavern domain register

Надеюсь, вы получите Domain tavern successfully registered.

Теперь у нас есть Domain для начала работы.

Давайте рассмотрим одну из замечательных особенностей Cadence. Она предоставляет вам пользовательский интерфейс для управления доменами и просмотра рабочих процессов.

Посетите http://localhost:8088/, и вам должен быть представлен пользовательский интерфейс, если у вас запущена функция compose. Попробуйте выполнить поиск по Tavern, и вам будет представлен домен, который вы можете посетить.

Вход в домен пока не будет супер увлекательным, у нас запущено 0 рабочих процессов. Но это подводит нас к следующему компоненту для изучения — Рабочие процессы.

Рабочий процесс — один из основных компонентов, вокруг которого развивается Cadence. Рабочий процесс — это набор задач (действий), выполняемых в определенном порядке. Рабочий процесс поможет вам управлять состоянием процессоров, которые у вас будут запущены. Рабочий процесс имеет множество замечательных встроенных вспомогательных функций, таких как повторные попытки для неудачных событий. Вскоре мы узнаем больше о рабочих процессах, внедрив один из них.

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

Рабочий сервис — Скрам-мастер

Второе, что нам нужно запустить, — это worker service, рабочий сервис, как скрам-мастер. Он будет следить за тем, чтобы задания принимались в рабочий процесс, и распределял их среди рабочих, а затем возвращал ответ на сервер. Рабочая служба также позаботится о том, чтобы любые отправленные задания выполнялись только один раз. Даже если у вас есть несколько сервисных работников, прослушивающих одни и те же задания.

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

До того, как мы запустили сервер Cadence с помощью docker-compose, рабочая служба будет отвечать за подключение к серверу и следить за тем, чтобы задачи обрабатывались в рабочем процессе (наш набор действий, которые следует применять).

Надеюсь, после его реализации станет легче понять. Первое, что мы создадим, — это worker service, который мы будем использовать для регистрации workflow, который будет приветствовать новых клиентов. Рабочий процесс запускает набор функций в порядке, который мы определяем, как только новый клиент входит в таверну.

Я знаю, приветствие новым клиентам может показаться очень похожим на приветствие, мир. Однако давайте проясним, Cadence — это большой инструмент, и чтобы понять его, я думаю, мы должны начать с простого. Давайте построим приветственный мир и проверим, что все это значит, а затем перейдем оттуда в крутую таверну.

Начните с того, что убедитесь, что у вас установлен Cadence SDK в Go и что мы настроили go module. Имя, которое вы даете своему модулю, очень важно, оно используется, когда мы запускаем рабочий процесс.

mkdir app && cd app
go mod init programmingpercy/cadence-tavern
go get go.uber.org/cadence

Мы сохраним простую и базовую настройку проекта, более продвинутые и масштабируемые способы обсудим позже, когда у нас будет понимание инструментов Cadence.

Внутри папки app создайте файл main.go. Это мой текущий макет папки.

Внутри main.go мы пока создадим очень простой клиент Cadence. Это будет worker service, который мы будем запускать. Чтобы создать рабочую службу, нам нужно подключиться к серверу cadence, который мы запускаем, через docker-compose.

Подключение к серверу осуществляется с помощью Yarpc, протокола связи, созданного Uber. Он очень прост в использовании, так что не бойтесь.

Для создания рабочей службы uber предоставляет go.uber.org/cadence/worker SDK. Существует функция для создания нового воркера с именем New, которую мы будем использовать, но для ее работы необходимы некоторые настройки.

  • service= Соединение Yarpc с сервером, мы легко создадим его с помощью SDK.
  • domain = пространство имен домена для работы, ранее мы создали домен tavern, который будем использовать
  • tasklist = строка, используемая для идентификации клиентского рабочего процесса, а также для идентификации рабочих процессов и действий, выполняемых рабочей службой. Думайте о списке задач как о именованной очереди.
  • options= структура, используемая для настройки запущенной службы, ее можно использовать для настройки ведения журнала, показателей и т. д. Сначала мы будем использовать самые основные настройки.

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

Вы можете попробовать запустить рабочую службу сейчас, выполнив основной файл.

go run main.go

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

Рабочие процессы — набор действий, которые нужно выполнять по порядку

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

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

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

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

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

Это мой макет проекта на данный момент.

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

Единственное, что нужно помнить, это то, что функция рабочего процесса должна принимать workflow.Context в качестве первого параметра. Затем он может принимать любые другие входные параметры, если они сериализуемы! В нашем случае мы примем структуры workflow.Context и Customer.

Результатом рабочего процесса должен быть error и любой другой сериализуемый вывод.

Ниже приведено несколько примеров возможных функций рабочего процесса.

В рабочем процессе мы можем добавить конфигурации для запущенного процесса, такие как тайм-ауты, политики повторных попыток, конфигурации Heartbeat. Это добавляется с помощью workflow.ActivityOptions.

Чтобы убедиться, что журналы выводятся, вы должны использовать workflow.Logger или activity.Logger, чтобы они использовались в структуре Cadence. И рабочий процесс, и пакет активности предоставляют регистратору функцию GetLogger.

Внутри рабочего процесса, который мы создаем сейчас, мы скажем «Привет» с помощью Greeting, а затем сохраним информацию о пользователе в функции Update. Чтобы выполнить эти функции, мы заставляем рабочий процесс запускать их с помощью ExecuteActivity, который будет ожидать контекст рабочего процесса в качестве первого входного параметра.

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

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

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

Действия — функции бизнес-логики

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

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

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

Регистрация рабочих процессов и действий

Каждый рабочий процесс и действие отвечают за Registering на сервере Cadence, что они существуют. Это делается с помощью соответствующей функции Register из пакетов workflow и activity.

Обычно это делается в функции init в рабочих процессах и пакетах действий.

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

Чтобы это произошло, мы добавим импорт в main.go, который является нашим рабочим процессом в этом примере.

Выполнение рабочих процессов и действий

Наконец-то мы готовы приступить к выполнению действий и ознакомиться со всей структурой Cadence.

Для справки, вот как выглядит мой нынешний workflows/greetings/greetings.go.

Убедитесь, что у вас запущена программа Cadence docker-compose.

Перейдите в папку /app и запустите рабочую службу.

А почему не печатаются приветствия? Давайте удостоверимся, что у нас есть понимание того, что мы делаем.

Сначала мы запустили docker-compose. Это запустило сервер Cadence, который управляет состоянием всех заданий, которые должны быть выполнены.

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

Итак, у нас есть Скрам-мастер и Разработчик, но нет актуальных проблем, которые нужно решать!

Вот где CLI cadence великолепен, мы можем добавлять новые задания с помощью CLI. Давайте посетим Таверну.

Я не буду описывать все команды внутри инструмента CLI, некоторые из них вы уже должны знать (домен, список задач). В команде cadence вы увидите флаг --tl, который является сокращением от списка задач. Это должно соответствовать списку задач, который вы написали в конфигурации для рабочей службы.

--wt — это вариант типа рабочего процесса. Это будет строка, состоящая из полного пути к рабочему процессу, то есть модуль Go + файл и функция, в которой объявлен рабочий процесс. В моем случае мой модуль programmingpercy/cadence-tavern. Рабочий процесс хранится в папке workflows/greetings, а функция называется workflowGreetings. Это делает полный путь programmingpercy/cadence-tavern/workflows/greetings.workflowGreetings.

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

Запустите эту команду в новом терминале, и вы должны увидеть сообщение журнала, похожее на мою суть.

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

Давайте посмотрим немного больше данных об этом выполнении, вы можете открыть пользовательский интерфейс Cadence, посетив http://localhost:8088/.

Он попросит вас указать домен, введите tavern, и вам должны быть показаны все выполнения, которые выполняются в этом домене.

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

Попробуйте щелкнуть и перейти к работе, чтобы увидеть больше отображаемой информации.

Помните объявление рабочего процесса?

func workflowGreetings(ctx workflow.Context, visitor customer.Customer) (customer.Customer, error) {

Мы указываем, что ввод должен быть Customer, поэтому мы должны вводить данные JSON, подтверждающие объявление структуры Customer.

Мы можем сделать это, используя флаг --i, за которым следует JSON.

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

Не забудьте проверить логи в worker-service, чтобы увидеть, что происходит.

В это время вы можете попробовать одну из утилит оркестровки Cadence. Отключите работающую рабочую службу, чтобы у вас не было процесса, который извлекает задания, передаваемые на сервер каденции. А затем используйте тот же CLI cadence для отправки задания.

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

Сигналы — надежный асинхронный способ предоставления данных

До сих пор мы запускали синхронные действия, которые полагаются друг на друга в рабочем процессе.

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

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

Давайте попробуем это, создав второй рабочий процесс, workflowOrder, который принимает заказ от клиента и обрабатывает заказ, например, обслуживание и прием оплаты.

Я не буду здесь рассказывать о рабочем процессе, в основном, мы переделаем то, что у нас есть для workflowGreetings, но вместо этого создадим долговременный рабочий процесс.

Я создам новую папку с именем orders внутри папки workflows. В этой папке будут храниться все рабочие процессы и действия, связанные с заказами.

Чтобы начать слушать Signals, нам понадобится Selector. Селектор — это компонент, который отвечает за запуск рабочего процесса, в источнике cadence он объясняется как замена обычного оператора select.

Сигналы отправляются на workflow.Channel, и мы можем подписаться на тему, используя workflow.GetSignalChannel(ctx, TOPICNAME).

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

Вы можете добавить получателей, запустив selector.AddRecieve, который принимает workflow.Channel и функцию-обработчик.

Наконец, вы можете заставить Selector начать принимать сигналы, запустив selector.Select(ctx).

Может показаться, что это много, но посмотрите, как мало кода нам нужно.

Прежде чем вы сможете попробовать это, не забудьте добавить импорт в main.go для нового рабочего процесса, иначе он не будет зарегистрирован.

import (
_ "programmingpercy/cadence-tavern/workflows/orders"
......
)

После импорта перезапустите рабочую службу (main.go).

go run main.go

Следующая часть новая, так что читайте внимательно! Чтобы отправить сигнал, нам нужно будет предоставить файл workflow-id. Вы можете найти этот идентификатор при запуске рабочего процесса с помощью интерфейса командной строки cadence. Вы можете использовать ту же команду, что и раньше, когда мы запускали greetingsWorkflow, но поменять workflow_type и удалить ввод. Помните, что сервис отключится по истечении --et времени, мы скоро поговорим об этом подробнее.

В верхней части вывода возьмите идентификатор рабочего процесса.

Наш сервис теперь работает и ждет заказов, давайте отправим сигнал, что мы хотим заказать пиво. Мы снова можем использовать Cadence CLI.

Входим в домен так же, как и раньше, используем команду workflow, и подкоманду signal. Мы устанавливаем workflow-ID с помощью флага -w, а имя сигнала с помощью -n. Помните, что вы должны повторно использовать то же имя сигнала, что и в коде.

Флаг -i используется так же, как и раньше, с предметом, который мы хотим купить, и именем покупателя.

Вы должны увидеть, что сообщение о состоянии печатается с Signal workflow Succeeded. Это означает, что сигнал был отправлен в рабочий процесс, и вы можете просмотреть журналы рабочего процесса, либо открыв терминал, на котором запущена ваша рабочая служба, либо посетив пользовательский интерфейс.

Я рекомендую изучить пользовательский интерфейс, если вы установите таймер --et достаточно высоким, чтобы рабочий процесс не истек, отправить несколько сигналов, а затем просмотреть вкладку history в пользовательском интерфейсе. Это отлично подходит для наблюдения за всеми событиями, происходящими в рабочем потоке.

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

Рабочий процесс работает вечно без тайм-аута

Нет смысла устанавливать флаг --et, чтобы рабочий процесс выполнялся в течение определенного периода времени. В реальной системе вы решите это двумя способами.

Давайте немного обсудим этот вопрос. Вы хотите, чтобы рабочий процесс работал вечно и всегда был готов, но рабочий процесс сохраняет историю и состояния. Это приведет к «утечке» или росту памяти. Рабочий процесс будет расти и расти, и мы не можем этого допустить.

Чтобы решить эту проблему, я бы рекомендовал два решения. Первый, на мой взгляд, лучший, но его сложнее реализовать. Сценарий может заключаться в том, что мы хотим, чтобы workflowOrder работал только в часы работы. Нет смысла запускать его, если Таверна закрыта. Способ реализовать это — отправить событие/сигнал после открытия таверны, которое запускает рабочий процесс заказа.

Второе решение — просто заставить его работать вечно, но с изюминкой. Ребята в uber не шутники, конечно, они это придумали и решили.

Рабочий процесс может быть перезапущен сам по себе, очищая всю историю и состояние. Это делается с помощью workflow.NewContinueAsNewError. Когда эта ошибка возвращается, Cadence фактически ожидает завершения всей текущей работы и перезапускает рабочий процесс с тем же идентификатором. Это также сбросит все таймеры тайм-аута!

Один из способов узнать, когда возвращать эту ошибку, — либо подсчитать количество полученных сигналов, либо использовать таймер, который короче, чем таймер --et.

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

Добавим счетчик сигналов и максимальное количество сигналов, и если мы обработали достаточно сигналов, вернем NewContinueAsNewError.

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

Как только у вас есть это изменение, перезапустите его и попробуйте отправить более 3 сигналов и посмотрите, как все работает гладко.

Отлично, теперь мы можем приветствовать новых клиентов и принимать заказы. В реальном приложении я хотел бы, чтобы события для клиентов уходили, проверка внутри рабочего процесса, что клиент присутствует в таверне перед заказом и т. д.

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

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

Дочерние рабочие процессы

Давайте начнем простой рабочий процесс, который позаботится о порядке, как ребенок.

Запуск дочернего рабочего процесса выполняется так же, как мы запускаем Activity. Пакет рабочего процесса предоставляет функцию ExecuteChildWorkflow.

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

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

Суть показывает небольшую часть workflowOrder, где мы выполняем дочерний рабочий процесс для обработки заказов, после чего мы создадим фактический рабочий процесс.

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

Вот полный код моего orders.go.

Чтобы попробовать это, нам нужно сделать 4 вещи прямо сейчас.

  • Перезапустите рабочую службу
go run main.go
  • Активировать рабочий процесс заказа
cadence --domain tavern workflow run --tl greetings --wt programmingpercy/cadence-tavern/workflows/orders.workflowOrder --et 1000
  • Посетите таверну с пользователем
cadence --domain tavern workflow run --tl greetings --wt programmingpercy/cadence-tavern/workflows/greetings.workflowGreetings --et 20 -i '{"name": "Percy", "age": 22"}'
  • Сделать заказ по сигналу
cadence --domain tavern workflow signal -w YOUR-WORKFLOW-ID -n order -i '{"item": "Beer", "by": "Percy"}'

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

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

API — у пользователей не будет интерфейса командной строки

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

Интерфейс командной строки помог нам протестировать вещи во время разработки, и они также позволяют вам управлять производственной средой.

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

Мы настроим простую конечную точку HTTP, которая позволит нам обернуть оба рабочих процесса, которые у нас есть.

Я создам папку с именем api внутри папки app.

Теперь давайте добавим клиент Cadence, который можно использовать для связи с сервером cadence и заменить то, что мы делаем с помощью CLI.

Для этого нам нужно инициировать yarpc соединение с сервером, а затем создать workflowserviceclient, который является SDK для управления рабочими процессами. Этот workflowserviceclient должен быть введен в cadence.Client, который мы будем использовать для выполнения рабочих процессов.

Весь код, связанный с клиентом cadence, будет помещен в файл с именем client.go. Для этой демонстрации у нас будут жестко заданные значения, но в реальном приложении вы можете сделать это настраиваемым.

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

Далее мы изменим обработчик GreetUser, чтобы он был методом, прикрепленным к CadenceClient. Мы добавим некоторые параметры выполнения и ExecuteWorkflow.

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

Помните, что рабочий процесс ожидал ввода customer.Customer? Поэтому мы должны использовать это в исполнении здесь.

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

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

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

Чтобы запустить рабочий процесс, мы используем StartWorkflow, разница между Start и Execute заключается в том, что Start отправит команду запуска и немедленно вернется. Execute запустит рабочий процесс и дождется его завершения.

Таким образом, StartWorkflow идеально подходит, когда мы хотим запускать длительные рабочие процессы программно, а не через интерфейс командной строки.

Нам нужно предоставить некоторые параметры времени выполнения, такие как тайм-ауты, не забудьте установить ExecutionStartToCloseTimeout достаточно высоким, чтобы вы были уверены, что время ожидания не истечет до того, как ContinueAsNew будет запущен, если вы хотите, чтобы рабочий процесс работал постоянно.

StartWorkflow вернет информацию о выполнении, такую ​​как WorkflowID и RunID, два элемента, которые нам нужно сохранить для отправки Signal.

Время протестировать API! Убедитесь, что ваша рабочая служба запущена, а затем загрузите API. Я использую CURL, чтобы посетить таверну, а затем сделать заказ.

Посетите журналы Cadence или пользовательский интерфейс и убедитесь, что вы видите, что Заказ был сделан.

Метрики — Prometheus и Grafana

Возможно, некоторые из вас уже замечали, что в docker-compose есть и Prometheus, и Grafana. Я не буду рассказывать, что они из себя представляют в этой статье, так как она и так очень длинная, но вкратце, они используются для метрик.

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

Я создам новую папку внутри app с именем prometheus, в ней у нас будет вспомогательная функция для создания нового репортера Prometheus. Репортер Prometheus — это способ предоставления данных метрик.

У нас также будут вспомогательные функции для создания двух областей Tally: одной для рабочих и одной для рабочих служб.

Если вы не знакомы с Tally, это пакет метрик, созданный Uber. Мы будем публиковать только метрики каденса по умолчанию, но вы можете легко добавить свои метрики. Я не буду рассказывать об этом здесь, но вы можете прочитать об этом на их GitHub.

Чтобы начать публиковать метрики, нам нужно изменить Tally Scope как в API main.go, так и в рабочем сервисе.

Рабочий сервис будет выглядеть следующим образом: мы изменим то, что создадим репортер на локальном хосте: 9098, на котором будет размещаться веб-сайт метрик Prometheus.

То же самое касается API. Здесь вместо этого мы используем localhost:9099, чтобы избежать конфликтов портов.

Теперь службы публикуют метрики по этим URL-адресам, вы можете посетить их, чтобы просмотреть данные.

Нам нужно сказать Prometheus очистить эти URL-адреса, что делается в файле prometheus.yml, который использует docker-compose.

Мы добавим localhost:9090, а также два URL-адреса от воркеров, нам нужно использовать host.docker.internal, чтобы это работало.

Перезапустите docker-compose, а затем загрузите API и службу Worker.

Мы начнем с посещения Prometheus, чтобы убедиться, что цели очистки подключаются.

Посетите http://localhost:9090/targets, и вы должны увидеть все цели очистки и их статус, если они не в порядке. Убедитесь, что вы ввели правильные порты и что все службы запущены.

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

Grafana должна находиться на localhost:3000, если вы не меняли файл docker-compose.

В левом боковом меню нам нужно выбрать «Источники данных», чтобы мы могли добавить сервер Prometheus в качестве поставщика данных для Grafana.

Вы попадете на экран со множеством опций, для этого простого примера вам нужно только указать URL-адрес. URL-адрес должен быть http://host.docker.internal:9090.

Внизу вы найдете кнопку с надписью Save & Test.

Когда у нас есть источник данных, нам нужно добавить панели мониторинга JSON.

Импортируйте обе панели мониторинга, и все готово. Теперь у вас есть метрики в Grafana.

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

Конечно, есть только метрики по умолчанию о выполнении, задержках, сбоях и т. д. Вы можете добавить больше, предоставив подсчет ваших метрик.

Заключение

Начало работы с Cadence требует немного усилий, но время потрачено с пользой. У Cadence небольшие накладные расходы, код, который мы произвели, невелик, и что это вам дало? Отказоустойчивые, повторяемые, управляемые событиями задачи. Реализовать это самостоятельно было бы сумасшедшим количеством кода.

Cadence предлагает очень многое, и ребята из Uber действительно доказали свою компетентность.

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

Дополнительное время для его настройки возвращается во многих других целях.

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

Фреймворк также легко встроить в обычный код Go.

В производственной кодовой базе вы должны создать пакеты-оболочки/утилиты для создания клиентов Cadence. У нас есть дублирующийся код для подключения каденции workflowserviceclient внутри службы Worker и API прямо сейчас. Эти служебные пакеты еще больше уменьшат накладные расходы.

Кроме того, вы должны сделать все настраиваемым, а API должен попытаться перечислить рабочие процессы, чтобы убедиться, что они еще не запущены, и т. д.

Для производства обязательно ознакомьтесь с производственной операцией, найденной в их документации.

Спасибо, что прочитали, возможно, это слишком длинная статья, но ее было трудно удержать короткой. Полный код можно найти на GitHub.