用 原生 Redis(Jedis、Lettuce) 實現分布式鎖,可以參考 Redisson 的原理,但需要自己處理鎖的自動續期、故障恢復等細節。核心思路是使用 Redis 的 SET NX EX
或 SET PX NX
命令來實現互斥鎖,并利用 Lua 腳本 保障原子性。
實現思路
- 獲取鎖
SET key value NX PX expiration
,確保鎖只能被一個線程獲取,并設置過期時間。
- 自動續期
- 通過 后臺線程 定時續期,防止業務執行時間過長導致鎖超時釋放。
- 釋放鎖
- 通過 Lua 腳本 保證原子性,只有 當前線程 持有鎖時才能釋放。
完整代碼
1. 依賴引入
使用 Jedis 作為 Redis 客戶端:
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.3.1</version>
</dependency>
2. 分布式鎖實現
獲取鎖
import redis.clients.jedis.Jedis;
import java.util.UUID;public class RedisLock {private static final String LOCK_SUCCESS = "OK";private static final String LOCK_KEY = "my_lock";private static final int EXPIRE_TIME = 10_000; // 10秒過期private final Jedis jedis;private final String lockValue; // 唯一標識鎖的持有者public RedisLock(Jedis jedis) {this.jedis = jedis;this.lockValue = UUID.randomUUID().toString(); // 防止誤刪}public boolean tryLock() {String result = jedis.set(LOCK_KEY, lockValue, "NX", "PX", EXPIRE_TIME);return LOCK_SUCCESS.equals(result); // 返回 true 代表加鎖成功}
}
自動續期
如果業務執行時間超過 10s,鎖會自動釋放,所以需要定時續期:
import java.util.concurrent.*;public class LockRenewal {private static final int RENEWAL_INTERVAL = 5000; // 每5秒續期private final Jedis jedis;private final String lockValue;private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);public LockRenewal(Jedis jedis, String lockValue) {this.jedis = jedis;this.lockValue = lockValue;}public void startRenewal() {scheduler.scheduleAtFixedRate(() -> {if (lockValue.equals(jedis.get("my_lock"))) {jedis.pexpire("my_lock", 10_000);}}, 0, RENEWAL_INTERVAL, TimeUnit.MILLISECONDS);}public void stopRenewal() {scheduler.shutdown();}
}
釋放鎖
使用 Lua 腳本,確保只有 持有鎖的線程 才能刪除:
public void unlock() {String luaScript ="if redis.call('get', KEYS[1]) == ARGV[1] then " +" return redis.call('del', KEYS[1]) " +"else " +" return 0 " +"end";jedis.eval(luaScript, 1, "my_lock", lockValue);
}
3. 使用示例
Jedis jedis = new Jedis("localhost", 6379);
RedisLock redisLock = new RedisLock(jedis);
LockRenewal renewal = new LockRenewal(jedis, redisLock.lockValue);if (redisLock.tryLock()) {renewal.startRenewal(); // 開啟自動續期try {// 執行業務邏輯System.out.println("獲取到鎖,執行業務...");Thread.sleep(15000); // 模擬業務執行時間超過鎖的原始超時時間} catch (InterruptedException e) {e.printStackTrace();} finally {redisLock.unlock();renewal.stopRenewal(); // 停止續期}
} else {System.out.println("獲取鎖失敗");
}
總結
- 互斥性:
SET NX PX
確保只有一個線程能獲取鎖。 - 自動續期:定期
PEXPIRE
延長鎖的存活時間。 - 安全釋放:Lua 腳本保證只有鎖的持有者才能刪除。