'
Летов Н.К.
ЭФФЕКТИВНЫЕ МЕТОДЫ ОБЕСПЕЧЕНИЯ СОГЛАСОВАННОСТИ ДАННЫХ В МИКРОСЕРВИСАХ НА БАЗЕ SPRING FRAMEWORK И POSTGRESQL *
Аннотация:
в работе рассматриваются методы оптимизации обработки транзакций в микросервисной архитектуре на платформе Java / Spring Framework и PostgreSQL. Описываются основные проблемы, связанные с конкурентным доступом к данным, такие как конфликты транзакций, мертвые блокировки и накладные расходы. Проведен анализ различных подходов к управлению транзакциями, включая использование уровней изоляции, оптимистических и пессимистических блокировок. На основе полученных данных предложены эффективные методы обеспечения согласованности данных и предотвращения конкурентных ошибок. Результаты исследования могут быть полезны для разработки высоконагруженных микросервисных приложений, требующих устойчивой и производительной работы с данными.
Ключевые слова:
микросервисы, транзакции, согласованность данных, уровни изоляции, оптимистическая блокировка, пессимистическая блокировка, управление конкурентным доступом, высоконагруженные системы, идемпотентность, дедлоки, производительность, масштабируемость
DOI 10.24412/2712-8849-2024-1281-1437-1445
Современные высоконагруженные приложения, построенные на микросервисной архитектуре, требуют высокой согласованности данных при сохранении гибкости, масштабируемости и производительности. Использование микросервисов позволяет разделять сложные системы на отдельные независимые компоненты, которые могут быть разработаны, развернуты и масштабированы отдельно. Однако такое разделение приводит к новым вызовам, особенно в контексте согласованности данных и управления транзакциями [3, с. 54].В микросервисной архитектуре каждое приложение может использовать собственные базы данных, взаимодействовать с другими компонентами через асинхронные сообщения или REST API. Это создает риски для целостности данных, так как взаимодействие между сервисами осуществляется в условиях высокой конкурентности и часто асинхронно. Например, неупорядоченные записи в базу данных и «гонки» между потоками могут приводить к некорректным данным, что особенно критично для финансовых, медицинских и аналитических систем.Одним из широко используемых Java фреймворков для разработки микросервисных приложений является Spring Framework, предоставляющий все инструменты для упрощения работы с БД и управлением состоянием данных [2, с. 225]. Однако недостаточное понимание того как работать с ними может привести к некорректным результатам и снижению эффективности системы.Настоящее исследование посвящено анализу методов обеспечения согласованности данных в микросервисной архитектуре на основе Spring Framework и PostgreSQL, а также выявлению наиболее эффективных подходов для использования в высоконагруженных системах. И направлено на повышение устойчивости и производительности микросервисных приложений, что является критически важным для современных высоконагруженных систем, таких как финансовые, аналитические и корпоративные решения.Для идентификации проблемы написано простейшее приложение, единственной целью которого будет учет “лайков”, поставленных слушателями технической конференции докладчикам. Лайки в свою очередь будут собираться по названию доклада. Код сервиса представлен на рисунке 1.Рис. 1. Код метода рассматриваемого сервиса.Вид таблицы БД, с которой работает сервис представлен на таблице 1.Таблица 1. Представление таблицы в БД до выполнения тестов.Для тестирования работы приложения будем использовать ПО с открытым исходным кодом – Gatling, и написанный тест отправляющий в топик брокера сообщений 2 тысячи сообщений с лайками для доклада “ Spring best practice” для нашего приложения. Чтение сообщений приложением будет происходить в 5 потоков. После завершения тестов можно наблюдать, что не все “лайки” были добавлены, а именно около 57% было потеряно. См. таблица 2.Таблица 2. Представление таблицы в БД после первого теста.Данная проблема возникает по причине отсутвия управления транзакциями в написанном приложении. Написанный сервис вычитывает из БД значение поля likes и добавляет к нему количество лайков пришедшем в запросе, не обращая внимания, на то, что в 4 других потоках (потребление сообщений производится в 5 потоков) возможно выполняется та же самая операция, и происходит перезапись. То есть считывается значение одновременно одно сразу в 5 потоках, но записывается только последнее значение, которое обрабатывалось дольше всего.Решить данную проблему возможно выстроив правильное управление транзакциями в нашем приложении. Транзакция – это группа последовательных операций с базой данных, которая представляет собой логическую единицу работы с данными. Транзакция может быть выполнена либо целиком и успешно, соблюдая целостность данных и независимо от параллельно идущих других транзакций, либо не выполнена вообще и тогда она не должна произвести никакого эффекта.Spring Framework представляет все необходимые инструменты для работы с транзакциями, а также представляется проект Spring Data JPA, который предоставляет все необходимые абстракции для работы с БД. Так же PostgreSQL полностью поддерживает реализацию ACID (Атомарность, Согласованность, Изолированность, Надежность) транзакций, средствами изоляции [6, с. 157]. Однако нельзя полагаться только на фреймворк, всю работу он сам не сделает и ему нужно объяснить, как работать с транзакцией.Один из полезных инструментов в Spring Framework это аннотация @Transactional, однако, если мы установим данную аннотацию над методом работы с БД в написанном приложении, это только усугубит ситуацию, и мы получим большие потери. Дело в том, что существует 4 уровня изоляции транзакции на БД каждый из которых определяет степень допустимого взаимодействия между параллельно выполняющимися транзакциями и балансирует между производительностью и уровнем защиты от конкурентных ошибок:Read Uncommitted – транзакция может считывать данные, которые еще не были зафиксированы другой транзакцией (грязное чтение).Read Committed – транзакция видит только те изменения, которые были зафиксированы другими транзакциями. Грязное чтение предотвращается, но может возникнуть проблема неповторяющегося чтения.Repeatable Read – транзакция гарантирует, что данные, считанные в начале, останутся неизменными до её завершения. Это предотвращает как грязное чтение, так и неповторяющееся чтение.Serializable – самый высокий уровень изоляции, обеспечивающий полную сериализуемость транзакций. Все параллельные транзакции исполняются так, как если бы они выполнялись последовательно. Этот уровень гарантирует максимальную согласованность данных [5, с. 47].Соответственно, чем более высокий уровень изоляции используется, работы больше работы требуется выполнить БД, тем самым ухудшая производительность, но повышая согласованность данных. PostgreSQL не поддерживает Read Uncommitted, а самым низким и значением по умолчанию является Read Committed, а как видно из описания, этот уровень никак не защищает наше приложение от конкурентного чтения и записи.Выставив уровень изоляции в конфигурации нашего приложения на Repeatable Read, мы увидим что потерь практически не осталось, таблица 3.Таблица 3. Представление таблицы в БД после второго теста в Repeatable ReadОднако, потеря данных в размере 4 “лайков” все еще имеет место быть, а если обратиться к журналу ошибок приложения можно увидеть следующее сообщение “ Could not serialize access due to concurrent update” Это означает, что БД не дала нам перезаписать данные, но и сами данные просто потерялись. Исправить данную ошибку возможно используя проект Spring Retry и пометить метод повтора аннотацией @Retry в нашем приложении, повторяя тем самым проблемную операцию записи. После проведения очередного теста можно наблюдать, что все 2000 лайков были установлены в БД.Точно такие же результаты были получены при переключении на уровень изоляции – Serializable, так как БД самостоятельно выстраивает все в очередь все параллельный транзакции.Однако, есть еще один вариант решения, без прибегания к изменению режима изоляции транзакции на БД, а именно использование блокировок. Существует 2 типа блокировок, используемых для управления доступом к данным в многопользовательских системах:Оптимистическая блокировка - предполагает, что конфликтов между транзакциями будет мало, и они редки. Этот подход работает на уровне приложения и основывается на версии данных.Пессимистическая блокировка предполагает, что конфликтов между транзакциями будет много, и они должны быть предотвращены заранее. Данный подход работает на уровне БД и использует синтаксис “ SELECT ... FOR UPDATE” [1. с. 305]Применение подхода с оптимистической блокировкой для решения описанной проблемы явно не подходит, так как конкурентность по умолчанию высокая, но для чистоты эксперимента данный подход так же необходимо проверить. Если мы используем блокировки в приложении, то уровень изоляции можно выставить на минимальный уровень, в случае с PostgreSql это будет Read Committed. Для реализации оптимистической блокировки @Retry метод был оставлен, но поле updated было помечено аннотацией @Version а метод получения записи из БД в JPA репозитории помечается @Lock(LockModeType.OPTIMISTIC). Теперь приложение при попытке записать новое значение в БД будет проверять не отличается ли поле updated в текущей момент от значения на момент чтения записи, и в случае различий будет выбрасывать исключение, которое вызовет повтор записи в блоке @Retry. После запуска теста мы снова получили успешный результат в виде 2000 лайков в БД.Для применения пессимистичной блокировки @Retry блок можно удалить, также @Version больше не требуется. А метод извлечения данных из БД аннотируется @Lock(LockModeType.PESSIMISTIC_WRITE). После запуска последнего теста мы видим, что БД снова находится в согласованном состоянии таблица 4.Таблица 4. Представление таблицы в БД после второго теста в Repeatable ReadКаждый из 4-ех описанных способов разрешил поставленную проблему, что было экспериментально подтверждено, однако необходимо оценить производительность каждого подхода. На рисунке 2, на графике отражена зависимость количества необработанных сообщений в очереди от времени для четырех последовательных тестов.Рис. 2. Скорость разбора сообщений из очереди в 5 потоков.Слева на право:Режим изоляции – Repeatable Read + RetryРежим изоляции – Serializable + RetryРежим изоляции – Read Committed + Пессимичтиная блокировкаРежим изоляции – Read Committed + Оптимистичная блокировкаИсследование показывает, что в высоконагруженной среде, где может имеет место быть в большом колличестве параллельный доступ к данным, пессимичтическая блокировка показывает производительность (скорость обработки данных) а так же сокращает объем кода, который необходимо написать. Связано это с тем, что не происходит дополнительных вызовов логики повторения записи в БД после выброса исключений. Однако, в средах где конкурентность не такая высокая, оптимистическая блокировка или просто смена уровня изоляции может исправить проблему без большего влияния на производительность.
Номер журнала Вестник науки №12 (81) том 3
Ссылка для цитирования:
Летов Н.К. ЭФФЕКТИВНЫЕ МЕТОДЫ ОБЕСПЕЧЕНИЯ СОГЛАСОВАННОСТИ ДАННЫХ В МИКРОСЕРВИСАХ НА БАЗЕ SPRING FRAMEWORK И POSTGRESQL // Вестник науки №12 (81) том 3. С. 1437 - 1445. 2024 г. ISSN 2712-8849 // Электронный ресурс: https://www.вестник-науки.рф/article/19903 (дата обращения: 23.06.2025 г.)
Вестник науки © 2024. 16+
*