單機版redis解決方案(redis集群還在學)
此博客解決方案適用于絕大部分業(yè)務場景在跳,不能容忍任何一點的race condition(如錢相關的業(yè)務)并不適用
原理分析參考另一篇博客
什么分布式鎖?
本地鎖:在多個線程中政己,保證只有一個線程執(zhí)行(線程安全的問題)
分布鎖:在分布式中艇潭,保證只有一個jvm執(zhí)行(多個jvm線程安全問題)
如果我們服務器是集群的時候,定時任務可能會重復執(zhí)行 可以采用分布式鎖解決
分布式鎖解決方案:
- 基于數(shù)據(jù)庫方式實現(xiàn)
- 基于Zk方式實現(xiàn) 采用臨時節(jié)點+事件通知
- 基于Redis方式實現(xiàn) setnx 方式
解決分布式鎖核心思路:
- 獲取鎖
多個不同的jvm 同時創(chuàng)建一個相同的標記(全局唯一的) 只要誰能夠創(chuàng)建成功誰就能夠獲取鎖 - 釋放鎖
釋放該全局唯一的標記宜岛,其他的jvm重新進入到獲取鎖資源。 - 超時鎖(沒有獲取鎖、已經(jīng)獲取鎖)
等待獲取鎖的超時時間
已經(jīng)獲取到鎖 鎖的有效期 5s
分析:基于Redis實現(xiàn)分布式鎖思路
獲取鎖
多個不同的jvm 同時創(chuàng)建一個相同的標記使用Setnx命令界阁,因為Rediskey必須保證是唯一的,只要誰能夠創(chuàng)建成功誰就能夠獲取鎖
Set命令的時候:如果key不存在則創(chuàng)建胖喳,如果key已經(jīng)存在則修改原值泡躯;
SetNx命令: 如果key不存在則創(chuàng)建 返回1,如果已經(jīng)存在則不執(zhí)行任何操作返回0
1 不存在創(chuàng)建成功 0 已經(jīng)存在 不執(zhí)行任何操作。釋放鎖
對我們的redis的key設置一個有效期(或者是主動刪除該key)可以靈活的自動的釋放該全局唯一的標記,其他的jvm重新進入到獲取鎖資源较剃。超時鎖(沒有獲取鎖咕别、已經(jīng)獲取鎖)
等待獲取鎖的超時時間
已經(jīng)獲取到鎖 鎖的有效期 5s
分析基于Zk實現(xiàn)分布式鎖思路
獲取鎖
多個不同的jvm在zk集群上創(chuàng)建一個相同的全局唯一的臨時路徑,只要誰能夠創(chuàng)建成功誰就能夠獲取到鎖写穴。
分析:臨時節(jié)點對我們節(jié)點設置有效期釋放鎖
人為主動刪除該節(jié)點或者使用Session有效期超時鎖(沒有獲取鎖惰拱、已經(jīng)獲取鎖)
等待獲取鎖的超時時間
已經(jīng)獲取到鎖 鎖的有效期 5s
代碼實現(xiàn)
- maven依賴
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!--用于判斷字符串是否為空,測試類用到了-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
- redis工具類配置redis
public class RedisUtil {
//protected static Logger logger = Logger.getLogger(RedisUtil.class);
private static String IP = "你自己的redis IP";
//Redis的端口號
private static int PORT = 6379;
//可用連接實例的最大數(shù)目啊送,默認值為8偿短;
//如果賦值為-1,則表示不限制馋没;如果pool已經(jīng)分配了maxActive個jedis實例昔逗,則此時pool的狀態(tài)為exhausted(耗盡)。
private static int MAX_ACTIVE = 100;
//控制一個pool最多有多少個狀態(tài)為idle(空閑的)的jedis實例篷朵,默認值也是8勾怒。
private static int MAX_IDLE = 20;
//等待可用連接的最大時間,單位毫秒款票,默認值為-1控硼,表示永不超時。如果超過等待時間艾少,則直接拋出JedisConnectionException卡乾;
private static int MAX_WAIT = 3000;
private static int TIMEOUT = 3000;
//在borrow一個jedis實例時,是否提前進行validate操作缚够;如果為true幔妨,則得到的jedis實例均是可用的;
private static boolean TEST_ON_BORROW = true;
//在return給pool時谍椅,是否提前進行validate操作误堡;
private static boolean TEST_ON_RETURN = true;
private static JedisPool jedisPool = null;
/**
* redis過期時間,以秒為單位
*/
public final static int EXRP_HOUR = 60 * 60; //一小時
public final static int EXRP_DAY = 60 * 60 * 24; //一天
public final static int EXRP_MONTH = 60 * 60 * 24 * 30; //一個月
/**
* 初始化Redis連接池
*/
private static void initialPool() {
try {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(MAX_ACTIVE);
config.setMaxIdle(MAX_IDLE);
config.setMaxWaitMillis(MAX_WAIT);
config.setTestOnBorrow(TEST_ON_BORROW);
jedisPool = new JedisPool(config, IP, PORT, TIMEOUT);
//有密碼用下面這種構造方法
// jedisPool = new JedisPool(config, IP, PORT, TIMEOUT, "123456");
} catch (Exception e) {
//logger.error("First create JedisPool error : "+e);
e.getMessage();
}
}
/**
* 在多線程環(huán)境同步初始化
*/
private static synchronized void poolInit() {
if (jedisPool == null) {
initialPool();
}
}
/**
* 同步獲取Jedis實例
*
* @return Jedis
*/
public synchronized static Jedis getJedis() {
if (jedisPool == null) {
poolInit();
}
Jedis jedis = null;
try {
if (jedisPool != null) {
jedis = jedisPool.getResource();
}
} catch (Exception e) {
e.getMessage();
// logger.error("Get jedis error : "+e);
}
return jedis;
}
/**
* 釋放jedis資源
*
* @param jedis
*/
public static void returnResource(final Jedis jedis) {
if (jedis != null && jedisPool != null) {
jedisPool.returnResource(jedis);
}
}
public static Long sadd(String key, String... members) {
Jedis jedis = null;
Long res = null;
try {
jedis = getJedis();
res = jedis.sadd(key, members);
} catch (Exception e) {
//logger.error("sadd error : "+e);
e.getMessage();
}
return res;
}
}
- 分布式鎖實現(xiàn)工具類
public class RedisLock {
private static int lockSuccss = 1;
/**
* @param lockKey 在Redis中創(chuàng)建的key值
* @param notLockTime 嘗試獲取鎖超時時間
* @return 返回lock成功值
*/
public String getLock(String lockKey,int notLockTime, int timeOut){
//獲取Redis連接
Jedis jedis=RedisUtil.getJedis();
//計算超時時間
long endTime = System.currentTimeMillis() + notLockTime;
try {
//當前系統(tǒng)時間小于endTime說明獲取鎖沒有超時
while (System.currentTimeMillis()<endTime){
String lockValue = UUID.randomUUID().toString();
// 當多個不同的jvm同時創(chuàng)建一個相同的rediskey 只要誰能夠創(chuàng)建成功誰就能夠獲取鎖
if(jedis.setnx(lockKey,lockValue)==1){
jedis.expire(lockKey,timeOut/1000);
return lockValue;
}
}
}catch (Exception e){
e.printStackTrace();
}
finally {
if(jedis!=null){
jedis.close();
}
}
return null;
}
/**
* 釋放鎖
* @return
*/
public boolean unLock(String lockKey,String lockValue){
//獲取Redis連接
Jedis jedis=RedisUtil.getJedis();
try {
// 判斷獲取鎖的時候保證自己刪除自己(防止空指針將lockValue寫前面,因為redis可能獲取到空值)
if(lockValue.equals(jedis.get(lockKey))){
return jedis.del(lockKey)>0 ? true:false;
}
}
catch (Exception e){
e.printStackTrace();
}finally {
if (jedis != null) {
jedis.close();
}
}
return false;
}
}
- 測試類
public class TestService {
private static final String LOCKKEY = "lock";
public static void service() {
// 1.獲取鎖
RedisLock mayiktRedisLock = new RedisLock();
String lockValue = mayiktRedisLock.getLock(LOCKKEY, 5000, 5000);
if (StringUtils.isEmpty(lockValue)) {
System.out.println(Thread.currentThread().getName() + "雏吭,獲取鎖失敗了");
return;
}
// 執(zhí)行我們的業(yè)務邏輯
System.out.println(Thread.currentThread().getName() + "锁施,獲取鎖成功:lockValue:" + lockValue);
// 3.釋放鎖(設置了失效時間,不釋放也不會出現(xiàn)死鎖)
mayiktRedisLock.unLock(LOCKKEY, lockValue);
}
public static void main(String[] args) {
service();
}
/***
*
* 嘗試獲取鎖為什么次數(shù)限制杖们?
* 如果我們業(yè)務邏輯5s 內(nèi)沒有執(zhí)行完畢呢悉抵?
*
* 分場景:
* 1.鎖的超時時間根據(jù)業(yè)務場景來預估
* 2.可以自己延遲鎖的時間
* 3.在提交事務的時候檢查鎖是否已經(jīng)超時 如果已經(jīng)超時則回滾(手動回滾)否則提交。
*
* 僅限于單機版本
*/
}
From 螞蟻課堂