一次併發處理過程, 基於 Redis

lzw123發表於2020-03-01
  1. APP: 牌桌遊戲APP
  2. 業務流程:
    • 每一局牌桌有四個人, 每一局牌桌結束後, 多個APP客戶端同時上傳遊戲中的使用者id資訊;
    • 後端給每個使用者分配稱號, 有多種稱號, 稱號等級越高, 獲得的概率越低.
    • 稱號會存到資料庫, 一定時間段會重置.
    • 後端分配稱號後儲存使用者稱號, 儲存牌局結果, 響應結果給客戶端
  3. 問題:
    • 牌局結束後, 多個客戶端會同時上傳相同的牌局結果, 因為不是完全從資料庫獲取資料(有些使用者是新使用者, 會重新隨機出來並寫入使用者表中, 有些使用者稱號被定時隨機了)
    • 併發狀態下會造成多客戶端獲取的結果不一致, 同時也會寫入多條牌局結果到資料庫中(正確情況下應該是隻有一條), 重複寫入多條使用者資料到使用者表中.

方案1: 由於是多客戶端同時請求, 客戶端要求的響應時間不能太長, 資料庫加鎖的方式太慢, 不採用
方案2: 使用 redis setnx, 保證同一個使用者的稱號在不同的客戶端請求中, 結果一致.

遍歷使用者資料時, 對每一個使用者, 首先生成使用者稱號, 並使用redis setnx設定key, value 為使用者稱號, 保證多客戶端的使用者稱號是相同的.
因為多客戶端上傳資料相同, 還要保證寫資料庫時資料是唯一的(每個使用者只有一條使用者資料, 每一局比賽只有一條資料)
程式碼:

<?php
use Illuminate\Support\Facades\Redis;
class UserTitle 
{
    public function generateTitle($users, $tableId)
    {
        foreach($users as $k=>$v)
        {
            $title = 0; //稱號id
            $user = DB::table('users')->where('user_id', $v)->first();
            if(empty($user))
            {
                $title = $this->getRand(); // 使用者不存在, 隨機獲取一個稱號
            } else {
                $title = $user->title;
            }

            // 使用redis setnx, 給當前使用者id加鎖, 其他客戶端的該使用者可以獲得資料
            $userKey = 'user_title:'.$v; //使用使用者id作為key
            Redis::setNX($userKey, $newTitleId); //
            Redis::expire($userKey, 2); //沒有使用事務, 會導致多個客戶端重複延長該值
            $newTitleId = Redis::get($userKey); //重新從redis獲取key, 防止多個客戶端值不一致的問題
            // 資料庫新增/更新使用者title等操作, 一個使用者id只能有一條使用者資料
        }

        // 整個比賽結果落庫, 只能有一條比賽資料
        $matchKey = 'user_title:'.$tableId; //多個客戶端的table_id是相同的
        $matchLock = Redis::get($dbKey); //獲取值
        if (empty($matchLock))
        {
            Redis::setNX($matchKey, 'lock_match');
            Redis::expire($matchKey, 20);
            // 資料庫操作, 執行 updateOrInsert 方法

        }
    }
}

redis 可以做的東西很多, 設計中肯定還有不完善的地方, 如果您有更好的方案, 請不吝賜教, 謝謝!

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章