Как разделить транзакции только для чтения и чтения-записи с JPA и Hibernate
У меня довольно тяжелый java-сайт, который обслуживает тысячи запросов/сек, и он использует master Postgresql db, который реплицируется на одну вторичную (только для чтения) базу данных с использованием потоковой (асинхронной) репликации.
Таким образом, я отделяю запрос от первичного до вторичного (только для чтения) с использованием URL-адресов, чтобы избежать вызовов только для чтения, чтобы первичная база данных ошибок считала, что время репликации минимально.
ПРИМЕЧАНИЕ. Я использую один sessionFactory с источником RoutingDataSource, предоставленным весной, который ищет db для использования на основе ключа. Я заинтересован в многопользовательской работе, поскольку я использую hibernate 4.3.4, который ее поддерживает.
У меня есть два вопроса:
- Я не думаю, что разделение на основе URL-адресов является эффективным, поскольку я могу перемещать только 10% трафика, потому что URL-адресов, доступных только для чтения, недостаточно. Какой подход следует рассмотреть?
- Может быть, каким-то образом, на основе URL-адресов я достигаю некоторый уровень распределения среди обоих узлов, но что бы я сделал с моими кварцевыми заданиями (у которых даже есть отдельная JVM)? Какой прагматичный подход я должен принять?
Я знаю, что я не могу получить идеальный ответ здесь, так как это действительно широко, но я просто хочу, чтобы ваше мнение касалось контекста.
Dudes У меня в моей команде:
- Spring4
- Hibernate4
- Quartz2.2
- Java7/Tomcat7
Пожалуйста, проявите интерес. Заранее спасибо.
Ответы
Ответ 1
Вы должны иметь:
DataSource
настроен для подключения к основному узлу
DataSource
, настроенный для подключения к узлу или узлам Подписчика (для них можно использовать циклическое планирование доступа)
- маршрутизация
DataSource
стоит перед этими двумя, как и ваша SessionFactory
.
- Вы можете использовать флаг
@Transactional(readOnly=true)
, чтобы убедиться, что вы перенаправляете транзакции только для чтения на Подписчика DataSource
.
- И Первичный, и Последователь
DataSource
требуют механизма пула соединений, и самым быстрым из них, безусловно, является HikariCP. HikariCP настолько быстр, что при одном моем тесте я получил среднее время получения соединения в 100us.
- Вы должны убедиться, что вы установили правильный размер для ваших пулов соединений, потому что это может иметь огромное значение. Для этого я рекомендую использовать flexy-pool. Вы можете найти больше об этом здесь и здесь.
- Вы должны быть очень прилежными и убедиться, что вы помечаете все транзакции только для чтения соответственно. Это необычно, что только 10% ваших транзакций доступны только для чтения. Может ли быть так, что у вас есть такое приложение для записи или вы используете транзакции записи, когда вы выполняете только операторы запроса?
- Следите за выполнением всех запросов с помощью среды ведения журнала SQL. Чем короче выполнение запроса, тем короче время получения блокировки, тем больше транзакций в секунду будет обрабатываться вашей системой.
Для пакетной обработки вам определенно нужны транзакции с наибольшей частотой записи, но OLTP в целом и Hibernate в частности не подходят для OLAP. Если вы все еще решили использовать Hibernate для своих кварцевых заданий, убедитесь, что вы включили пакетную обработку JDBC, и у вас должны быть установлены следующие свойства Hibernate:
<property name="hibernate.order_updates" value="true"/>
<property name="hibernate.order_inserts" value="true"/>
<property name="hibernate.jdbc.batch_versioned_data" value="true"/>
<property name="hibernate.jdbc.fetch_size" value="25"/>
<property name="hibernate.jdbc.batch_size" value="25"/>
Для пакетной обработки вы можете использовать отдельный источник данных, который использует другой пул соединений (и поскольку вы уже сказали, что у вас другая JVM, чем та, что у вас уже есть). Просто убедитесь, что ваш общий размер соединения всех пулов соединений меньше, чем количество соединений, с которыми был настроен PostgreSQL.
Таким образом, пакетный процессор использует отдельный HikariCPDataSource, который подключается к основному. Каждое пакетное задание должно использовать отдельную транзакцию, поэтому убедитесь, что вы используете разумный размер пакета. Вы хотите удерживать блокировки и завершать транзакции как можно быстрее. Если процессор пакетной обработки использует одновременно работающих работников, убедитесь, что размер соответствующего пула соединений равен числу работников, чтобы они не ждали, пока другие освободят соединения.
Ответ 2
Вы говорите, что ваш URL-адрес приложения составляет всего 10%, а остальные 90% имеют хотя бы некоторую форму записи в базе данных.
10% READ
Вы можете подумать об использовании дизайна CQRS, который может улучшить производительность чтения базы данных. Он, безусловно, может быть прочитан из вторичной базы данных и, возможно, более эффективен, спроектировав запросы и модели домена специально для уровня чтения/просмотра.
Вы не сказали, являются ли 10% -ные запросы дорогими или нет (например, запуск отчетов)
Я бы предпочел использовать отдельный sessionFactory, если вы должны следовать за дизайном CQRS, поскольку загружаемые/кэшируемые объекты, скорее всего, будут отличаться от написанных.
90% НАПИСАТЬ
Что касается остальных 90%, вы не захотите читать из вторичной базы данных (при записи на основной) во время некоторой логики записи, поскольку вам не нужны потенциально устаревшие данные.
Некоторые из этих чтений, вероятно, будут искать "статические" данные. Если кэширование Hibernate не уменьшает количество обращений к базам данных для чтения, я бы рассмотрел кеш памяти, такой как Memcached или Redis для данных такого типа. Этот же кеш может использоваться как 10% -Read, так и 90% -write процессами.
Для чтения, которые не являются статическими (т.е. Чтение данных, которые вы недавно написали), Hibernate должен хранить данные в своем кеше объектов, если их размер соответствует. Можете ли вы определить производительность вашего кеша/промаха?
QUARTZ
Если вы точно знаете, что запланированное задание не повлияет на один и тот же набор данных, как на другое задание, вы можете запускать их для разных баз данных, однако, если у вас есть сомнения, всегда выполняйте пакетные обновления на одном (первичном) сервере и реплицируйте изменения. Лучше быть логически правильным, чем вводить проблемы репликации.
Разделение БД
Если ваши 1000 запросов в секунду записывают много данных, посмотрите на разделение вашей базы данных. Вы можете обнаружить, что у вас когда-либо растут столы. Разделение является одним из способов решения проблемы без архивирования данных.
Иногда вам мало или вообще не нужно менять код приложения.
Архивирование - это, очевидно, еще один вариант
Отказ от ответственности: любой вопрос, подобный этому, всегда будет специфичным для приложения. Всегда старайтесь максимально упростить архитектуру.
Ответ 3
Если я правильно понимаю, 90% HTTP-запросов к вашему webapp содержат хотя бы одну запись и должны работать с основной базой данных. Вы можете направлять транзакции только для чтения в базу данных копий, но это улучшение повлияет только на 10% работы глобальных баз данных, и даже эти операции только для чтения попадут в базу данных.
Общая архитектура здесь заключается в использовании хорошего кэша базы данных (Infinispan или Ehcache). Если вы можете предложить достаточно большой кеш, вы можете надеяться, что значительная часть базы данных читает только попадает в кеш и становится операциями с памятью, либо являющимися частью транзакции только для чтения, либо нет. Настройка кеша - это деликатная операция, но IMHO необходимо для достижения высокой производительности. В этом кеше даже предусмотрены распределенные интерфейсы, даже если в этом случае конфигурация немного сложнее (вам, возможно, придется искать кластеры Terracotta, если вы хотите использовать Ehcache).
В настоящее время репликация базы данных в основном используется для защиты данных и используется в качестве механизма оптимизации параллельности только в том случае, если у вас есть высокие части информационных систем, которые только читают данные, и это не то, что вы описываете.