2015-04-01 1 views
1

У меня есть приложение, написанное на Java, Spring, Hibernate, Postgres.Параллельные объекты, которые читают/обновляют с помощью 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 час назад - он увеличивает wrongAttemptsCount и если (wrongAttemptsCount == 5) - устанавливает blockDate.
  • , если последняя попытка была более чем 1 час назад - он устанавливает wrongAttemptsCount к 1.

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

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

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

ответ

1

Оптимистичная блокировка обеспечивает наилучшую производительность, а также prevents lost updates in multi-request conversations, что предотвратит одновременное обновление нескольких параллельных транзакций без уведомления о новой версии строки.

Пройдет только одна транзакция, а другие получат исключение из состояния устаревших состояний. Вы должны понимать это locks are acquired even if you don't explicitly request it so. Каждая модифицированная строка занимает блокировку, просто transactional write-behind cache переносит переход состояния объекта в конец текущей транзакции.

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

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

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

+0

позволяет обобщить. Что касается моего вопроса, я должен: 1) использовать аннотацию 'Version' (ввести в нее какое-то специальное поле в моей сущности), 2) catch' StaleStateException' во всех методах, которые сохраняют \ обновлять этот объект, 3), если такое исключение происходит - снова получить объект, чтобы получить его новое состояние и повторить все шаги моего алгоритма. Я прав? – kumade

+0

Вы правы. Шаг 3 должен запускаться в новой транзакции/сеансе. –