2012-02-06 3 views
0

Это было сделано миллион раз. Я уверен, но мой поиск foo сегодня слаб, и я хотел бы получить мнения о том, что обычно считается best способ достижения этой цели.Блокировка записей БД для параллелизма между потоками

Мое приложение отслеживает сеансы для пользователей онлайн в системе. Каждый сеанс соответствует одной записи в базе данных. Сессию можно завершить одним из двух способов. Получается сообщение «stop», или сеанс может быть тайм-аутом. Первый случай прост, он обрабатывается в потоке обработки сообщений, и все в порядке. В последнем случае возникает проблема.

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

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

В настоящее время мое приложение напрямую использует JDBC. Был бы более простой/стандартный метод для решения этой проблемы, если бы я использовал инфраструктуру, такую ​​как Hibernate?

+0

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

+0

Как это будет работать? Я понимаю, что это будет блокироваться с * object * sessionID, а не * значением * sessionID. Поскольку объекты, созданные в двух потоках после чтения БД, не являются тем же самым объектом, это похоже на то, что он никогда не блокирует. Если у вас есть поле для объекта блокировки и обновлено его из двух потоков перед синхронизированным блоком, у вас есть все те же проблемы с параллелизмом, которые у вас были до этого, просто с фокусом, переключенным на объект блокировки. Если есть способ синхронизировать блок по значению *, о котором я не знаю? – tdimmig

+0

Ответ меняется в зависимости от вашей структуры. Если у вас есть mysession.handleMessage (x) и mysession.closeAndCleanup(), вы должны иметь возможность блокировать mysession. Условия гонки состоят в том, что если сообщение получено после того, как сессия помечена как закрытая, но до того, как она попадет в блок синхронизации, сообщение будет обработано, а сеанс будет закрыт или сеанс будет закрыт, и сообщение вернет ошибку о тайм-ауте , Если структура - это handlemessage (ID, msg) и close (ID), где ID - это просто int, вам понадобится Карта или что-то, что можно синхронизировать. – Thomas

ответ

1

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

Классическим решением было бы использовать транзакции (http://dev.mysql.com/doc/refman/5.0/en/commit.html). Это позволяет гарантировать согласованность ваших данных, но долгосрочная транзакция в базе данных превращает ее в огромное узкое место; если ваш код «найти тайм-ауты» выполняется в течение минуты, транзакция может выполняться в течение всего этого периода, что фактически блокирует доступ к записи для затронутой таблицы (таблиц). Большинство систем не справлялись бы с этим.

Мое благоприятное решение для такого рода ситуаций - иметь «состояние машины» для статуса; Мне нравится реализовать это как таблицу истории, но это ведет к быстро растущей базе данных.

Вы определяете состояния сеанса как «инициированные», «запущенные», «тайм-аут-закрытие», «тайм-аут-закрыт» и «остановлен пользователем» (например).

Вы реализуете код, который отличает логику перехода состояния в любой логике доступа к данным.Псевдо-код для «зачистки» сценария может затем быть:

  • обновления всех записи, в которых время окончания < сейчас() и чей статус «работает, устанавливаются статус =„тайм-аут - закрытие“
  • для каждой записи, статус которого «тайм-аут - закрытие»
    • делать все другие вещи, что вам нужно сделать
    • обновления, запись, чтобы установить статус «Истекли время ожидания - закрыто», где статус = «тайм-аут - закрытие "
  • Следующая запись

Все остальные попытки изменить текущее состояние записи сеанса должны убедиться, что текущий статус действителен для попытки изменения.

Например, «ручной» стоп-код должен быть что-то вроде этого:

update sessions 
set status = "stopped by user" 
where session_id = xxxxx 
and status = 'running' 

Если Автозакрытие процедура стартовала в то время между отображением пользовательского интерфейса и кода базы данных, ГДЕ предложение не будет соответствовать никаким записям, поэтому остальная часть кода просто не запускается.

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

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

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

+0

Это добавляет некоторые вызовы DB к рабочей нагрузке, что, очевидно, представляет проблемы с производительностью. Кроме того, если мне не хватает чего-то, у вас все еще есть возможность параллелизма? Если нить 2 читает запись до того, как нить 1 сохранится «тайм-аут-закрытие», так как ничего не существует, чтобы остановить ее, было бы хорошо, что все будет хорошо и продолжит любую обработку.Возможно, это сокращает окно проблем, помещая его в начале обработки, а не ждет до конца, но окно все еще существует. – tdimmig

+0

Я обновил ответ, чтобы отразить ваши проблемы. –

Смежные вопросы