国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

PHP 使用 Redis 實現(xiàn)分布式鎖

Coly / 1312人閱讀

摘要:由于執(zhí)行的原子性所以不要在中執(zhí)行過長開銷的程序,否則會驗證影響其它請求的執(zhí)行。同一個腳本生成的簽名都是相同的,所以簽名可以先在本地生成,然后在服務(wù)器上一次腳本,程序中只需保存和使用該簽名即可。同樣的腳本,是始終生成相同的簽名的。

Last-Modified: 2019年6月5日15:59:34

參考鏈接

PHP使用Redis+Lua腳本操作的注意事項

《Redis官方文檔》用Redis構(gòu)建分布式鎖

鎖實現(xiàn)的注意點

互斥: 任意時刻, 只能有一個客戶端獲得鎖

不會死鎖: 客戶端持有鎖期間崩潰, 沒有主動解除鎖, 能保證后續(xù)的其他客戶端獲得鎖

鎖歸屬標(biāo)識: 加鎖和解鎖的必須是同一個客戶端, 客戶端不能解掉非自己持有的鎖(鎖應(yīng)具備標(biāo)識)

如果是Redis集群, 還得考慮具有容錯性: 只要大部分Redis節(jié)點正常運行, 客戶端就可以加鎖和解鎖.

以下只考慮 Redis單機(jī)部署的 場景.

如果是Redis集群部署, 可以使用

加鎖

php 加鎖示例

$redis = new Redis();
$redis->pconnect("127.0.0.1", 6379);
$redis->auth("password");    // 密碼驗證
$redis->select(1);    // 選擇所使用的數(shù)據(jù)庫, 默認(rèn)有16個

$key = "...";
$value = "...";
$expire = 3;

// 參數(shù)解釋 ↓
// $value 加鎖的客戶端請求標(biāo)識, 必須保證在所有獲取鎖清秋的客戶端里保持唯一, 滿足上面的第3個條件: 加鎖/解鎖的是同一客戶端
// "NX" 僅在key不存在時加鎖, 滿足條件1: 互斥型
// "EX" 設(shè)置鎖過期時間, 滿足條件2: 避免死鎖
$redis->set($key, $value, ["NX", "EX" => $expire])

執(zhí)行上面代碼結(jié)果:

$key 對應(yīng)的鎖不存在, 進(jìn)行加鎖操作

$key 對應(yīng)的鎖已存在, 什么也不做

加鎖容易錯誤的點:

使用 setnxexpire 的組合

原因: 若在 setnx 后腳本崩潰會導(dǎo)致死鎖

$value 客戶端標(biāo)識的:

簡單點就用 毫秒級unix時間戳 + 客戶端標(biāo)識(大部分情況下夠用了)

使用其他算法確保生成唯一隨機(jī)值

connect 與 pconnect

在php中, 若使用 pconnect 連接redis, 則在當(dāng)前腳本聲明周期結(jié)束后, 與redis建立的連接仍會保留, 直到對應(yīng)fpm進(jìn)程的生命周期結(jié)束, 同時在下一次請求時, fpm會重用該連接.

即該連接的生命周期是 fpm 進(jìn)程的生命周期, 而非一次php腳本的執(zhí)行.

若代碼使用 pconnect, close 的作用僅是使當(dāng)前php腳本不能再進(jìn)行redis請求, 并沒有真正關(guān)閉與redis的連接, 連接在后續(xù)請求中仍然會被重用.

pconnect函數(shù)在線程版本中不能被使用

上圖中, php-fpm 與redis建立的連接并未隨請求結(jié)束后馬上斷開
解鎖

php解鎖示例: 使用lua腳本

$key = "...";
$identification = "...";
// KEYS 和 ARGV 是lua腳本中的全局變量
$script = <<< EOF
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end
EOF;
# $result = $redis->eval($script, [$key, $identification], 1);
// 返回結(jié)果 >0 表示解鎖成功
// php中參數(shù)的傳遞順序與標(biāo)準(zhǔn)不一樣, 注意區(qū)分
// 第2個參數(shù)表示傳入的 KEYS 和 ARGV, 通過第3個參數(shù)來區(qū)分, KEYS 在前, ARGV 在后
// 第3個參數(shù)表示傳入的 KEYS 的個數(shù)
$result = $redis->evaluate($script, [$key, $identification], 1);    

使用Lua腳本的原因:

避免誤刪其他客戶端加的鎖

eg. 某個客戶端獲取鎖后做其他操作過久導(dǎo)致鎖被自動釋放, 這時候要避免這個客戶端刪除已經(jīng)被其他客戶端獲取的鎖, 這就用到了鎖的標(biāo)識.

lua 腳本中執(zhí)行 getdel 是原子性的, 整個lua腳本會被當(dāng)做一條命令來執(zhí)行

即使 get 后鎖剛好過期, 此時也不會被其他客戶端加鎖

eval命令執(zhí)行Lua代碼的時候,Lua代碼將被當(dāng)成一個命令去執(zhí)行,并且直到eval命令執(zhí)行完成,Redis才會執(zhí)行其他命令。

由于 script 執(zhí)行的原子性, 所以不要在script中執(zhí)行過長開銷的程序,否則會驗證影響其它請求的執(zhí)行。

解鎖容易錯誤的點:

直接 del 刪除鍵

原因: 可能移除掉其他客戶端加的鎖(在自己的鎖已過期情況下)

get判斷鎖歸屬, 若符合再 del

原因: 非原子性操作, 若在 get 后鎖過期了, 此時別的客戶端進(jìn)行加鎖操作, 這里的 del 就會錯誤的將其他客戶端加的鎖解開.

Redis 中使用 Lua 腳本的注意點

↓ 這一段內(nèi)容轉(zhuǎn)載自 https://blog.csdn.net/zhouzme...

注意點:

Redis 會把所有執(zhí)行過的腳本都緩存在內(nèi)存中

Redis 在重啟的時候會釋放掉之前保存的腳本

Lua 腳本中所需要用到的鍵名以及參數(shù)一定要使用 KEYS 和 ARGV 來替換,千萬不要寫死在代碼中,除非你百分百確定每次請求時他們是固定不變的值,特別是涉及到 時間,隨機(jī)數(shù)的,一定要用參數(shù)代入,因為 Redis 每次使用 script 都會校驗?zāi)_本緩存中是否已存在相同腳本,否則就會存儲到緩存中,如果你的腳本很長,且每次請求存在不同的變量值,則會生成無數(shù)多個腳本緩存,你將會發(fā)現(xiàn)Redis占用的內(nèi)存會唰唰唰的往上漲,我一開始因為key 和 參數(shù)太多,分開寫太麻煩了,就圖省事方便,直接把變量拼接到腳本里面,結(jié)果發(fā)現(xiàn)內(nèi)存不停的漲,很是抓狂,找了好久才發(fā)現(xiàn)是這么個原因。

義變量一定要使用局部變量, 即 local var = 1, 局部變量只在所定義的塊(指控制結(jié)構(gòu), 函數(shù)或chunk等)內(nèi)有效, 使用局部變量可以避免命名沖突 并且訪問更快(lua中局部變量和全局變量存儲方式是不一樣的)

如果Lua腳本寫的比較長,非本地或局域網(wǎng)的情況下,建議使用 SHA 簽名的方法來調(diào)用,這樣節(jié)省帶寬,但對性能似乎沒什么直接的提升。這里對小白普及下我理解的原理就是 Redis 會把每個腳本都生成唯一簽名,把腳本作為函數(shù)體,并使用該簽名作為腳本的函數(shù)名放到緩存中,所以后面調(diào)用就只需要傳一個 SHA 簽名就可以調(diào)用該函數(shù)了,精簡很多了。同一個腳本生成的簽名都是相同的,所以SHA簽名可以先在本地生成,然后在服務(wù)器上 script load 一次腳本,程序中只需保存和使用該簽名即可。另外需要注意的是,腳本如果被改動哪怕一個換行或一個空格(這些容易被忽略或誤操作)都必須重新 load 來獲取新的 SHA

注意:獲取 SHA 簽名是多帶帶的功能,不要放在你的正常流程中,當(dāng)本地開發(fā)時就可以生成SHA,把字符串寫死在流程中。同樣的腳本,Reids是始終生成相同的簽名的。

通過 eval 帶入的 ARGV 參數(shù)如果原來是數(shù)字的,會被轉(zhuǎn)換為字符串,如果你的邏輯中需要判斷該變量 > 0 或 < 0 之類的數(shù)字判斷則必須進(jìn)行字符串到數(shù)字的轉(zhuǎn)換,使用 tonumber() 方法 if (tonumber(ARGV[1]) > 0) then return 1; end;

我測試了幾個 lua script 與 PIPELINE 處理對比,發(fā)現(xiàn) script 的效率一般比 PIPELINE 高 30% ~ 40% 左右

Redis集群分布式鎖

Redis 集群相對單機(jī)來說, 需要考慮一個 容錯性, 設(shè)計上更為復(fù)雜

由于這個我也從未實踐過, 先貼一個官方的教程貼壓壓驚

https://github.com/antirez/re...

對應(yīng)的翻譯: http://ifeve.com/redis-lock/

RedLock 算法

官方給出了一個 RedLock 算法

情景: 當(dāng)前有N個完全獨立的Redis master節(jié)點, 分別部署在不同的主機(jī)上

客戶端獲取鎖的操作:

使用相同key和唯一值(作為value)同時向這N個redis節(jié)點請求鎖, 鎖的超時時間應(yīng)該 >> 超時時間(考慮到請求耗時), 若某個節(jié)點阻塞了了應(yīng)盡快跳過

計算步驟1消耗的時間, 若總消耗時間超過超時時間, 則認(rèn)為鎖失敗. 客戶端需在大多數(shù)(超過一半)的節(jié)點上成功獲取鎖, 才認(rèn)為是鎖成功.

如果鎖成功了, 則該鎖有效時間就是 鎖原始有效時間 - 步驟1消耗的時間

如果鎖失敗了(超時或無法獲取超過一半 N/2 + 1 實例的鎖), 客戶端會到每個節(jié)點釋放鎖(是每個, 即使之前認(rèn)為加鎖失敗的節(jié)點)

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/31441.html

相關(guān)文章

  • php+redis實現(xiàn)搶購功能

    摘要:實現(xiàn)思路實現(xiàn)分布式鎖思路思路很簡單,主要用到的函數(shù)是,這個應(yīng)該是實現(xiàn)分布式鎖最主要的函數(shù)。實現(xiàn)任務(wù)隊列這里的實現(xiàn)會用到上面的分布式的鎖機(jī)制,主要是用到了里的有序集合這一數(shù)據(jù)結(jié)構(gòu)。 實現(xiàn)思路 1.Redis實現(xiàn)分布式鎖思路   思路很簡單,主要用到的redis函數(shù)是setnx(),這個應(yīng)該是實現(xiàn)分布式鎖最主要的函數(shù)。首先是將某一任務(wù)標(biāo)識名(這里用Lock:order作為標(biāo)識名的例子)作...

    BetaRabbit 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<