Одновременное чтение / обновление сущностей с помощью Hibernate и Postgres

1

У меня есть приложение, написанное с Java, Spring, Hibernate, Postgres.

Он отслеживает попытки входа пользователя в систему. Если пользователь имеет более 5 недопустимых попыток с одного и того же IP-адреса менее чем за 1 час - этот IP-адрес будет заблокирован.

У меня есть следующий класс сущностей для хранения информации о попытках входа в систему:

@Entity
public class BlockedIp {

private String ipAddress;
private Date blockDate;
private Date unblockDate;
private Date lastAttemptDate;
private Integer wrongAttemptsCount;
...}

Во-первых, когда приложение получает запрос на вход - он проверяет, заблокирован ли IP-адрес (blockDate! = Null). Затем в результате возвращается специальный код ответа пользователю.

Если приложение получает запрос на вход, а учетные данные неверны:

  • если последняя попытка была меньше, чем 1 час назад - она увеличивает значение errAttemptsCount и if (wrongAttemptsCount == 5) - устанавливает blockDate.
  • если последняя попытка была более 1 часа назад - она устанавливает недопустимые значенияAttemptsCount в 1.

Если приложение получает запрос на вход и учетные данные верны - он сбрасывает неправильные значенияAttemptsCount на ноль, поэтому пользователь может сделать до 5 ошибок снова :)

Проблема заключается в том, что некоторые пользователи пытаются войти в систему одновременно с одного и того же IP-адреса. Например, wrongAttemptsCount = 4, поэтому пользователь может иметь только одну последнюю попытку. И у нас есть 3 входящих запроса, и все они имеют неправильные учетные данные. Теоретически, только первый запрос будет проходить, но два других будут заблокированы. На практике, конечно, все ошибаются. Атрибут "Захват" равен 4 от базы данных, поэтому все они будут обрабатываться как незаблокированные.

Итак, какие варианты я должен решить эту проблему и с минимальной потерей производительности, если это возможно? Я думал о инструкции SELECT FOR UPDATE для моего запроса, но мой коллега сказал, что это серьезно повлияет на производительность. Также он предложил посмотреть аннотацию @Version. Это действительно стоит? Это намного быстрее? Может быть, кто-то может предложить другие варианты?

Теги:
hibernate
transactions
concurrency

1 ответ

1
Лучший ответ

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

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

SELECT FOR UPDATE, как и любой пессимистический механизм блокировки, использует явные блокировки. Вы также можете использовать PESSIMISTIC_READ для получения общей блокировки, так как PostgreSQL поддерживает это тоже.

PESSIMISTIC_READ не позволит другим транзакциям получить общую блокировку вашего объекта User, но без оптимистической блокировки вы все равно можете иметь более 5 попыток с ошибкой. После того, как текущая транзакция освободит блокировку, другая конкурирующая транзакция приобретет недавно выпущенную блокировку и в любом случае сохранит новую неудачную логическую попытку. Это происходит для READ_COMMITTED но это предотвращается с помощью REPEATABLE_READ или SERIALIZABLE, но увеличение уровня изоляции транзакций действительно может снизить масштабируемость приложения.

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

  • 0
    подведем итоги. С точки зрения моего вопроса, я должен: 1) использовать аннотацию Version (для этого в моей сущности ввести какое-то специальное поле), 2) перехватить StaleStateException во всех методах, которые сохраняют \ обновить эту сущность, 3) если возникнет такая исключительная ситуация - снова извлечь сущность чтобы получить его новое состояние и повторить все шаги моего алгоритма. Я прав?
  • 0
    Ты прав. Шаг 3 должен выполняться в новой транзакции / сеансе.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню