Высокораспределенная архитектура OLTP
Есть ли известное архитектурное решение для высокораспределенных OLTP-ситуаций, где применяются предварительные условия? Например, давайте использовать пример банковского дела. Лицо A хочет перевести $N лицу B. Предварительные условия для этого состоят в том, что лицо A должно иметь более $N на своем счете.
С точки зрения человека А они входят в какое-то веб-приложение. Они создают перевод от себя Человеку B за N $. Имейте в виду, что в фоновом режиме деньги снимаются и зачисляются на счет Лица А в режиме реального времени, когда применяется этот перевод и когда он создается. Деньги могут существовать до создания, но после применения перевода их может не быть. Другими словами, это не может быть проверка на стороне клиента. Человек А хотел бы знать, что эта передача была успешной или не удалась синхронно. Человек А не хотел бы передавать передачу асинхронно, а затем позже возвращаться в очередь или какое-либо уведомление о том, что передача не удалась.
Есть ли известная архитектура, которая решает эту проблему в больших масштабах? Если все учетные записи находятся в одной СУБД, то вы можете сделать что-то подобное с помощью встроенных транзакционных возможностей. Но если вы используете в конечном итоге непротиворечивое хранилище данных в стиле NoSQL или инфраструктуру на основе журналов / сообщений, такую как Kafka, есть ли известное решение таких проблем?
2 ответа
Вы смотрели на Splice Machine? Это СУБД, полностью совместимая с ACID, которая работает поверх стека hadoop (hbase, spark, hdfs, zookeeper). Они имеют двойную архитектуру, которая использует hbase для быстрых OLTP-запросов и искры для OLAP-запросов, и имеет встроенные транзакционные возможности, которые не требуют какой-либо блокировки.
ClustrixDB - еще одно решение, которое стоит попробовать. Он использует Paxos для разрешения распределенных транзакций (встроенный в распределенную, ACID, SQL-совместимую СУБД), а также имеет встроенную отказоустойчивость.
По сути, вам нужен механизм распределенной блокировки. Многие из распределенных серверных приложений предоставляют такую возможность.
В основном, если мы конвертируем ваш вопрос в код, он будет выглядеть так
// BANK WITHDRAWAL APPLICATION
// Fetch BankAccount object from NCache
BankAccount account = cache.Get("Key") as BankAccount; // balance = 30,000
Money withdrawAmount = 15000;
if (account != null && account.IsActive)
{
// Withdraw money and reduce the balance
account.Balance -= withdrawAmount;
// Update cache with new balance = 15,000
cache.Insert("Key", account);
}
=========================
// BANK DEPOSIT APPLICATION
// Fetch BankAccount object from NCache
BankAccount account = cache.Get("Key") as BankAccount; // balance = 30,000
Money depositAmount = 5000;
if (account != null && account.IsActive)
{
// Deposit money and increment the balance
account.Balance += depositAmount;
// Update cache with new balance = 35,000
cache.Insert("Key", account);
}
Это в основном пример состояния гонки
Условие состязания - это когда два или более пользователей пытаются получить доступ к одним и тем же общим данным и изменить их одновременно, но в конечном итоге делают это в неправильном порядке.
Ответ на приведенный выше код в распределенной блокировке будет
LockHandle lockHandle = new LockHandle();
// Specify time span of 10 sec for which the item remains locked
// NCache will auto release the lock after 10 seconds.
TimeSpan lockSpan = new TimeSpan(0, 0, 10);
try
{
// If item fetch is successful, lockHandle object will be populated
// The lockHandle object will be used to unlock the cache item
// acquireLock should be true if you want to acquire to the lock.
// If item does not exists, account will be null
BankAccount account = cache.Get(key, lockSpan,
ref lockHandle, acquireLock) as BankAccount;
// Lock acquired otherwise it will throw LockingException exception
if(account != null && account.IsActive)
{
// Withdraw money or Deposit
account.Balance += withdrawAmount;
// account.Balance -= depositAmount;
// Insert the data in the cache and release the lock simultaneously
// LockHandle initially used to lock the item must be provided
// releaseLock should be true to release the lock, otherwise false
cache.Insert("Key", account, lockHandle, releaseLock);
}
else
{
// Either does not exist or unable to cast
// Explicitly release the lock in case of errors
cache.Unlock("Key", lockHandle);
}
}
catch(LockingException lockException)
{
// Lock couldn't be acquired
// Wait and try again
}
Этот ответ очень специфичен для NCache (распределенный кэш). Я уверен, что вы найдете больше решений под ключевым словом "Распределенная блокировка"