Я пишу биржу для онлайн-игры. Как убедиться, что заказы выполняются изолированно, используя Django и Postgres? - PullRequest
0 голосов
/ 12 апреля 2020

Я пишу онлайн-игру, используя Django, и я планирую использовать Postgres в качестве движка базы данных.

Одной из особенностей игры будет симуляция акций. биржевой и товарный рынок. Если пользователь A говорит «Я хочу купить яблоко за 50 долларов», а пользователь B говорит «Я хочу продать яблоко за 50 долларов», то эти два заказа должны быть сопоставлены друг с другом, в результате чего пользователь B должен продать яблоко пользователю A .

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

Я еще не написал никакого реального кода для этой функции , но давайте предположим, что классы моделей выглядят так:

class Account(models.Model):
    number_of_dollars = models.BigIntegerField(validators=[MinValueValidator(0)], default=0)
    number_of_apples = models.BigIntegerField(validators=[MinValueValidator(0)], default=0)

class AppleOrder(models.Model):
    account = models.ForeignKey(Account, on_delete=models.CASCADE)
    price = models.IntegerField(validators=[MinValueValidator(0)], default=0)
    direction = models.IntegerField(choices=[(1, 'buy'), (2, 'sell')])
    status = models.IntegerField(choices=[(1, 'open'), (2, 'canceled'), (3, 'filled')])

Некоторые веб-запросы могут привести к тому, что игра выполнит поиск заказов, соответствующих друг другу, и выполнит их. Но как я могу убедиться, что заказы выполняются правильно?

Наивно, я хотел бы сделать это так:

def execute_orders(buy_order, sell_order, price)
    # We assume that "buy_order" and "sell_order" are matching orders, "price"
    # is the correct price to execute the order at, the buyer has sufficient
    # money, and the seller has an apple.

    buyer = buy_order.account
    seller = sell_order.account

    with transaction.atomic():
        buyer.number_of_dollars = F('number_of_dollars') - price
        seller.number_of_dollars = F('number_of_dollars') + price

        buyer.number_of_apples = F('number_of_apples') + 1
        seller.number_of_apples = F('number_of_apples') - 1

        buy_order.status = 3
        sell_order.status = 3

        buyer.save()
        seller.save()
        buy_order.save()
        sell_order.save()

Ах, но есть проблема! Что если два потока вызывают эту функцию одновременно, для одной и той же пары заказов, и оба потока выполняют одни и те же шаги в одно и то же время? Заказы будут выполнены дважды!

Как я могу предотвратить это?

Одна из моих идей заключалась в том, чтобы при смене статусов заказов на «заполненные» они менялись. используя оператор, такой как UPDATE AppleOrders SET status = 3 WHERE id = $1 AND status = 1, и проверить количество затронутых строк, чтобы убедиться, что заказ действительно имел статус 1 непосредственно перед тем, как я обновил его. Однако я слышал, что оператор, выполняющийся в транзакции, может видеть старые данные, поэтому, возможно, мой код подумает , что он устанавливает статус на 3, когда на самом деле статус уже был установлен на 3 другой веткой. И, кроме того, я не знаю, как выполнить sh с помощью Django.

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

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

Какой хороший способ гарантировать, что заказы выполняются атомарно c, последовательным и изолированным способом

...