Статьи

Микросервисы и распределенные транзакции

Пример использования XTA: API транзакций XA

Аннотация

Протокол двухфазной фиксации был разработан в эпоху систем «большого железа», таких как мейнфреймы и серверы UNIX; спецификация XA была определена в 1991 году, когда типичная модель развертывания состояла из установки всего программного обеспечения на одном сервере. Удивительно, но непротиворечивая часть спецификации может быть повторно использована для поддержки распределенных транзакций в архитектуре на основе микросервисов. В этом посте объясняется, как LIXA и XTA позволяют разрабатывать распределенные транзакционные системы Polyglot.

Вступление

Вкратце, протокол двухфазной фиксации [1] является специализированным консенсусным протоколом, который требует двух фаз. На первом этапе, на этапе голосования , все заинтересованные ресурсы должны быть « подготовлены »: положительный ответ означает, что достигнуто « долговременное » состояние. Второй этап, этап фиксации , подтверждает новое состояние всех соответствующих ресурсов. В случае ошибки протокол откатывается и все ресурсы достигают предыдущего состояния.

Спецификация XA

Одной из наиболее распространенных реализаций протокола двухфазной фиксации является спецификация XA [2]: она поддерживается многими промежуточными программами, разработанными в девяностые годы, и используется в спецификации JTA [3].

Историческое Принятие

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

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

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

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

Протокол двухфазной фиксации и микросервисы

Многие авторы не одобряют использование как стандарта XA, так и протокола двухфазной фиксации в сфере микросервисов; Читатель может найти несколько постов в разделе «Ссылки» [4].

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

Архитектура

LIXA — это менеджер транзакций, который реализует двухфазное принятие и поддерживает спецификацию XA. Это бесплатное программное обеспечение с открытым исходным кодом, лицензированное в соответствии с условиями GNU Public License и Lesser GNU Public License; Его можно бесплатно загрузить с GitHub и SourceForge [5].

XTA означает API транзакций XA и представляет собой интерфейс, разработанный для удовлетворения потребностей современного программирования: он использует основные компоненты, разработанные проектом LIXA, и представляет новую модель программирования (см. Ниже). XTA в настоящее время доступна для C, C ++, Java и Python; он может быть скомпилирован из исходного кода и установлен в системе Linux или может быть выполнен в виде контейнера Docker.

Приведенная выше диаграмма показывает основную картину архитектуры XTA: независимо от языка программирования «прикладная программа» напрямую взаимодействует с XTA для управления транзакциями и с одним или несколькими «менеджерами ресурсов» для управления постоянными данными. Типичными менеджерами ресурсов являются MySQL / MariaDB и PostgreSQL.

Приведенная выше диаграмма показывает архитектуру высокого уровня, реализованную на примере, описанном в этом посте:

  • Rest-client — клиентская программа, разработанная с использованием Python 3; он сохраняет данные в базе данных MySQL.

  • «Rest-server» — это сервис, реализованный на Java, который вызывается с помощью REST API; он сохраняет данные в базе данных PostgreSQL.

  • «Lixad» — это сервер состояний LIXA, он сохраняет состояние транзакций от имени процессов XTA.

Все компоненты выполнены в виде контейнеров Docker [6] для простоты, но может быть выполнена и традиционная установка.

Выполнение примера

Перед началом работы вам понадобится операционная система с двумя основными инструментами: git и Docker. Оба доступны бесплатно и их легко установить в Linux, MacOS и Windows.

Настроить

Прежде всего, клонируйте репозиторий git, содержащий все, что вам нужно:

git clone https://github.com/tiian/lixa-docker.git

Затем перейдите в каталог примеров:

cd lixa-docker/examples/PythonJavaREST

Создайте образы Docker для «rest-client» и «rest-server»:

docker build -f Dockerfile-client -t rest-client .
docker build -f Dockerfile-server -t rest-server .

Проверьте встроенные изображения, вы должны увидеть что-то вроде:

docker images | grep rest
rest-server          latest              81eda2af0fd4        25 hours ago        731MB
rest-client          latest              322a3a26e040        25 hours ago        390MB

Запустите MySQL, PostgreSQL и сервер состояний LIXA (lixad):

docker run --rm -e MYSQL_ROOT_PASSWORD=mysecretpw -p 3306:3306 -d lixa/mysql
docker run --rm -e POSTGRES_PASSWORD=lixa -p 5432:5432 -d lixa/postgres -c 'max_prepared_transactions=10'
docker run --rm -p 2345:2345 -d lixa/lixad

Проверьте запущенные контейнеры, вы должны увидеть что-то вроде:

docker ps | grep lixa
16099992bd82        lixa/lixad          "/home/lixa/lixad-en…"   6 seconds ago       Up 3 seconds        0.0.0.0:2345->2345/tcp              sharp_yalow
15297ed6ebb1        lixa/postgres       "docker-entrypoint.s…"   13 seconds ago      Up 9 seconds        0.0.0.0:5432->5432/tcp              unruffled_brahmagupta
3275a2738237        lixa/mysql          "docker-entrypoint.s…"   21 seconds ago      Up 18 seconds       0.0.0.0:3306->3306/tcp, 33060/tcp   sharp_wilson

Запустите программы

Активируйте службу Java (замените IP-адрес «192.168.123.35» на IP-адрес вашего хоста Docker):

docker run -ti --rm -e MAVEN_OPTS="-Djava.library.path=/opt/lixa/lib" -e LIXA_STATE_SERVERS="tcp://192.168.123.35:2345/default" -e PQSERVER="192.168.123.35" -p 18080:8080 rest-server

Дождитесь готовности службы и проверьте следующие сообщения в консоли:

Mar 24, 2019 9:21:45 PM org.glassfish.grizzly.http.server.NetworkListener start
INFO: Started listener bound to [0.0.0.0:8080]
Mar 24, 2019 9:21:45 PM org.glassfish.grizzly.http.server.HttpServer start
INFO: [HttpServer] Started.
Jersey app started with WADL available at http://0.0.0.0:8080/xta/application.wadl
Hit enter to stop it...

Запустите клиент Python из другого терминала (замените IP-адрес «192.168.123.35» на IP-адрес хоста Docker):

docker run -ti --rm -e SERVER="192.168.123.35" -e LIXA_STATE_SERVERS="tcp://192.168.123.35:2345/default" rest-client

На этом этапе у вас должно быть несколько сообщений в консоли службы Java :

***** REST service called: xid='1279875137.c5b6d8bf7d584065b4ee29d62fe35ab5.ac3d62eb862b49fe6a50bfee46d142b9', oper='delete' *****
2019-03-24 21:23:04.047857 [1/139693866854144] INFO: LXC000I this process is starting a new LIXA transaction manager (lixa package version is 1.7.6)
Created a subordinate branch with XID '1279875137.c5b6d8bf7d584065b4ee29d62fe35ab5.ac3d62eb862b49fe9de52bd41657494a'
PostgreSQL: executing SQL statement >DELETE FROM authors WHERE id=1804<
Executing first phase of commit (prepare)
Returning 'PREPARED' to the client
Executing second phase of commit
***** REST service called: xid='1279875137.91f1712af1164dd38dcc0b14d58819d2.ac3d62eb862b49fe6a50bfee46d142b9', oper='insert' *****
Created a subordinate branch with XID '1279875137.91f1712af1164dd38dcc0b14d58819d2.ac3d62eb862b49fe20cca6a7fc7c4802'
PostgreSQL: executing SQL statement >INSERT INTO authors VALUES(1804, 'Hawthorne', 'Nathaniel')<
Executing first phase of commit (prepare)
Returning 'PREPARED' to the client
Executing second phase of commit

И некоторые другие сообщения в консоли клиента Python :

2019-03-24 21:23:03.909808 [1/140397122036736] INFO: LXC000I this process is starting a new LIXA transaction manager (lixa package version is 1.7.6)
***** REST client *****
MySQL: executing SQL statement >DELETE FROM authors WHERE id=1840<
Calling REST service passing: xid='1279875137.c5b6d8bf7d584065b4ee29d62fe35ab5.ac3d62eb862b49fe6a50bfee46d142b9', oper='delete'
Server replied >PREPARED<
Executing transaction commit
***** REST client *****
MySQL: executing SQL statement >INSERT INTO authors VALUES(1840, 'Zola', 'Emile')<
Calling REST service passing: xid='1279875137.91f1712af1164dd38dcc0b14d58819d2.ac3d62eb862b49fe6a50bfee46d142b9', oper='insert'
Server replied >PREPARED<
Executing transaction commit

Исполнение объяснил

Клиент Python:

  • Строка 1: клиент запускает свой менеджер транзакций.

  • Строка 3: клиент выполняет инструкцию SQL («DELETE») в MySQL.

  • Строка 4: клиент вызывает сервис Java, передавая идентификатор транзакции (xid) и требуемую операцию («удалить»).

Java сервис:

  • Строка 1: служба вызывается, она получает идентификатор транзакции (xid) и требуемую операцию («удалить»).

  • Строка 2: служба запускает свой менеджер транзакций.

  • Строка 3: служба разветвляет глобальную транзакцию и создает новые идентификаторы транзакции.

  • Строка 4: служба выполняет инструкцию SQL («DELETE») в PostgreSQL.

  • Строка 5: служба выполняет первую фазу протокола фиксации, она «готовит» PostgreSQL.

  • Строка 6: сервис возвращает результат «ПОДГОТОВЛЕН» клиенту.

  • Строка 7: служба выполняет второй этап протокола фиксации.

Клиент Python:

  • Строка 5: клиент получает результат «ПОДГОТОВЛЕН» от службы.

  • Строка 6: клиент выполняет протокол фиксации.

Остальные шаги повторяют те же действия для второго оператора SQL («INSERT»).

Модель программирования XTA

Описанный выше пример клиент / сервер реализует один из шаблонов, поддерживаемых XTA: «несколько приложений, параллельные ветви / псевдосинхронные».

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

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

Поле с пурпурным пунктиром содержит вызов REST и первую фазу протокола фиксации: tx.commit() вызывается  true в качестве значения параметра «неблокирование».

Синяя пунктирная рамка содержит вторую фазу протокола фиксации.

На диаграмме не показаны взаимодействия между «rest-client», «rest-server» и сервером состояний LIXA:

  • Волшебство происходит, когда фоновый поток Java-сервера вызывает  tx.commit(false).

  • Сервер состояний LIXA распознает транзакцию с несколькими ветвями и блокирует «rest-server», пока «rest-client» не завершит свою первую фазу фиксации.

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

  • Наконец, оба игрока могут перейти ко второму этапу протокола фиксации.

В случае сбоя либо на стороне клиента, либо на стороне сервера стороны откатываются или автоматически восстанавливаются во второй раз: объяснение этих сценариев оставлено для будущей публикации.

Приведенные ниже фрагменты кода показывают, как должны использоваться объекты и методы XTA.

Клиентский код Python

Игнорируя шаблон и строительные леса, вот интересная часть исходного кода клиента Python:

# initialize XTA environment
Xta_init()

# create a new MySQL connection
# Note: using MySQLdb functions
rm = MySQLdb.connect(host=hostname, user="lixa", password="passw0rd", db="lixa")

# create a new XTA Transaction Manager object
tm = TransactionManager()

# create an XA resource for MySQL
# second parameter "MySQL" is descriptive
# third parameter "localhost,0,lixa,,lixa" identifies the specific database
xar = MysqlXaResource(rm._get_native_connection(), "MySQL", 
      hostname + "/lixa")

# Create a new XA global transaction and retrieve a reference from
# the TransactionManager object
tx = tm.createTransaction()

# Enlist MySQL resource to transaction
tx.enlistResource(xar)

sys.stdout.write("***** REST client *****\n")
# Start a new XA global transaction with multiple branches
tx.start(True)

# Execute DELETE statement
sys.stdout.write("MySQL: executing SQL statement >" + delete_stmt + "<\n")
cur = rm.cursor()
cur.execute(delete_stmt)

# Retrieving xid
xid = tx.getXid().toString()

# Calling server passing xid
sys.stdout.write("Calling REST service passing: xid='" + xid + "', oper='delete'\n")
r = requests.post("http://" + hostname + ":18080/xta/myresource",
     data={'xid':xid, 'oper':'delete'})
sys.stdout.write("Server replied >" + r.text + "<\n")

# Commit the transaction
sys.stdout.write("Executing transaction commit\n")
tx.commit()
  • Строка 14: ресурс XA (xar) связан с подключением MySQL (rm).

  • Строка 22: ресурс XA зачисляется в объект транзакции.

  • Строка 26: глобальная транзакция запущена.

  • Строка 38: клиент вызывает сервис REST.

  • Строка 44: транзакция совершена.

Полный исходный код доступен здесь: https://github.com/tiian/lixa-docker/blob/master/examples/PythonJavaREST/client.py

Код Java-сервера

Игнорируя шаблон и строительные леса, в примере используется среда Jersey, вот интересная часть исходного кода сервера Java:

// 1. create an XA Data Source
xads = new PGXADataSource();
// 2. set connection parameters (one property at a time)
xads.setServerName(System.getenv("PQSERVER"));
xads.setDatabaseName("lixa");
xads.setUser("lixa");
xads.setPassword("passw0rd");
// 3. get an XA Connection from the XA Data Source
xac = xads.getXAConnection();
// 4. get an XA Resource from the XA Connection
xar = xac.getXAResource();
// 5. get an SQL Connection from the XA Connection
conn = xac.getConnection();
//
// XTA code
//
// Create a mew XTA Transaction Manager
tm = new TransactionManager();
// Create a new XA global transaction using the Transaction
// Manager as a factory
tx = tm.createTransaction();
// Enlist PostgreSQL resource to transaction
tx.enlistResource(xar, "PostgreSQL",
System.getenv("PQSERVER") + ";u=lixa;db=lixa");
// create a new branch in the same global transaction
tx.branch(xid);
System.out.println("Created a subordinate branch " +
"with XID '" + tx.getXid().toString() + "'");
//
// Create and Execute a JDBC statement for PostgreSQL
//
System.out.println("PostgreSQL: executing SQL statement >" +
sqlStatement + "<");
// create a Statement object
stmt = conn.createStatement();
// Execute the statement
stmt.executeUpdate(sqlStatement);
// close the statement
stmt.close();
// perform first phase of commit (PREPARE ONLY)
System.out.println("Executing first phase of commit (prepare)");
tx.commit(true);
// start a backgroud thread: control must be returned to the client
// but finalization must go on in parallel
new Thread(new Runnable() {
@Override
public void run() {
try {
    // perform second phase of commit
    System.out.println("Executing second phase of commit");
    tx.commit(false);
    // Close Statement, SQL Connection and XA
    // Connection for PostgreSQL
    stmt.close();
    conn.close();
    xac.close();
} catch  (XtaException e) {
    System.err.println("XtaException: LIXA ReturnCode=" +
e.getReturnCode() + " ('" +
e.getMessage() + "')");
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();

System.out.println("Returning 'PREPARED' to the client");
return "PREPARED";
  • Строка 11: ресурс XA (xar) извлекается соединением PostgreSQL (xac).

  • Строка 23: ресурс XA зачисляется в объект транзакции.

  • Строка 26: создается новая ветвь глобальной транзакции.

  • Строка 42: выполняется первая фаза протокола фиксации («подготовить»).

  • Строка 51: вторая фаза протокола фиксации выполняется фоновым потоком.

  • Строка 69: служба возвращает результат вызывающей стороне.

Полный исходный код доступен здесь: https://github.com/tiian/lixa-docker/blob/master/examples/PythonJavaREST/src/main/java/org/tiian/lixa/xta/examples/MyResource.java

LIXA и XTA Уникальность

LIXA был спроектирован и разработан с четкой и четко определенной архитектурой: вся информация о транзакциях сохраняется сервером состояний ( lixad ), большая часть логики транзакций управляется клиентской библиотекой ( lixac и его производными). Сильное разделение между «логикой» и «состоянием» позволяет встраивать возможности управления транзакциями в «прикладную программу», не требуя ни фреймворков, ни серверов приложений.

XTA продвинула концепцию «распределенной транзакции» на шаг вперед: клиент и сервер не должны выполняться каким-либо «промежуточным программным обеспечением супервизора», потому что они координируют себя автономно, взаимодействуя с сервером состояний LIXA . Единственная информация, которую клиент должен передать серверу, — это строковое представление ASCII XID. Другие подходы, реализованные в прошлом, требовали настройки и координации между серверами приложений; В большинстве случаев даже протоколы связи должны были знать об аспектах транзакций.

Кроме того, XTA поддерживает несколько клиентов / серверов в одной глобальной транзакции: один и тот же XID может быть разветвлен многими вызываемыми сервисами, даже иерархически.

XTA поддерживает синхронные протоколы, такие как протокол RESTful, показанный в этом примере, а также асинхронные протоколы посредством другого шаблона («несколько приложений, параллельные ветви / псевдо асинхронные»).

Выводы

XTA API в сочетании с сервером состояний LIXA позволяет разрабатывать системы, которые реализуют распределенные транзакции ACID [7] между 2 или более приложениями (службами). XTA позволяет разрабатывать транзакционные системы полиглотов, которые используют несколько менеджеров ресурсов и любой протокол связи, не требуя какого-либо менеджера приложений.

Ссылки

[1] Транзакции XA (двухфазная фиксация): простое руководство: https://dzone.com/articles/xa-transactions-2-phase-commit

[2] Распределенная обработка транзакций: спецификация XA, X / Open: https://publications.opengroup.org/c193

[3] API транзакций Java (JTA): https://download.oracle.com/otn-pub/jcp/jta-1_2-mrel2-eval-spec/JTA1.2Specification.pdf

[4] Двухфазный коммит и микросервисы:

[5] Проект LIXA: http://www.tiian.org/lixa/

[6] Образы LIXA Docker доступны в DockerHub: https://hub.docker.com/search?q=lixa&type=image

[7] КИСЛОТА: атомарность, согласованность, изоляция, долговечность: https://en.wikipedia.org/wiki/ACID_(computer_science)