本篇文章為大家帶來了關於redis的相關知識,其中主要介紹了關於分散式鎖定是什麼? Redis又是怎麼實現分散式鎖定的?需要滿足什麼條件?下面一起來看看吧,希望對需要的朋友有幫助。
分散式鎖定:滿足分散式系統或叢集模式下多進程可見且互斥的鎖定。
分散式鎖定應該滿足的條件:
#常見的分散式鎖定有三種:
Mysql:mysql本身就帶有鎖定機制,但由於mysql效能本身一般,所以採用分散式鎖的情況下,其實使用mysql作為分散式鎖比較少見
Redis:redis作為分散式鎖是非常常見的一種使用方式,現在企業級開發中基本上都使用redis或zookeeper作為分散式鎖,利用setnx這個方法,如果插入key成功,則表示獲得到了鎖,如果有人插入成功,其他人插入失敗則表示無法獲得到鎖,利用這套邏輯來實現分散式鎖定
Zookeeper:zookeeper也是企業級開發中較好的一個實現分散式鎖定的方案
實作分散式鎖定時需要實作的兩個基本方法:
#取得鎖定:
釋放鎖定:
基於Redis實作分散式鎖定原理:
SET resource_name my_random_value NX PX 30000
利用NX的原子性,多個執行緒並發時,只有一個執行緒可以設定成功,設定成功表示獲得鎖,可以執行後續的業務處理;如果發生異常,過了鎖的有效期,鎖自動釋放;
##1、定義ILock介面
public interface ILock extends AutoCloseable { /** * 尝试获取锁 * * @param timeoutSec 锁持有的超时时间,过期后自动释放 * @return true代表获取锁成功;false代表获取锁失败 */ boolean tryLock(long timeoutSec); /** * 释放锁 * @return */ void unLock(); }
2、基於Redis實作分散式鎖定—RedisLock
public class SimpleRedisLock { private final StringRedisTemplate stringRedisTemplate; private final String name; public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) { this.stringRedisTemplate = stringRedisTemplate; this.name = name; } private static final String KEY_PREFIX = "lock:"; @Override public boolean tryLock(long timeoutSec) { //获取线程标识 String threadId = Thread.currentThread().getId(); //获取锁 Boolean success = stringRedisTemplate.opsForValue() .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS); return Boolean.TRUE.equals(success); } @Override public void unLock() { //通过del删除锁 stringRedisTemplate.delete(KEY_PREFIX + name); } @Override public void close() { unLock(); } }鎖定誤刪問題
問題說明:
持有鎖的線程1在鎖的內部出現了阻塞,這時鎖超時自動釋放,這時線程2嘗試獲得鎖,然後線程2在持有鎖執行過程中,線程1反應過來,繼續執行,走到了刪除鎖定邏輯,此時就會把本來應該屬於執行緒2的鎖進行刪除,這就是鎖定誤刪的情況。解決方案:
在存入鎖時,放入自己執行緒的標識,在刪除鎖定時,判斷目前這把鎖的標識是不是自己存入的,如果是,則進行刪除,如果不是,則不進行刪除。 版本二:解決鎖定誤刪問題public class SimpleRedisLock { private final StringRedisTemplate stringRedisTemplate; private final String name; public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) { this.stringRedisTemplate = stringRedisTemplate; this.name = name; } private static final String KEY_PREFIX = "lock:"; private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-"; @Override public boolean tryLock(long timeoutSec) { //获取线程标识 String threadId = ID_PREFIX + Thread.currentThread().getId(); //获取锁 Boolean success = stringRedisTemplate.opsForValue() .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS); return Boolean.TRUE.equals(success); } @Override public void unLock() { // 获取线程标示 String threadId = ID_PREFIX + Thread.currentThread().getId(); // 获取锁中的标示 String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name); // 判断标示是否一致 if(threadId.equals(id)) { // 释放锁 stringRedisTemplate.delete(KEY_PREFIX + name); } } @Override public void close() { unLock(); } }
問題分析:
上述釋放鎖的程式碼依然有鎖誤刪問題,當執行緒1取得鎖中的執行緒標識,並根據標識判斷是自己的鎖,這時鎖到期自動釋放,恰好執行緒2嘗試取得鎖,並拿到了鎖,此時執行緒1仍執行釋放鎖的操作,就導致誤刪了執行緒2持有的鎖。 原因在於,由java程式實現的釋放鎖定流程不是原子操作,存在線程安全問題。解決方案:
Redis提供了Lua腳本功能,在一個腳本中寫多條Redis指令,可以確保多條指令執行時的原子性。 版本三:呼叫Lua腳本改造分散式鎖定public class SimpleRedisLock implements ILock { private final StringRedisTemplate stringRedisTemplate; private final String name; public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) { this.stringRedisTemplate = stringRedisTemplate; this.name = name; } private static final String KEY_PREFIX = "lock:"; private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-"; @Override public boolean tryLock(long timeoutSec) { //获取线程标识 String threadId = ID_PREFIX + Thread.currentThread().getId(); //获取锁 Boolean success = stringRedisTemplate.opsForValue() .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS); return Boolean.TRUE.equals(success); } @Override public void unLock() { String script = "if redis.call("get",KEYS[1]) == ARGV[1] then\n" + " return redis.call("del",KEYS[1])\n" + "else\n" + " return 0\n" + "end"; //通过执行lua脚本实现锁删除,可以校验随机值 RedisScript<Boolean> redisScript = RedisScript.of(script, Boolean.class); stringRedisTemplate.execute(redisScript, Collections.singletonList(KEY_PREFIX + name), ID_PREFIX + Thread.currentThread().getId()); } @Override public void close() { unLock(); } }
Redis影片教學》
以上是聊聊分散式鎖原理及Redis如何實現分散式鎖的詳細內容。更多資訊請關注PHP中文網其他相關文章!