Темы в весенней загрузке службы - PullRequest
0 голосов
/ 23 октября 2018

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

@Service
public class AccountServiceImpl implements AccountService {

static final HashMap<Long, ReentrantLock> locks = new HashMap<Long, ReentrantLock>();

    private ReentrantLock getLock(Long id) {
        synchronized (locks) {
            ReentrantLock lock = locks.get(id);
            if (lock == null) {
                lock = new ReentrantLock();
                locks.put(id, lock);
            }
            return lock;
        }
      }
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public List<Transaction> transferMoney(Long sourceAccountId, Long targetAccountId, Currency currency, BigDecimal amount) {

        Lock lock = getLock(sourceAccountId);
        try {
            lock.lock();
       Balance balance = getBalance(sourceAccountId, currency);
                System.out.println("BALANCE BEFORE TRANSFER " + balance.getBalance()); 
        //createTransactions here using transactionService.create(transactions)
        accountRepository.refresh(accountRepository.findOne(sourceAccountId))  
        balance = getBalance(sourceAccountId, currency);
        System.out.println("BALANCE AFTER TRANSFER " + balance.getBalance()); 
        return transactions;
       }finally{
           lock.unlock()
        }


}

В основном это работает как положено, за исключением случаев, когда я отправляю несколько параллельных запросов, используя apache jmeter.если я отправляю несколько запросов на перевод 100 USD со счета на 1000 балансов, выводит консоль примерно так:

БАЛАНС ДО ПЕРЕДАЧИ 1000БАЛАНС ПОСЛЕ ПЕРЕДАЧИ 900БАЛАНС ДО ПЕРЕДАЧИ 1000БАЛАНС ПОСЛЕ ПЕРЕДАЧИ 900БАЛАНС ДО ПЕРЕДАЧИ 900БАЛАНС ПОСЛЕ ПЕРЕДАЧИ 800БАЛАНС ДО ПЕРЕДАЧИ 800БАЛАНС ПОСЛЕ ПЕРЕДАЧИ 700БАЛАНС ДО ПЕРЕДАЧИ 700БАЛАНС ПОСЛЕ ПЕРЕДАЧИ 600БАЛАНС ДО ПЕРЕДАЧИ 700БАЛАНС ПОСЛЕ ПЕРЕДАЧИ 600

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

Propagation.REQUIRES_NEW

консольный вывод всегда был

БАЛАНС ДО ПЕРЕДАЧИ 1000БАЛАНС ПОСЛЕ ПЕРЕДАЧИ 900

Теперь это иногда работает, иногда нет.Это не соответствует.

Метод Get Balance обновляет учетную запись с помощью:

accountRepository.refresh ()

и TransactionsService.createTransactions также аннотируется с помощью Propagation.REQUIRES_NEW. Так что любой можетскажите, почему это не работает?По крайней мере, направь меня в правильном направлении.

спасибо


РЕДАКТИРОВАТЬ: , если из БД считываются недостаточно четкие данные.используя пружину jpa.

Ответы [ 2 ]

0 голосов
/ 23 октября 2018

Как ответили GPI и прокомментировали другие, избавьтесь от блокировки Java, так как преимущества в долгосрочной перспективе отразят потери.Эта проблема уже решена для spring-data-jpa с помощью аннотации - org.springframework.data.jpa.repository.Lock.

Просто аннотируйте свой метод репозитория, используемый для выбора данных счета (объекта счета) для целей перевода денег, с помощью - @Lock(LockModeType.PESSIMISTIC_WRITE).Это заблокирует выбранные данные до завершения транзакции.Я думаю, тот же репозиторий будет использоваться для извлечения из , а также до учетных записей.

Примените суммы перевода к обеим учетным записям и сохраните вызов в репозитории с помощью метода обслуживания @Transactional.

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

У меня есть класс обслуживания для перевода денег между счетами, где я использую детальную блокировку.Потоки будут блокироваться, если они принадлежат одной учетной записи.

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

0 голосов
/ 23 октября 2018

Скорее всего, проблема в том, что у вас есть гонка между транзакцией БД и блокировкой Java.

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

Сценарий для гонки блокировки БД / Java:

  1. HTTP-запрос попадает на ваш контроллер,
  2. @ Транзакция запускает транзакцию БД
  3. Вы получаете блокировку Java
  4. Вы выполняете операцию, в БД еще ничего не сбрасывается
  5. Вы снимаете блокировку Java, но ваш метод контроллера еще не возвращен, поэтому транзакция JPAне сбрасывается в БД
  6. Приходит другой запрос, открывает транзакцию и «видит мир» таким, какой он есть, то есть ничего не сбрасывается (например, шаг «0»)
  7. Независимо от того, чтопроисходит сейчас, у вас есть две транзакции, одна из которых имеет «неправильное» представление о мире, в соответствии с вашими потребностями.

Теперь представьте, если помимо этого ваша программа имеет несколько экземпляров (например,аварийное переключение, распределение нагрузки), ваши блокировки Java даже не будут работать, какой кошмар: -).

"Простой" способ сделать это (на этом уровне сложности) будет "ВЫБРАТЬ ДЛЯ ОБНОВЛЕНИЯ"ваши сущности, которые предотвратят переплетениеВЫБОРЫ.Вам даже не понадобятся блокировки Java, механизм SQL будет обеспечивать блокировку изначально (второй выбор не будет возвращаться до совершения первой транзакции).

Но вы все равно рискуетео взаимоблокировке (если поступят два запроса, один для учетной записи от А до В и один от В до С, что открывает возможность блокировки B в двух транзакциях, вам придется перехватить и повторить дело, надеясь на конфликтдолжно быть разрешено в этот момент).

Вы можете прочитать по адресу: https://www.baeldung.com/jpa-pessimistic-locking, чтобы увидеть, как вы можете выполнить SELECT FOR UPDATE, что в основном влечет за собой загрузку вашей сущности следующим образом:

entityManager.find(Account.class, accountId, LockModeType.PESSIMISTIC_WRITE);

Другая возможность - отменить блокировку Java и @transactionnal.Таким образом, вы никогда не попадете в БД, пока не перейдете в эксклюзивный режимНо это привело бы к тому, что несколько экземпляров программы в нескольких JVM не могли совместно использовать блокировки.Если это не ваш случай, то это, вероятно, проще.

В любом случае вам все равно придется заблокировать обе стороны (блокировка БД или Java) и учесть взаимоблокировки.

...