分散式鎖在WEB程式設計中的應用相當廣泛
以介面冪等性為例,未實現介面冪等性的註冊介面,是這樣的:
<?php
public function register(Request $request)
{
//一些驗證邏輯
$user = new User();
$user->username = $request->post('phone');
$user->password = Hash::make($request->post('password'));
$user->save();
}
這樣寫有什麼問題?明顯,如果同一時間以相同的請求資料請求該介面,可能會導致資料庫有兩條同樣的記錄。誠然,我們可以為Users
表的username
欄位設定唯一索引,再catch
異常來丟擲友好錯誤提示。但,這裡我想介紹另一種方法,用分散式鎖,以username
作為分散式鎖key
的構建。
public function register(Request $request)
{
//一些驗證邏輯
$phone = $request->post('phone');
$lock = Cache::lock('register-user-' . $phone, 10);
if ($lock->get()) {
$user = new User();
$user->username = $phone;
$user->password = Hash::make($request->post('password'));
$user->save();
$lock->release();
} else {
//返回友好的錯誤資訊
}
}
同樣的,也可以編寫一個限制訪問頻次的中介軟體,以客戶端ip、路由和引數生成唯一指紋。
class AccessLimit
{
public function handle($request, Closure $next)
{
$fingerprint = md5($request->ip() . $request->route()->getName() . var_export($request->all(), true));
$lock = Cache::lock($fingerprint, 5);
if ($lock->get()) {
return $next($request);
}
throw new \Exception('訪問頻繁');
}
}
還有一些非電商場景下的秒殺交易,比如說虛擬幣,掛單交易只允許被一個使用者購買。
如果不處理併發問題,就會發生超賣的情況。
<?php
public function deal(Request $request)
{
$dealId = $request->post('id');
$deal = Deal::find($dealId);
//一些驗證操作
if (is_null($deal->sell_time)) { //狀態判斷邏輯
DB::beginTransaction();
try {
//處理業務邏輯
$deal->sell_time = date('Y-m-d H:i:s');
$deal->save();
DB::commit();
} catch (\Exception $e) {
DB::rollback();
}
return; //返回交易成功響應
}
throw new \Exception('交易失敗響應');
}
這樣寫在高併發場景下有明顯的問題,兩個使用者同時搶單時,狀態判斷邏輯同時成功,並執行到接下來的業務邏輯。怎麼改進?用分散式鎖。
<?php
public function deal(Request $request)
{
$dealId = $request->post('id');
$lock = Cache::lock('deal-' . $dealId, 10);
if (!$lock->get()) {
throw new \Exception('該掛單已交易完成');
}
$deal = Deal::find($dealId);
//一些驗證操作
DB::beginTransaction();
try {
//處理業務邏輯
if (!is_null($deal->sell_time)) { //狀態判斷邏輯
throw new \Exception('該掛單已被交易');
}
$deal->sell_time = date('Y-m-d H:i:s');
$deal->save();
DB::commit();
//返回成功響應
} catch (\Exception $e) {
DB::rollback();
//返回失敗響應
} finally {
$lock->release();
}
}
這樣處理,即可以避免超賣,也可以避免請求頻繁落到DB層,提升整體的系統吞吐量。
本作品採用《CC 協議》,轉載必須註明作者和本文連結