最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

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

Redis是一個(gè)開(kāi)源的、基于內(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)速度極快,適合于高頻讀寫(xiě)的場(chǎng)景。
  2. 原子性:Redis對(duì)某些命令(如SETNX)提供了原子操作,還可以執(zhí)行l(wèi)ua腳本,所以確保了業(yè)務(wù)的穩(wěn)定性。
  3. 超時(shí)釋放:可以設(shè)置鎖的有效期,即使持有鎖的進(jìn)程崩潰,也能通過(guò)過(guò)期機(jī)制自動(dòng)釋放鎖,避免死鎖問(wèn)題。

二、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ì)寫(xiě)入,就算有N個(gè)并發(fā)同時(shí)在寫(xiě)這個(gè)key,redis也能確保只會(huì)有一個(gè)能寫(xiě)成功。
  • 如何避免死鎖?
    死鎖一般發(fā)生在我們的業(yè)務(wù)代碼拋出異常或者執(zhí)行超時(shí),最終沒(méi)有釋放鎖從而導(dǎo)致產(chǎn)生了死鎖。這種情況我們可以通過(guò)增加一個(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);
}

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

我們可以通過(guò) $redis->eval() 方法執(zhí)行 lua腳本 來(lái)解決這個(gè)問(wèn)題(我們不用關(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 方法使用詳解,官方的文檔和示例寫(xiě)得有點(diǎn)打腦殼,完全沒(méi)寫(xiě)腳本字符串中的 KEYS 和 ARGV 和傳遞參數(shù)的對(duì)應(yīng)關(guān)系。下面寫(xiě)了一個(gè)對(duì)應(yīng)關(guān)系的例子方便大家理解:

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

參數(shù)說(shuō)明:

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

官方文檔eval方法說(shuō)明:

//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 '加鎖成功 開(kāi)始執(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面向?qū)ο笾衧tatic靜態(tài)屬性和靜態(tài)方法的調(diào)用

    php面向?qū)ο笾衧tatic靜態(tài)屬性和靜態(tài)方法的調(diào)用

    這篇文章主要介紹了php面向?qū)ο笾衧tatic靜態(tài)屬性和靜態(tài)方法的調(diào)用,實(shí)例分析了static靜態(tài)屬性和靜態(tài)方法的原理與調(diào)用技巧,需要的朋友可以參考下
    2015-02-02
  • php批量刪除超鏈接的實(shí)現(xiàn)方法

    php批量刪除超鏈接的實(shí)現(xiàn)方法

    有時(shí)候我們會(huì)遇到這種需求,清除掉一段html文本內(nèi)容中的超鏈接,這時(shí)有什么好辦法呢?下面就總結(jié)幾種簡(jiǎn)單的方法清除html文本中的超鏈接,需要的朋友可以參考下
    2015-10-10
  • 談?wù)勑率秩绾螌W(xué)習(xí)PHP網(wǎng)絡(luò)編程

    談?wù)勑率秩绾螌W(xué)習(xí)PHP網(wǎng)絡(luò)編程

    最近用到了php,雖然php的好的都不懂,也只是做一些簡(jiǎn)單的修改和書(shū)寫(xiě)很少的代碼,但感覺(jué)php的功能真的很強(qiáng)
    2008-05-05
  • php.ini?中文版?(PHP7,PHP8)?金步國(guó)

    php.ini?中文版?(PHP7,PHP8)?金步國(guó)

    php.ini?中文版?(PHP7,PHP8),比較適合使用php7、php8的朋友使用,需要的朋友可以參考下
    2023-09-09
  • 8個(gè)PHP數(shù)組面試題

    8個(gè)PHP數(shù)組面試題

    這篇文章主要介紹了8個(gè)PHP數(shù)組面試題,例如寫(xiě)函數(shù)創(chuàng)建長(zhǎng)度為10的數(shù)組,數(shù)組中的元素為遞增的奇數(shù),首項(xiàng)為1、創(chuàng)建長(zhǎng)度為10的數(shù)組,數(shù)組中的數(shù)為遞增的等比數(shù),比值為3,首項(xiàng)為等題目,需要的朋友可以參考下
    2015-06-06
  • php簡(jiǎn)單讀取.vcf格式文件的方法示例

    php簡(jiǎn)單讀取.vcf格式文件的方法示例

    這篇文章主要介紹了php簡(jiǎn)單讀取.vcf格式文件的方法,結(jié)合具體實(shí)例形式分析了php自定義函數(shù)讀取vcf格式文件的具體實(shí)現(xiàn)方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下
    2017-09-09
  • PHP正則驗(yàn)證字符串是否為數(shù)字的兩種方法并附常用正則

    PHP正則驗(yàn)證字符串是否為數(shù)字的兩種方法并附常用正則

    今天小編就為大家分享一篇關(guān)于PHP正則驗(yàn)證字符串是否為數(shù)字的兩種方法并附常用正則,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-02-02
  • php實(shí)現(xiàn)倒計(jì)時(shí)效果

    php實(shí)現(xiàn)倒計(jì)時(shí)效果

    這篇文章主要介紹了php實(shí)現(xiàn)倒計(jì)時(shí)效果,寫(xiě)了一個(gè)考試系統(tǒng)剩余時(shí)間倒計(jì)時(shí)的顯示代碼和大家一起探討,需要的朋友可以參考下
    2015-12-12
  • 理解php原理的opcodes(操作碼)

    理解php原理的opcodes(操作碼)

    現(xiàn)自己對(duì)php的原理基本上都不了解,特別是深層的~呵呵,繼續(xù)努力,特轉(zhuǎn)載喜歡的朋友可以研究下。
    2010-10-10
  • array_values()在php中返回?cái)?shù)組的操作實(shí)例

    array_values()在php中返回?cái)?shù)組的操作實(shí)例

    在本篇文章里小編給大家分享的是一篇關(guān)于array_values()在php中返回?cái)?shù)組的操作實(shí)例內(nèi)容,有興趣的朋友們可以學(xué)習(xí)參考下。
    2021-02-02

最新評(píng)論

葫芦岛市| 桂东县| 朝阳市| 莱州市| 汕头市| 祁连县| 英超| 都匀市| 比如县| 东山县| 洪湖市| 黑河市| 伊金霍洛旗| 鄂托克旗| 西和县| 中牟县| 伊宁市| 三河市| 缙云县| 平和县| 祁东县| 漳州市| 望奎县| 天镇县| 关岭| 崇阳县| 丽水市| 江川县| 九龙县| 辉县市| 鄂尔多斯市| 江油市| 临汾市| 芜湖市| 崇阳县| 曲水县| 于都县| 威远县| 和田市| 八宿县| 吴旗县|