《艾爾登法環》亞壇高原海市蜃樓法師塔開啟方法
redis setnx命令是當key不存在時設定key
但setnx不能同時完成expire設定失效時長,不能保證setnx和expire的原子性.
因此可以採用set命令的可選項:
set key value [EX seconds] [PX milliseconds] [NX|XX]
EX seconds:設定失效時長,單位秒
PX milliseconds:設定失效時長,單位毫秒
NX:key不存在時設定value,成功返回OK,失敗返回(nil)
XX:key存在時設定value,成功返回OK,失敗返回(nil)
set name aa ex 100 nx
ttl 檢視剩餘過期時間
程式碼
package com.jzy;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collections;
import java.util.Properties;
public class JedisUtils {
private static JedisPool jp;
static {
// 載入Jedis連線池配置引數
URL resource = JedisUtils.class.getResource("/jedisConfig.properties");
InputStream is = null;
try {
is = new FileInputStream(resource.getPath().substring(1));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Properties prop = new Properties();
try {
prop.load(is);
} catch (IOException e) {
e.printStackTrace();
}
String host = prop.getProperty("jedis.host");
int port = Integer.parseInt(prop.getProperty("jedis.port"));
int maxTotal = Integer.parseInt(prop.getProperty("jedis.maxTotal"));
int maxIdle = Integer.parseInt(prop.getProperty("jedis.maxIdle"));
// 設定Jedis連線池引數
JedisPoolConfig jpc = new JedisPoolConfig();
jpc.setMaxTotal(maxTotal);
jpc.setMaxIdle(maxIdle);
// 初始化Jedis連線池
jp = new JedisPool(jpc, host, port);
}
// 從Jedis連線池獲取連線
public static Jedis getJedis() {
return jp.getResource();
}
//上鎖
public static boolean lock(String key,String lockVal,int expire){
if(null == key){
return false;
}
try {
Jedis jedis = getJedis();
String res = jedis.set(key, lockVal, "NX", "EX", expire);
jedis.close();
return res != null && "OK".equals(res);
}catch (Exception e){
e.printStackTrace();
return false;
}
}
//重試鎖
public static boolean tryLock(String key,String lockValue,int timeout,int expire){
final long start = System.currentTimeMillis();
if(timeout > expire) {
timeout = expire;
}
final long end = start + timeout * 1000;
boolean res = false; // 預設返回失敗
while(!(res = lock(key,lockValue,expire))){ // 呼叫了上面的 lock方法
if(System.currentTimeMillis() > end) {
break;
}
}
return res;
}
//刪除鎖 這裡使用lua指令碼 因為在java中要經過多步操作,獲取鎖,判斷鎖狀態,刪除鎖等有原子問題,因此使用redis支援的lua指令碼 單執行緒同步執行
private static final Long lockReleaseOK = 1L;
static String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
public static boolean releaseLock(String key ,String lockValue){
if(key == null || lockValue == null) {
return false;
}
try {
Jedis jedis = getJedis();
Object res =jedis.eval(luaScript, Collections.singletonList(key),Collections.singletonList(lockValue));
jedis.close();
return res!=null && res.equals(lockReleaseOK);
} catch (Exception e) {
return false;
}
}
}
上面我們說的是redis,是單點的情況。如果是在redis sentinel叢集中情況就有所不同了。關於redis sentinel 叢集可以看這裡。在redis sentinel叢集中,我們具有多臺redis,他們之間有著主從的關係,例如一主二從。我們的set命令對應的資料寫到主庫,然後同步到從庫。當我們申請一個鎖的時候,對應就是一條命令 setnx mykey myvalue ,在redis sentinel叢集中,這條命令先是落到了主庫。假設這時主庫down了,而這條資料還沒來得及同步到從庫,sentinel將從庫中的一臺選舉為主庫了。這時,我們的新主庫中並沒有mykey這條資料,若此時另外一個client執行 setnx mykey hisvalue , 也會成功,即也能得到鎖。這就意味著,此時有兩個client獲得了鎖。這不是我們希望看到的,雖然這個情況發生的記錄很小,只會在主從failover的時候才會發生,大多數情況下、大多數系統都可以容忍,但是不是所有的系統都能容忍這種瑕疵。
redlock
為了解決故障轉移情況下的缺陷,Antirez 發明了 Redlock 演算法,使用redlock演算法,需要多個redis例項,加鎖的時候,它會想多半節點發送 setex mykey myvalue 命令,只要過半節點成功了,那麼就算加鎖成功了。釋放鎖的時候需要想所有節點發送del命令。這是一種基於【大多數都同意】的一種機制。感興趣的可以查詢相關資料。在實際工作中使用的時候,我們可以選擇已有的開源實現,python有redlock-py,java 中有Redisson redlock。
redlock確實解決了上面所說的“不靠譜的情況”。但是,它解決問題的同時,也帶來了代價。你需要多個redis例項,你需要引入新的庫 程式碼也得調整,效能上也會有損。
以上是 《艾爾登法環》亞壇高原海市蜃樓法師塔開啟方法 的全部内容, 来源链接: utcz.com/yxgl/577076.html