一)分佈式鎖特性
1、互斥性:需保證任何時刻只能由一個客戶端持有鎖。
2、高可用:加鎖和解鎖操作要一致,保證命令執行的原子性。
3、容錯性:當Redis大多數節點正常使用時,客戶端可用加鎖和解鎖。或加入重試機制。
二)單機模式分佈式鎖
實現原理:
第一步:執行setIfAbsent命令(當key不存在時,才加鎖成功, key存在, 表示已鎖),並設置10秒有效期, 防止加鎖失敗導致死鎖。
加鎖方式1:
boolean isLock = stringRedisTemplate.opsForValue().setIfAbsent(redisLockKey, "LOCK", 10, TimeUnit.SECONDS);
加鎖方式2:在獲取鎖的時候能夠保證設置Redis值和過期時間的原子性。
public String lock(final String key, final String value, final int secondsToExpire) {
String result = stringRedisTemplate.execute(new RedisCallback<String>() {
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
Object nativeConnection = connection.getNativeConnection();
// 設置參數
SetParams setParams = SetParams.setParams();
setParams.nx(); // 表示只有當鎖定資源不存在的時候才能SET成功
setParams.ex(secondsToExpire); // expire表示鎖定資源的自動過期時間,單位是秒
// 單機模式
if (nativeConnection instanceof Jedis) {
// JedisCommands需要引入Jedis的Jar, 位於redis.clients.jedis.commands.JedisCommands下
JedisCommands commands = (JedisCommands) connection;
return commands.set(key, "鎖定資源", setParams);
}
// 集羣模式
else if (nativeConnection instanceof JedisCluster ) {
JedisCluster cluster = (JedisCluster) connection;
return cluster.set(key, "鎖定資源", setParams);
}
return null;
}
});
return result;
}
第二步:執行業務邏輯。
第三步:業務邏輯執行完, 把key刪除, 相當於解鎖, 執行del命令。
stringRedisTemplate.delete(redisLockKey);
大多數情況下,該方式加鎖解鎖已經可以正常使用,除了集羣模式下,多個客戶端情況。
比如A客戶端請求加鎖成功,B客戶端也請求加鎖成功,但B客戶端執行效率高,把key解鎖了,如果又有一個請求A客戶端,但上一次A客戶端請求還未完成,會出現加鎖解鎖不一致問題,無法保證鎖的互斥性。
加鎖、執行業務邏輯、解鎖源碼:
// http://localhost:8080/redis/redisLock
@RequestMapping(value = "/redisLock")
public String redisLock() {
// 初始化
String redisLockKey = "redis_lock_key";
// 執行setIfAbsent命令(當key不存在時,才加鎖成功, key存在, 表示已鎖),並設置10秒有效期, 防止加鎖失敗導致死鎖
boolean isLock = stringRedisTemplate.opsForValue().setIfAbsent(redisLockKey, "LOCK", 10, TimeUnit.SECONDS);
if (!isLock) {
System.out.println("-----lock-----");
return "LOCK";
}
System.out.println("-----lock success-----");
System.out.println("==>begin 開始執行業務邏輯!");
try {
// 執行業務邏輯
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("==>end 結束執行業務邏輯!");
// 業務邏輯執行完, 把key刪除, 相當於解鎖, 執行del命令
stringRedisTemplate.delete(redisLockKey);
System.out.println("-----unlock success-----");
return "S";
}
三)集羣模式分佈式鎖
Redis從2.6.0可使用內置的Lua解釋器,利用EVAL命令對Lua腳本進行求值。
可以通過Lua腳本來達到釋放鎖的原子操作,定義 Lua 腳本如下:
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end;
使用LUA腳本,把判斷鎖是否存在,不存在則直接返回,存在則刪除的邏輯直接綁定。保證了Redis命令的原子性,要麼命令執行都成功,要麼命令執行都失敗。
解鎖代碼:
public String unlock(final String key, final String value) {
List<String> keyList = new ArrayList<String>();
keyList.add(key);
List<String> valueList = new ArrayList<String>();
valueList.add(value);
// LUA腳本
StringBuilder builder = new StringBuilder();
builder.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
builder.append("then ");
builder.append(" return redis.call(\"del\",KEYS[1]) ");
builder.append("else ");
builder.append(" return 0 ");
builder.append("end ");
String lua = builder.toString();
// 執行腳本
String result = stringRedisTemplate.execute(new RedisCallback<String>() {
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
Object nativeConnection = connection.getNativeConnection();
// 單機模式
if (nativeConnection instanceof Jedis) {
return (String) ((Jedis) nativeConnection).eval(lua, keyList, valueList);
}
// 集羣模式
else if (nativeConnection instanceof JedisCluster ) {
return (String) ((JedisCluster) nativeConnection).eval(lua, keyList, valueList);
}
return null;
}
});
return result;
}
識別二維碼關注個人微信公衆號
本章完結,待續,歡迎轉載!
本文說明:該文章屬於原創,如需轉載,請標明文章轉載來源!