Конфликт строк оракула, вызывающий ошибки взаимоблокировки в приложении JMS с высокой пропускной способностью

Обзор:

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

Подробности:

Мы создаем приложение для обмена сообщениями JMS с высокой пропускной способностью. Производственная среда будет состоять из двух узлов weblogic 11g (на каждом из которых запущено 6 экземпляров прослушивателя MDB). Мы получали ошибки взаимоблокировки Oracle (ORA-00060), когда мы получали около 1000 сообщений, пытающихся обновить одну и ту же строку в базе данных оракула. Синхронизация Java между узлами невозможна в стандартном API потоковой передачи Java (если нет другого решения, мы не хотим использовать какие-либо сторонние решения, такие как терракота и т. д.).

Мы надеялись, что оператор Oracle «выбрать для обновления WAIT n secs» поможет, потому что это, по сути, заставит конкурирующие потоки (для одной и той же строки) ждать несколько секунд, прежде чем первый поток (который первым получил блокировку строки) закончит с этим. .

Первая проблема с «SELECT FOR UPDATE WAIT n» заключается в том, что он не позволяет использовать миллисекунды для времени ожидания. Это начинает отрицательно сказываться на пропускной способности нашего приложения, потому что установка 1 секунды WAIT (наименьшее время ожидания) вызывает задержки сообщений.

Во-вторых, мы возимся с параметром задержки повторной доставки очереди weblogic (30 секунд в нашем случае). Всякий раз, когда поток возвращается из-за ошибки взаимоблокировки, он будет ждать 30 секунд перед повторной попыткой.

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

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

Есть идеи, что нам здесь не хватает? кто-нибудь уже имел дело с подобными проблемами?

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

ДЕТАЛИ РАСЧЕТА: КАЖДОЕ из этих 1000 сообщений создаст 4 объекта 4 разных типов позиций, с каждым из которых связано количество. Эти количества должны быть объединены в эти 4 разных слота (в зависимости от типа позиции). Тупик возникает, когда эти 4 отдельных слота обновляются каждым отдельным потоком. Мы уже заказали эти отдельные обновления в определенном порядке, прежде чем применять их к строкам базы данных, чтобы избежать любых возможных условий гонки.


person robin bajaj    schedule 10.09.2013    source источник
comment
Что ты делаешь? Какой вариант использования: 1000 сообщений обновляются в одну строку?   -  person Beryllium    schedule 11.09.2013
comment
Я добавил более подробную информацию в разделе ДЕТАЛИ ВЫЧИСЛЕНИЯ.   -  person robin bajaj    schedule 11.09.2013


Ответы (2)


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

Сделав шаг назад, кажется маловероятным, что вы действительно захотите, чтобы многие потоки обновляли одну и ту же строку в таблице, когда вы не можете предсказать порядок обновлений. Весьма вероятно, что это приведет к большому количеству потерянных обновлений и довольно непредсказуемому поведению. Что именно делает ваше приложение, чтобы сделать такие вещи разумными? Вы делаете что-то вроде обновления сводных таблиц после вставки строк в подробную таблицу (т. е. обновляете количество просмотров поста в дополнение к регистрации информации о конкретном просмотре)? Если да, то действительно ли эти операции должны быть синхронными? Или вы могли бы периодически обновлять количество просмотров в другом потоке, агрегируя просмотры за последние N секунд?

person Justin Cave    schedule 10.09.2013
comment
Мы не можем периодически обновлять, потому что это приложение в реальном времени и должно обновлять количество для каждого типа позиции для каждого набора записей журнала, которые создаются каждым входящим сообщением. Мы уже упорядочили сообщения по positionId + positionType (который является первичным ключом для таблицы position), и мы все еще застреваем в ситуациях, когда сообщения просто застревают после получения множества ошибок взаимоблокировки оракула и никогда не обрабатываются. - person robin bajaj; 11.09.2013
comment
подсказка о заказе была самой критической для нашей ситуации. Мы изменили наш код, чтобы убедиться, что каждый поток поставляется с предварительно упорядоченным списком сущностей, с которыми они хотят обновить одну и ту же строку. Это помогло нам. Мы все еще получаем взаимоблокировки от оракула, когда потоки сталкиваются (и они делают это довольно часто), но в конечном итоге они обрабатываются. Ранее мы застряли в классической проблеме взаимной блокировки потоков в стиле Java (объяснено здесь документы .oracle.com/javase/tutorial/essential/concurrency/) - person robin bajaj; 28.09.2013

Что касается МДБ.

  1. Пусть он потребляет сообщения и обновляет переменные экземпляра, которые содержат дельту количества обработанных сообщений (MDB может переносить состояние в своих переменных экземпляра для нескольких сообщений).

  2. Метод @Schedule в том же MDB сохраняет количества в одной транзакции базы данных, используя один оператор SQL каждую секунду (например)

update x set q1 = q1 + delta1, q2 = q2 + delta2, ...

Я сделал несколько тестов:

  • Для создания 1000 сообщений требуется 6 секунд (JBoss 7 с использованием HornetQ)
  • За это время уже сохранилось 840 сообщений.
  • Для сохранения оставшихся требуется еще 2 секунды (метод по расписанию запускается каждую секунду)
  • Для этого потребовалось семь команд обновления SQL в семи транзакциях БД.
  • Нагрузка полностью вызвана созданием сообщений; нет реальной нагрузки на БД

Примечания

  • Вам нужен еще один метод @PreDestroy для сохранения ожидающих изменений, чтобы убедиться, что ничего не потеряно.
  • Если вы должны гарантировать правильность транзакций, этот подход не подходит. В этом случае я предлагаю использовать обычный приемник очереди (= без MDB), сессию транзакции и receive(timeout) для сбора 100-10000 сообщений (или до истечения времени ожидания), выполнить одну транзакцию БД и сразу после этого зафиксировать сессию очереди. Это лучше, но это все еще не транзакционный XA. Если вам это нужно, обе фиксации должны быть скоординированы одной транзакцией XA.
person Beryllium    schedule 11.09.2013