Пример использования 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] Двухфазный коммит и микросервисы:
-
Распределенные транзакции и паттерны сага https://dzone.com/articles/distributed-transactions-and-saga-patterns
-
Управление транзакциями в микросервисах https://medium.com/@walkingtreetech/transaction-management-in-microservices-ab09b0cb803b
-
Транзакции через микросервисы REST? https://stackoverflow.com/questions/30213456/transactions-across-rest-microservices
-
Шаблон: Сага https://microservices.io/patterns/data/saga.html
-
Распределенные транзакции: айсберги микросервисов https://www.grahamlea.com/2016/08/distributed-transactions-microservices-icebergs/
-
Руководство по транзакциям через микросервисы https://www.baeldung.com/transactions-across-microservices
-
Шаблоны для распределенных транзакций в архитектуре микросервисов https://developers.redhat.com/blog/2018/10/01/patterns-for-distributed-transactions-within-a-microservices-architecture/
-
Распределенные транзакции в микросервисах https://blog.aspiresys.com/software-product-engineering/producteering/distributed-transactions-in-microservices/
[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)