在分散式系統中,如何確保多臺機器之間不會產生競爭條件,是一個常見且重要的問題。hyperf-wise-locksmith
庫作為 Hyperf
框架中的一員,提供了一個高效、簡潔的互斥鎖解決方案。
本文將帶你瞭解這個庫的安裝、特性、基本與高階功能,並結合實際應用場景,展示其在專案中的應用。
hyperf-wise-locksmith 庫簡介
hyperf-wise-locksmith
是一個適配 Hyperf 框架的互斥鎖庫,它基於 pudongping/wise-locksmith
庫構建。它可以幫助我們在分散式環境下進行鎖的管理,確保同一時刻只有一個程序能夠操作某些共享資源,從而避免資料的競爭和不一致問題。
安裝
要在你的 Hyperf 專案中使用 hyperf-wise-locksmith
,你需要透過 Composer 進行安裝:
composer require pudongping/hyperf-wise-locksmith -vvv
確保你的環境滿足以下要求:
- PHP >= 8.0
- hyperf ~3.0.0。
特性
hyperf-wise-locksmith
提供了多種鎖機制,包括檔案鎖、分散式鎖、紅鎖和協程級別的互斥鎖。這些鎖機制可以幫助開發者在不同的場景下保護共享資源,避免競態條件。
基本功能
檔案鎖(flock)
檔案鎖是一種簡單的鎖機制,它依賴於檔案系統。以下是一個使用檔案鎖的示例:
private function flock(float $amount)
{
$path = BASE_PATH . '/runtime/alex.lock.cache';
$fileHandler = fopen($path, 'a+');
// fwrite($fileHandler, sprintf("%s - %s \r\n", 'Locked', microtime()));
$res = $this->locker->flock($fileHandler, function () use ($amount) {
return $this->deductBalance($amount);
});
return $res;
}
分散式鎖(redisLock)
分散式鎖適用於分散式系統,它依賴於 Redis。以下是一個使用分散式鎖的示例:
private function redisLock(float $amount)
{
$res = $this->locker->redisLock('redisLock', function () use ($amount) {
return $this->deductBalance($amount);
}, 10);
return $res;
}
高階功能
紅鎖(redLock)
紅鎖是一種更安全的分散式鎖實現,它需要多個 Redis 例項。以下是一個使用紅鎖的示例:
private function redLock(float $amount)
{
$res = $this->locker->redLock('redLock', function () use ($amount) {
return $this->deductBalance($amount);
}, 10);
return $res;
}
協程級別的互斥鎖(channelLock)
協程級別的互斥鎖適用於協程環境,它提供了一種輕量級的鎖機制。以下是一個使用協程鎖的示例:
private function channelLock(float $amount)
{
$res = $this->locker->channelLock('channelLock', function () use ($amount) {
return $this->deductBalance($amount);
});
return $res;
}
實際應用場景
假設我們有一個線上支付系統,需要在多個請求中扣減使用者的餘額。如果不使用互斥鎖,可能會導致超扣或扣減失敗。使用 hyperf-wise-locksmith
庫,我們可以確保每次扣減操作都是原子性的。
程式碼示例
以下是一個扣減使用者餘額的示例,使用了 hyperf-wise-locksmith
庫:
<?php
declare(strict_types=1);
namespace App\Services;
use Hyperf\Contract\StdoutLoggerInterface;
use Pudongping\HyperfWiseLocksmith\Locker;
use Pudongping\WiseLocksmith\Exception\WiseLocksmithException;
use Pudongping\WiseLocksmith\Support\Swoole\SwooleEngine;
use Throwable;
class AccountBalanceService
{
/**
* 使用者賬戶初始餘額
*
* @var float|int
*/
private float|int $balance = 10;
public function __construct(
private StdoutLoggerInterface $logger,
private Locker $locker
) {
$this->locker->setLogger($logger);
}
private function deductBalance(float|int $amount)
{
if ($this->balance >= $amount) {
// 模擬業務處理耗時
usleep(500 * 1000);
$this->balance -= $amount;
}
return $this->balance;
}
/**
* @return float
*/
private function getBalance(): float
{
return $this->balance;
}
public function runLock(int $i, string $type, float $amount)
{
try {
$start = microtime(true);
switch ($type) {
case 'flock':
$this->flock($amount);
break;
case 'redisLock':
$this->redisLock($amount);
break;
case 'redLock':
$this->redLock($amount);
break;
case 'channelLock':
$this->channelLock($amount);
break;
case 'noMutex':
default:
$this->deductBalance($amount);
break;
}
$balance = $this->getBalance();
$id = SwooleEngine::id();
$cost = microtime(true) - $start;
$this->logger->notice('[{type} {cost}] ==> [{i}<=>{id}] ==> 當前使用者的餘額為:{balance}', compact('type', 'i', 'balance', 'id', 'cost'));
return $balance;
} catch (WiseLocksmithException|Throwable $e) {
return sprintf('Err Msg: %s ====> %s', $e, $e->getPrevious());
}
}
}
然後我們再寫一個控制器進行呼叫
<?php
declare(strict_types=1);
namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use App\Services\AccountBalanceService;
use Hyperf\Coroutine\Parallel;
use function \Hyperf\Support\make;
#[AutoController]
class BalanceController extends AbstractController
{
// curl '127.0.0.1:9511/balance/consumer?type=noMutex'
public function consumer()
{
$type = $this->request->input('type', 'noMutex');
$amount = (float)$this->request->input('amount', 1);
$parallel = new Parallel();
$balance = make(AccountBalanceService::class);
// 模擬 20 個併發
for ($i = 1; $i <= 20; $i++) {
$parallel->add(function () use ($balance, $i, $type, $amount) {
return $balance->runLock($i, $type, $amount);
}, $i);
}
$result = $parallel->wait();
return $this->response->json($result);
}
}
當我們訪問 /balance/consumer?type=noMutex
地址時,我們可以看到使用者的餘額會被扣成負數,這明顯不符合邏輯。
然而當我們訪問下面幾個地址時,我們可以看到使用者餘額不會被扣成負數,則說明很好的保護了競態下的共享資源的準確性。
/balance/consumer?type=flock
:檔案鎖/balance/consumer?type=redisLock
:分散式鎖/balance/consumer?type=redLock
:紅鎖/balance/consumer?type=channelLock
:協程級別的互斥鎖
注意
關於使用到 redisLock
和 redLock
時:
- 使用
redisLock
預設採用的config/autoload/redis.php
配置檔案中的第一個key
配置 redis 例項(即 default)。可按需傳入第 4 個引數string|null $redisPoolName
進行重新指定。 - 使用
redLock
預設採用的config/autoload/redis.php
配置檔案中的所有key
對應的配置 redis 例項。可按需傳入第 4 個引數?array $redisPoolNames = null
進行重新指定。
文件
詳細文件可見 pudongping/wise-locksmith。
結語
hyperf-wise-locksmith
庫為 Hyperf 框架的開發者提供了強大的互斥鎖功能,可以幫助我們在高併發場景下保護共享資源。
透過本文的介紹,希望你能對 hyperf-wise-locksmith
有一個全面的瞭解,並在你的專案中靈活運用。如果你覺得這個庫對你有幫助,希望你可以幫忙點個 Star 喲~