@Mosanescu

Laravel очередь, как добавить очередь выполнения?

Здравствуйте, я столкнулся с проблемой выполнения запроса, который отправляется одновременно два раза, но при этом изменяются данные у каждого из запросов.

Laravel обрабатывает их мгновенно и одновременно, как с этим быть?

Это ошибка не даёт покоя, поскольку баланс должен обновится при каждом запросе, очень много времени потратил на изучение этой проблемы, запускал и использовал queue от Laravel, но безрезультатно.

Что я могу предоставить, чтобы можно было решить проблему? Буду очень благодарен каждому.

Более подробнее об проблеме: https://pastebin.com/taPmpiuE (pastebin) или:
Из модели User.php:
  public function updateBalance($cash): void
  {
    $this->balance = $cash;
    logger('Запрос отправлен, баланс: ' . $cash);
    $this->save();
    logger('Запрос сохранён, баланс: ' . $this->balance);
  }
 
Из Controller.php:
  public function buy_bet_withdraw(Request $request) {
    $bet_amount = $request->get('bet');
    $user = $request->user();
    $bet_index = 1;
 
    try {
      $lockKey = 'lox_test_lock_' . $user->user_id;
 
      if ($this->redis->setnx($lockKey, 1)) {
        DB::transaction(function () use ($user, $bet_index, $bet_amount, $lockKey) {
          $demo_balance = $user->demo_balance;
 
          BuyLogs::insert([
            'user_id' => $user->user_id,
            'price' => floatval($bet_amount),
          ]);
 
          $this->redis->publish('buy', json_encode([
            'type' => 'cash',
            'price' => 0
          ]));
 
          $old_price = $user->balance;
          $price = $user->balance - $bet_amount;
          $user->updateBalance($price);
 
          logger('Баланс пользователя: ' . $old_price . ', стал: ' . $price . ' | Слот: ' . $bet_index);
        });
 
        $this->redis->del($lockKey);
        return response()->json([
          'balance' => $user->balance,
        ]);
      } else {
        return response()->json(['success' => false, 'message' => 'Ошибка при обработке. Попробуйте позже.'], 400);
      }
    }
    catch (\Throwable $error) {
      \Log::error('Error: ' . $error->getMessage());
      $this->redis->del($lockKey);
      return response()->json(['success' => false, 'message' => 'Неизвестная ошибка'], 400);
    }
  }
 
 
Выполняю запрос:
const { data } = await axios.post('/api/buy', params);
 
Он находится в функции, которая выполняется два раза одновременно, тем самым отправляя два /api/buy, в первый раз баланс изменяется, но уже во второй он не меняется, но при этом возвращая успех об изменении баланса :(
 
Судя по логам:
[2023-12-06 05:57:54] local.DEBUG: Запрос отправлен, баланс: 1427.1  
[2023-12-06 05:57:54] local.DEBUG: Запрос отправлен, баланс: 1427.1  
[2023-12-06 05:57:54] local.DEBUG: Запрос сохранён, баланс: 1427.1  
[2023-12-06 05:57:54] local.DEBUG: Баланс пользователя: 1437.10, стал: 1427.1 | Слот: 0  
[2023-12-06 05:57:54] local.DEBUG: Запрос сохранён, баланс: 1427.1  
[2023-12-06 05:57:54] local.DEBUG: Баланс пользователя: 1437.10, стал: 1427.1 | Слот: 1
 
Но если логически, то правильная последовательно будет такой: (но это мне не светит)
[2023-12-06 05:57:54] local.DEBUG: Запрос отправлен, баланс: 1427.1  
[2023-12-06 05:57:54] local.DEBUG: Запрос сохранён, баланс: 1427.1  
[2023-12-06 05:57:54] local.DEBUG: Баланс пользователя: 1437.10, стал: 1427.1 | Слот: 0  
[2023-12-06 05:57:54] local.DEBUG: Запрос отправлен, баланс: 1427.1  
[2023-12-06 05:57:54] local.DEBUG: Запрос сохранён, баланс: 1427.1  
[2023-12-06 05:57:54] local.DEBUG: Баланс пользователя: 1437.10, стал: 1427.1 | Слот: 1  
 
А так же видно, что в одной секунде как раз обрабатывается запрос, хотелось бы как-то иметь последовательность, я использовал её от Laravel Queue, но без результатно :(
 
Использовать блокировщик от Redis не вариант, поскольку он блокирует запрос, но это мне не нужно, использовал для ознакомления, дабы как-то найти решение проблемы.
 
Вот так вот, надеюсь каждый понял про мою проблему :(
  • Вопрос задан
  • 152 просмотра
Решения вопроса 1
nokimaro
@nokimaro
Меня невозможно остановить, если я смогу начать.
Используйте методы increment и decrement для изменеия баланса
https://laravel.com/docs/10.x/queries#increment-an...

public function updateBalance($bet_amount): void
  {
    $this->decrement('balance', $bet_amount);
  }


под капотом соотв-но получите запрос вида
UPDATE `users` SET `balance` = `balance` - $bet_amount WHERE id = ...


p.s. Ваша проблема не в том что Laravel обрабатывает запросы одновременно, а в том что при одновременных запросах сперва делается SELECT текущего баланса, потом вы вычисляете новый баланс на PHP и делаете просто перезапись значения баланса из-за чего естественно теряется одно из изменений.

p.p.s. Транзакции в БД хорошо, но дополнительно можно использовать lockForUpdate() чтобы исключить любые конфликты на уровне БД и тогда redis-локи в принципе можно убрать.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы