欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于PHP+Redis實(shí)現(xiàn)分布式鎖

 更新時(shí)間:2024年03月06日 10:19:45   作者:歡喜就好啊  
在高并發(fā)、分布式系統(tǒng)環(huán)境下,為了保證資源在同一時(shí)間只能被一個(gè)進(jìn)程訪問(例如數(shù)據(jù)庫(kù)操作、文件讀寫等),分布式鎖是一種常用的解決策略,本文給大家介紹了基于PHP+Redis實(shí)現(xiàn)分布式鎖,需要的朋友可以參考下

一、Redis作為分布式鎖的優(yōu)勢(shì)

Redis是一個(gè)開源的、基于內(nèi)存的鍵值存儲(chǔ)系統(tǒng),它支持多種數(shù)據(jù)結(jié)構(gòu)并具備持久化選項(xiàng)。由于其提供了原子操作(如SETNX、EXPIRE等)和高性能特性,使得Redis成為實(shí)現(xiàn)分布式鎖的理想選擇:

  1. 性能優(yōu)異:Redis是內(nèi)存數(shù)據(jù)庫(kù),響應(yīng)速度極快,適合于高頻讀寫的場(chǎng)景。
  2. 原子性:Redis對(duì)某些命令(如SETNX)提供了原子操作,還可以執(zhí)行l(wèi)ua腳本,所以確保了業(yè)務(wù)的穩(wěn)定性。
  3. 超時(shí)釋放:可以設(shè)置鎖的有效期,即使持有鎖的進(jìn)程崩潰,也能通過過期機(jī)制自動(dòng)釋放鎖,避免死鎖問題。

二、PHP中使用Redis實(shí)現(xiàn)分布式鎖的步驟與原理

前期準(zhǔn)備

在使用分布式鎖時(shí)候我們首先要考慮以下幾點(diǎn):

  • 如何確保鎖的唯一性?
    使用phpredis擴(kuò)展的 setNx('key','value') 或者使用 set('key', 'value', ['nx', 'ex'=>10]) # Will set the key, if it doesn't exist, with a ttl of 10 second 方法,這些方法保證這個(gè)key不存在于redis數(shù)據(jù)庫(kù)時(shí)才會(huì)寫入,就算有N個(gè)并發(fā)同時(shí)在寫這個(gè)key,redis也能確保只會(huì)有一個(gè)能寫成功。
  • 如何避免死鎖?
    死鎖一般發(fā)生在我們的業(yè)務(wù)代碼拋出異?;蛘邎?zhí)行超時(shí),最終沒有釋放鎖從而導(dǎo)致產(chǎn)生了死鎖。這種情況我們可以通過增加一個(gè)鎖的有效期就能避免產(chǎn)生死鎖。例如:
    • 使用redis的expire方法給對(duì)應(yīng)的key設(shè)置一個(gè)有效期 expire(string $key, int $seconds, ?string $mode = NULL): Redis|bool
    • 使用lua腳本 redis.call("expire", KEYS[1], ARGV[2])
  • 如何確保redis命令執(zhí)行的原子性?

要保證原子性必須要求一系列操作要么全部成功執(zhí)行,要么全部不執(zhí)行。舉例:

$redis = new \Redis();
$redis->connect('127.0.0.1',6379);
$result = $redis->setNx('key','val');
if ($result) {
	$redis->expire('key',30);
}

上面的代碼看起來沒有太大的問題,但是 $redis->expire() 一旦執(zhí)行失敗就創(chuàng)建了一個(gè)不過期的值,最終就可能導(dǎo)致產(chǎn)生死鎖,這就是為什么要保證命令執(zhí)行的原子性。

我們可以通過 $redis->eval() 方法執(zhí)行 lua腳本 來解決這個(gè)問題(我們不用關(guān)心實(shí)現(xiàn)細(xì)節(jié),這是底層的實(shí)現(xiàn),只需要知道要保證 redis 命令執(zhí)行的原子性用lua腳本就行)。示例:

$redis = new \Redis();
$redis->connect('127.0.0.1',6379);
$luaScript = <<<LUA
           if redis.call("setnx", KEYS[1], ARGV[1]) == 1 then
               redis.call("expire", KEYS[1], ARGV[2])
               return true
           end
           return false
LUA;

$result = $redis>eval($luaScript,[ $this->lockKey, $this->requestId, $this->expireTime ],1);

eval 方法使用詳解,官方的文檔和示例寫得有點(diǎn)打腦殼,完全沒寫腳本字符串中的 KEYS 和 ARGV 和傳遞參數(shù)的對(duì)應(yīng)關(guān)系。下面寫了一個(gè)對(duì)應(yīng)關(guān)系的例子方便大家理解:

語(yǔ)法:$redis>eval(string $script, ?array $args, ?int num_keys): mixed

參數(shù)說明:

  • string $script 執(zhí)行的lua腳本字符串
  • ?array $args lua腳本字符串中 KEYS 和 ARGV 的對(duì)應(yīng)值,按順序?qū)?yīng)(可選值)
  • ?int num_keys lua腳本字符串中 KEYS 的數(shù)量,寫了幾個(gè) KEYS 就傳幾個(gè)(可選值)

官方文檔eval方法說明:

//index.php
$redis = new \Redis();
$redis->connect('127.0.0.1',6379);
    
$luaScript = <<<LUA
   return {KEYS[1],KEYS[2],KEYS[3],ARGV[1],ARGV[2]};
LUA;
var_dump($redis->eval($luaScript,[1,2,3,4,5],3));

輸出結(jié)果

以下是完整的實(shí)現(xiàn)代碼:

  • RedisDistributedLock.php
<?php 
class RedisDistributedLock {
    private $redis;
    private $lockKey;
    private $requestId;
    private $expireTime;
    /**
     * @param string $lockKey    加鎖的key
     * @param int    $expireTime 鎖的有效期(單位:秒)
     */
    public function __construct(string $lockKey, $expireTime = 30) 
    {
        $redis = new \Redis();
        $redis->connect('127.0.0.1',6379);
        $this->redis      = $redis;
        $this->lockKey    = $lockKey;
        $this->expireTime = $expireTime;
        $this->requestId  = uniqid(); // 生成唯一請(qǐng)求ID
    }
    /**
     * 嘗試獲取鎖,并在指定次數(shù)內(nèi)進(jìn)行重試
     *
     * @param int $maxRetries 最大重試次數(shù),默認(rèn)為3次
     * @param int $retryDelay 兩次重試之間的延遲時(shí)間(單位:毫秒)
     * @return bool 是否成功獲取鎖
     */
    public function acquireLock(int $maxRetries = 3, int $retryDelay = 50): bool
    {
        
        for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
            if ($this->acquireLockOnce()) {
                return true;
            }
            usleep($retryDelay * 1000);
        }
        return false;
    }
    /**
     * 進(jìn)行加鎖
     * @return bool 加鎖是否成功
     */
    private function acquireLockOnce(): bool 
    {
        $luaScript = <<<LUA
            if redis.call("setnx", KEYS[1], ARGV[1]) == 1 then
                redis.call("expire", KEYS[1], ARGV[2])
                return true
            end
            return false
LUA;

        $result = $this->redis->eval(
            $luaScript,
            [ $this->lockKey, $this->requestId, $this->expireTime ],
            1
        );

        return (bool)$result;
    }
    /**
     * 釋放鎖
     * @return bool
     */
    public function releaseLock(): bool
    {
        $luaScript = <<<LUA
        if redis.call("get", KEYS[1]) == ARGV[1] then
            return redis.call("del", KEYS[1])
        else
            return 0
        end
LUA;

        $result = $this->redis->eval(
            $luaScript,
            [ $this->lockKey, $this->requestId ],
            1
        );

        return (bool)$result;
    }
}
?>
  • index.php
<?php
include 'RedisDistributedLock.php';
function task() {
    $lockKey = 'task_1';
    $handler = new RedisDistributedLock($lockKey);
    $startTime = time();
    if ($handler->acquireLock(4)) {
        //@TODO 加鎖成功后執(zhí)行具體的業(yè)務(wù)邏輯
        echo '加鎖成功 開始執(zhí)行加鎖邏輯的時(shí)間:'.date('Y-m-d H:i:s',$startTime);
        echo "\r\n";
        echo '鎖定到:'.date('Y-m-d H:i:s',time() + 15);
        sleep(15);
        $handler->releaseLock();
        echo "\r\n";
        echo '---15s后已釋放鎖---';
    } else {
        echo '加鎖失?。?.date('Y-m-d H:i:s',$startTime);
        return false;
    }
}
task();
?>

執(zhí)行結(jié)果如下:

三、待優(yōu)化的地方

  • 集群環(huán)境下如果主節(jié)點(diǎn)掛掉,如何保證設(shè)置的 key 在子節(jié)點(diǎn)上不會(huì)丟失?
  • 如何處理 key 的自動(dòng)續(xù)期

以上就是基于PHP+Redis實(shí)現(xiàn)分布式鎖的詳細(xì)內(nèi)容,更多關(guān)于PHP Redis分布式鎖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • PHP基于cookie實(shí)現(xiàn)統(tǒng)計(jì)在線人數(shù)功能示例

    PHP基于cookie實(shí)現(xiàn)統(tǒng)計(jì)在線人數(shù)功能示例

    這篇文章主要介紹了PHP基于cookie實(shí)現(xiàn)統(tǒng)計(jì)在線人數(shù)功能,涉及php文件讀寫、cookie訪問、計(jì)算等相關(guān)操作技巧,需要的朋友可以參考下
    2019-01-01
  • php版阿里大于(阿里大魚)短信發(fā)送實(shí)例詳解

    php版阿里大于(阿里大魚)短信發(fā)送實(shí)例詳解

    這篇文章主要介紹了php版阿里大于(阿里大魚)短信發(fā)送實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了阿里大于短信發(fā)送接口的配置與使用技巧,需要的朋友可以參考下
    2016-11-11
  • PHP 文件類型判斷代碼

    PHP 文件類型判斷代碼

    這篇文章主要為大家介紹一下判斷php文件類型的代碼,需要的朋友可以參考一下
    2009-03-03
  • php fsockopen偽造post與get方法的詳解

    php fsockopen偽造post與get方法的詳解

    本篇文章是對(duì)php中fsockopen偽造post與get的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-06-06
  • PHP中斷言函數(shù)的使用詳解

    PHP中斷言函數(shù)的使用詳解

    這篇文章主要介紹了PHP中斷言函數(shù)的使用詳解,幫助大家更好的理解和學(xué)習(xí)使用PHP,感興趣的朋友可以了解下
    2021-04-04
  • PHP中大于2038年時(shí)間戳的問題處理方案

    PHP中大于2038年時(shí)間戳的問題處理方案

    這篇文章主要介紹了PHP中大于2038年時(shí)間戳的問題處理方案,需要的朋友可以參考下
    2015-03-03
  • PHP基于面向?qū)ο蠓庋b的分頁(yè)類示例

    PHP基于面向?qū)ο蠓庋b的分頁(yè)類示例

    這篇文章主要介紹了PHP基于面向?qū)ο蠓庋b的分頁(yè)類,結(jié)合實(shí)例形式分析了php分頁(yè)類針對(duì)頁(yè)碼判斷、顯示等操作的封裝及分頁(yè)類使用相關(guān)操作技巧,需要的朋友可以參考下
    2019-03-03
  • php鏈表用法實(shí)例分析

    php鏈表用法實(shí)例分析

    這篇文章主要介紹了php鏈表用法,實(shí)例分析了php創(chuàng)建鏈表及針對(duì)鏈表節(jié)點(diǎn)的增加、刪除、更新與遍歷等常用操作,需要的朋友可以參考下
    2015-07-07
  • Phar反序列化超詳細(xì)介紹

    Phar反序列化超詳細(xì)介紹

    這篇文章主要為大家介紹了PHP開發(fā)技巧之PHAR反序列化詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • PHP5中使用mysqli的prepare操作數(shù)據(jù)庫(kù)的介紹

    PHP5中使用mysqli的prepare操作數(shù)據(jù)庫(kù)的介紹

    今天小編就為大家分享一篇關(guān)于PHP5中使用mysqli的prepare操作數(shù)據(jù)庫(kù)的介紹,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-03-03

最新評(píng)論