單機(jī)版redis解決方案(redis集群還在學(xué))
此博客解決方案適用于絕大部分業(yè)務(wù)場(chǎng)景,不能容忍任何一點(diǎn)的race condition(如錢相關(guān)的業(yè)務(wù))并不適用
原理分析參考另一篇博客
什么分布式鎖?
本地鎖:在多個(gè)線程中,保證只有一個(gè)線程執(zhí)行(線程安全的問(wèn)題)
分布鎖:在分布式中,保證只有一個(gè)jvm執(zhí)行(多個(gè)jvm線程安全問(wèn)題)
如果我們服務(wù)器是集群的時(shí)候,定時(shí)任務(wù)可能會(huì)重復(fù)執(zhí)行 可以采用分布式鎖解決
分布式鎖解決方案:
- 基于數(shù)據(jù)庫(kù)方式實(shí)現(xiàn)
- 基于Zk方式實(shí)現(xiàn) 采用臨時(shí)節(jié)點(diǎn)+事件通知
- 基于Redis方式實(shí)現(xiàn) setnx 方式
解決分布式鎖核心思路:
- 獲取鎖
多個(gè)不同的jvm 同時(shí)創(chuàng)建一個(gè)相同的標(biāo)記(全局唯一的) 只要誰(shuí)能夠創(chuàng)建成功誰(shuí)就能夠獲取鎖 - 釋放鎖
釋放該全局唯一的標(biāo)記,其他的jvm重新進(jìn)入到獲取鎖資源。 - 超時(shí)鎖(沒(méi)有獲取鎖、已經(jīng)獲取鎖)
等待獲取鎖的超時(shí)時(shí)間
已經(jīng)獲取到鎖 鎖的有效期 5s
分析:基于Redis實(shí)現(xiàn)分布式鎖思路
獲取鎖
多個(gè)不同的jvm 同時(shí)創(chuàng)建一個(gè)相同的標(biāo)記使用Setnx命令,因?yàn)镽ediskey必須保證是唯一的,只要誰(shuí)能夠創(chuàng)建成功誰(shuí)就能夠獲取鎖
Set命令的時(shí)候:如果key不存在則創(chuàng)建,如果key已經(jīng)存在則修改原值;
SetNx命令: 如果key不存在則創(chuàng)建 返回1,如果已經(jīng)存在則不執(zhí)行任何操作返回0
1 不存在創(chuàng)建成功 0 已經(jīng)存在 不執(zhí)行任何操作。釋放鎖
對(duì)我們的redis的key設(shè)置一個(gè)有效期(或者是主動(dòng)刪除該key)可以靈活的自動(dòng)的釋放該全局唯一的標(biāo)記,其他的jvm重新進(jìn)入到獲取鎖資源。超時(shí)鎖(沒(méi)有獲取鎖、已經(jīng)獲取鎖)
等待獲取鎖的超時(shí)時(shí)間
已經(jīng)獲取到鎖 鎖的有效期 5s
分析基于Zk實(shí)現(xiàn)分布式鎖思路
獲取鎖
多個(gè)不同的jvm在zk集群上創(chuàng)建一個(gè)相同的全局唯一的臨時(shí)路徑,只要誰(shuí)能夠創(chuàng)建成功誰(shuí)就能夠獲取到鎖。
分析:臨時(shí)節(jié)點(diǎn)對(duì)我們節(jié)點(diǎn)設(shè)置有效期釋放鎖
人為主動(dòng)刪除該節(jié)點(diǎn)或者使用Session有效期超時(shí)鎖(沒(méi)有獲取鎖、已經(jīng)獲取鎖)
等待獲取鎖的超時(shí)時(shí)間
已經(jīng)獲取到鎖 鎖的有效期 5s
代碼實(shí)現(xiàn)
- maven依賴
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!--用于判斷字符串是否為空,測(cè)試類用到了-->
<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的端口號(hào)
private static int PORT = 6379;
//可用連接實(shí)例的最大數(shù)目,默認(rèn)值為8;
//如果賦值為-1,則表示不限制;如果pool已經(jīng)分配了maxActive個(gè)jedis實(shí)例,則此時(shí)pool的狀態(tài)為exhausted(耗盡)。
private static int MAX_ACTIVE = 100;
//控制一個(gè)pool最多有多少個(gè)狀態(tài)為idle(空閑的)的jedis實(shí)例,默認(rèn)值也是8。
private static int MAX_IDLE = 20;
//等待可用連接的最大時(shí)間,單位毫秒,默認(rèn)值為-1,表示永不超時(shí)。如果超過(guò)等待時(shí)間,則直接拋出JedisConnectionException;
private static int MAX_WAIT = 3000;
private static int TIMEOUT = 3000;
//在borrow一個(gè)jedis實(shí)例時(shí),是否提前進(jìn)行validate操作;如果為true,則得到的jedis實(shí)例均是可用的;
private static boolean TEST_ON_BORROW = true;
//在return給pool時(shí),是否提前進(jìn)行validate操作;
private static boolean TEST_ON_RETURN = true;
private static JedisPool jedisPool = null;
/**
* redis過(guò)期時(shí)間,以秒為單位
*/
public final static int EXRP_HOUR = 60 * 60; //一小時(shí)
public final static int EXRP_DAY = 60 * 60 * 24; //一天
public final static int EXRP_MONTH = 60 * 60 * 24 * 30; //一個(gè)月
/**
* 初始化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);
//有密碼用下面這種構(gòu)造方法
// 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實(shí)例
*
* @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;
}
}
- 分布式鎖實(shí)現(xiàn)工具類
public class RedisLock {
private static int lockSuccss = 1;
/**
* @param lockKey 在Redis中創(chuàng)建的key值
* @param notLockTime 嘗試獲取鎖超時(shí)時(shí)間
* @return 返回lock成功值
*/
public String getLock(String lockKey,int notLockTime, int timeOut){
//獲取Redis連接
Jedis jedis=RedisUtil.getJedis();
//計(jì)算超時(shí)時(shí)間
long endTime = System.currentTimeMillis() + notLockTime;
try {
//當(dāng)前系統(tǒng)時(shí)間小于endTime說(shuō)明獲取鎖沒(méi)有超時(shí)
while (System.currentTimeMillis()<endTime){
String lockValue = UUID.randomUUID().toString();
// 當(dāng)多個(gè)不同的jvm同時(shí)創(chuàng)建一個(gè)相同的rediskey 只要誰(shuí)能夠創(chuàng)建成功誰(shuí)就能夠獲取鎖
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 {
// 判斷獲取鎖的時(shí)候保證自己刪除自己(防止空指針將lockValue寫前面,因?yàn)閞edis可能獲取到空值)
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;
}
}
- 測(cè)試類
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è)務(wù)邏輯
System.out.println(Thread.currentThread().getName() + ",獲取鎖成功:lockValue:" + lockValue);
// 3.釋放鎖(設(shè)置了失效時(shí)間,不釋放也不會(huì)出現(xiàn)死鎖)
mayiktRedisLock.unLock(LOCKKEY, lockValue);
}
public static void main(String[] args) {
service();
}
/***
*
* 嘗試獲取鎖為什么次數(shù)限制?
* 如果我們業(yè)務(wù)邏輯5s 內(nèi)沒(méi)有執(zhí)行完畢呢?
*
* 分場(chǎng)景:
* 1.鎖的超時(shí)時(shí)間根據(jù)業(yè)務(wù)場(chǎng)景來(lái)預(yù)估
* 2.可以自己延遲鎖的時(shí)間
* 3.在提交事務(wù)的時(shí)候檢查鎖是否已經(jīng)超時(shí) 如果已經(jīng)超時(shí)則回滾(手動(dòng)回滾)否則提交。
*
* 僅限于單機(jī)版本
*/
}
From 螞蟻課堂