摘要:由于執(zhí)行的原子性所以不要在中執(zhí)行過(guò)長(zhǎng)開(kāi)銷(xiāo)的程序,否則會(huì)驗(yàn)證影響其它請(qǐng)求的執(zhí)行。同一個(gè)腳本生成的簽名都是相同的,所以簽名可以先在本地生成,然后在服務(wù)器上一次腳本,程序中只需保存和使用該簽名即可。同樣的腳本,是始終生成相同的簽名的。
Last-Modified: 2019年6月5日15:59:34
參考鏈接PHP使用Redis+Lua腳本操作的注意事項(xiàng)
《Redis官方文檔》用Redis構(gòu)建分布式鎖
鎖實(shí)現(xiàn)的注意點(diǎn)互斥: 任意時(shí)刻, 只能有一個(gè)客戶(hù)端獲得鎖
不會(huì)死鎖: 客戶(hù)端持有鎖期間崩潰, 沒(méi)有主動(dòng)解除鎖, 能保證后續(xù)的其他客戶(hù)端獲得鎖
鎖歸屬標(biāo)識(shí): 加鎖和解鎖的必須是同一個(gè)客戶(hù)端, 客戶(hù)端不能解掉非自己持有的鎖(鎖應(yīng)具備標(biāo)識(shí))
如果是Redis集群, 還得考慮具有容錯(cuò)性: 只要大部分Redis節(jié)點(diǎn)正常運(yùn)行, 客戶(hù)端就可以加鎖和解鎖.
以下只考慮 Redis單機(jī)部署的 場(chǎng)景.
如果是Redis集群部署, 可以使用
加鎖php 加鎖示例
$redis = new Redis(); $redis->pconnect("127.0.0.1", 6379); $redis->auth("password"); // 密碼驗(yàn)證 $redis->select(1); // 選擇所使用的數(shù)據(jù)庫(kù), 默認(rèn)有16個(gè) $key = "..."; $value = "..."; $expire = 3; // 參數(shù)解釋 ↓ // $value 加鎖的客戶(hù)端請(qǐng)求標(biāo)識(shí), 必須保證在所有獲取鎖清秋的客戶(hù)端里保持唯一, 滿(mǎn)足上面的第3個(gè)條件: 加鎖/解鎖的是同一客戶(hù)端 // "NX" 僅在key不存在時(shí)加鎖, 滿(mǎn)足條件1: 互斥型 // "EX" 設(shè)置鎖過(guò)期時(shí)間, 滿(mǎn)足條件2: 避免死鎖 $redis->set($key, $value, ["NX", "EX" => $expire])
執(zhí)行上面代碼結(jié)果:
$key 對(duì)應(yīng)的鎖不存在, 進(jìn)行加鎖操作
$key 對(duì)應(yīng)的鎖已存在, 什么也不做
加鎖容易錯(cuò)誤的點(diǎn):
使用 setnx 和 expire 的組合
原因: 若在 setnx 后腳本崩潰會(huì)導(dǎo)致死鎖
$value 客戶(hù)端標(biāo)識(shí)的:
簡(jiǎn)單點(diǎn)就用 毫秒級(jí)unix時(shí)間戳 + 客戶(hù)端標(biāo)識(shí)(大部分情況下夠用了)
使用其他算法確保生成唯一隨機(jī)值
connect 與 pconnect在php中, 若使用 pconnect 連接redis, 則在當(dāng)前腳本聲明周期結(jié)束后, 與redis建立的連接仍會(huì)保留, 直到對(duì)應(yīng)fpm進(jìn)程的生命周期結(jié)束, 同時(shí)在下一次請(qǐng)求時(shí), fpm會(huì)重用該連接.
即該連接的生命周期是 fpm 進(jìn)程的生命周期, 而非一次php腳本的執(zhí)行.
若代碼使用 pconnect, close 的作用僅是使當(dāng)前php腳本不能再進(jìn)行redis請(qǐng)求, 并沒(méi)有真正關(guān)閉與redis的連接, 連接在后續(xù)請(qǐng)求中仍然會(huì)被重用.
pconnect函數(shù)在線程版本中不能被使用
上圖中, php-fpm 與redis建立的連接并未隨請(qǐng)求結(jié)束后馬上斷開(kāi)解鎖
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個(gè)參數(shù)表示傳入的 KEYS 和 ARGV, 通過(guò)第3個(gè)參數(shù)來(lái)區(qū)分, KEYS 在前, ARGV 在后 // 第3個(gè)參數(shù)表示傳入的 KEYS 的個(gè)數(shù) $result = $redis->evaluate($script, [$key, $identification], 1);
使用Lua腳本的原因:
避免誤刪其他客戶(hù)端加的鎖
eg. 某個(gè)客戶(hù)端獲取鎖后做其他操作過(guò)久導(dǎo)致鎖被自動(dòng)釋放, 這時(shí)候要避免這個(gè)客戶(hù)端刪除已經(jīng)被其他客戶(hù)端獲取的鎖, 這就用到了鎖的標(biāo)識(shí).
lua 腳本中執(zhí)行 get 和 del 是原子性的, 整個(gè)lua腳本會(huì)被當(dāng)做一條命令來(lái)執(zhí)行
即使 get 后鎖剛好過(guò)期, 此時(shí)也不會(huì)被其他客戶(hù)端加鎖
eval命令執(zhí)行Lua代碼的時(shí)候,Lua代碼將被當(dāng)成一個(gè)命令去執(zhí)行,并且直到eval命令執(zhí)行完成,Redis才會(huì)執(zhí)行其他命令。由于 script 執(zhí)行的原子性, 所以不要在script中執(zhí)行過(guò)長(zhǎng)開(kāi)銷(xiāo)的程序,否則會(huì)驗(yàn)證影響其它請(qǐng)求的執(zhí)行。
解鎖容易錯(cuò)誤的點(diǎn):
直接 del 刪除鍵
原因: 可能移除掉其他客戶(hù)端加的鎖(在自己的鎖已過(guò)期情況下)
get判斷鎖歸屬, 若符合再 del
原因: 非原子性操作, 若在 get 后鎖過(guò)期了, 此時(shí)別的客戶(hù)端進(jìn)行加鎖操作, 這里的 del 就會(huì)錯(cuò)誤的將其他客戶(hù)端加的鎖解開(kāi).
Redis 中使用 Lua 腳本的注意點(diǎn)↓ 這一段內(nèi)容轉(zhuǎn)載自 https://blog.csdn.net/zhouzme...
注意點(diǎn):
Redis 會(huì)把所有執(zhí)行過(guò)的腳本都緩存在內(nèi)存中
Redis 在重啟的時(shí)候會(huì)釋放掉之前保存的腳本
Lua 腳本中所需要用到的鍵名以及參數(shù)一定要使用 KEYS 和 ARGV 來(lái)替換,千萬(wàn)不要寫(xiě)死在代碼中,除非你百分百確定每次請(qǐng)求時(shí)他們是固定不變的值,特別是涉及到 時(shí)間,隨機(jī)數(shù)的,一定要用參數(shù)代入,因?yàn)?Redis 每次使用 script 都會(huì)校驗(yàn)?zāi)_本緩存中是否已存在相同腳本,否則就會(huì)存儲(chǔ)到緩存中,如果你的腳本很長(zhǎng),且每次請(qǐng)求存在不同的變量值,則會(huì)生成無(wú)數(shù)多個(gè)腳本緩存,你將會(huì)發(fā)現(xiàn)Redis占用的內(nèi)存會(huì)唰唰唰的往上漲,我一開(kāi)始因?yàn)閗ey 和 參數(shù)太多,分開(kāi)寫(xiě)太麻煩了,就圖省事方便,直接把變量拼接到腳本里面,結(jié)果發(fā)現(xiàn)內(nèi)存不停的漲,很是抓狂,找了好久才發(fā)現(xiàn)是這么個(gè)原因。
義變量一定要使用局部變量, 即 local var = 1, 局部變量只在所定義的塊(指控制結(jié)構(gòu), 函數(shù)或chunk等)內(nèi)有效, 使用局部變量可以避免命名沖突 并且訪問(wèn)更快(lua中局部變量和全局變量存儲(chǔ)方式是不一樣的)
如果Lua腳本寫(xiě)的比較長(zhǎng),非本地或局域網(wǎng)的情況下,建議使用 SHA 簽名的方法來(lái)調(diào)用,這樣節(jié)省帶寬,但對(duì)性能似乎沒(méi)什么直接的提升。這里對(duì)小白普及下我理解的原理就是 Redis 會(huì)把每個(gè)腳本都生成唯一簽名,把腳本作為函數(shù)體,并使用該簽名作為腳本的函數(shù)名放到緩存中,所以后面調(diào)用就只需要傳一個(gè) SHA 簽名就可以調(diào)用該函數(shù)了,精簡(jiǎn)很多了。同一個(gè)腳本生成的簽名都是相同的,所以SHA簽名可以先在本地生成,然后在服務(wù)器上 script load 一次腳本,程序中只需保存和使用該簽名即可。另外需要注意的是,腳本如果被改動(dòng)哪怕一個(gè)換行或一個(gè)空格(這些容易被忽略或誤操作)都必須重新 load 來(lái)獲取新的 SHA
注意:獲取 SHA 簽名是多帶帶的功能,不要放在你的正常流程中,當(dāng)本地開(kāi)發(fā)時(shí)就可以生成SHA,把字符串寫(xiě)死在流程中。同樣的腳本,Reids是始終生成相同的簽名的。
通過(guò) eval 帶入的 ARGV 參數(shù)如果原來(lái)是數(shù)字的,會(huì)被轉(zhuǎn)換為字符串,如果你的邏輯中需要判斷該變量 > 0 或 < 0 之類(lèi)的數(shù)字判斷則必須進(jìn)行字符串到數(shù)字的轉(zhuǎn)換,使用 tonumber() 方法 if (tonumber(ARGV[1]) > 0) then return 1; end;
我測(cè)試了幾個(gè) lua script 與 PIPELINE 處理對(duì)比,發(fā)現(xiàn) script 的效率一般比 PIPELINE 高 30% ~ 40% 左右
Redis集群分布式鎖Redis 集群相對(duì)單機(jī)來(lái)說(shuō), 需要考慮一個(gè) 容錯(cuò)性, 設(shè)計(jì)上更為復(fù)雜
由于這個(gè)我也從未實(shí)踐過(guò), 先貼一個(gè)官方的教程貼壓壓驚
https://github.com/antirez/re...
對(duì)應(yīng)的翻譯: http://ifeve.com/redis-lock/
RedLock 算法官方給出了一個(gè) RedLock 算法
情景: 當(dāng)前有N個(gè)完全獨(dú)立的Redis master節(jié)點(diǎn), 分別部署在不同的主機(jī)上
客戶(hù)端獲取鎖的操作:
使用相同key和唯一值(作為value)同時(shí)向這N個(gè)redis節(jié)點(diǎn)請(qǐng)求鎖, 鎖的超時(shí)時(shí)間應(yīng)該 >> 超時(shí)時(shí)間(考慮到請(qǐng)求耗時(shí)), 若某個(gè)節(jié)點(diǎn)阻塞了了應(yīng)盡快跳過(guò)
計(jì)算步驟1消耗的時(shí)間, 若總消耗時(shí)間超過(guò)超時(shí)時(shí)間, 則認(rèn)為鎖失敗. 客戶(hù)端需在大多數(shù)(超過(guò)一半)的節(jié)點(diǎn)上成功獲取鎖, 才認(rèn)為是鎖成功.
如果鎖成功了, 則該鎖有效時(shí)間就是 鎖原始有效時(shí)間 - 步驟1消耗的時(shí)間
如果鎖失敗了(超時(shí)或無(wú)法獲取超過(guò)一半 N/2 + 1 實(shí)例的鎖), 客戶(hù)端會(huì)到每個(gè)節(jié)點(diǎn)釋放鎖(是每個(gè), 即使之前認(rèn)為加鎖失敗的節(jié)點(diǎn))
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/31441.html
摘要:實(shí)現(xiàn)思路實(shí)現(xiàn)分布式鎖思路思路很簡(jiǎn)單,主要用到的函數(shù)是,這個(gè)應(yīng)該是實(shí)現(xiàn)分布式鎖最主要的函數(shù)。實(shí)現(xiàn)任務(wù)隊(duì)列這里的實(shí)現(xiàn)會(huì)用到上面的分布式的鎖機(jī)制,主要是用到了里的有序集合這一數(shù)據(jù)結(jié)構(gòu)。 實(shí)現(xiàn)思路 1.Redis實(shí)現(xiàn)分布式鎖思路 思路很簡(jiǎn)單,主要用到的redis函數(shù)是setnx(),這個(gè)應(yīng)該是實(shí)現(xiàn)分布式鎖最主要的函數(shù)。首先是將某一任務(wù)標(biāo)識(shí)名(這里用Lock:order作為標(biāo)識(shí)名的例子)作...
閱讀 1938·2021-09-23 11:21
閱讀 1725·2019-08-29 17:27
閱讀 1074·2019-08-29 17:03
閱讀 742·2019-08-29 15:07
閱讀 1965·2019-08-29 11:13
閱讀 2400·2019-08-26 12:14
閱讀 956·2019-08-26 11:52
閱讀 1752·2019-08-23 17:09