Spring?Boot?實(shí)現(xiàn)Redis分布式鎖原理
分布式鎖實(shí)現(xiàn)
引入jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>說明:本文采用jedis來實(shí)現(xiàn)分布式鎖。
封裝工具類
@Component
public class RedisLockUtil
{
private static final Logger logger = LoggerFactory.getLogger(RedisLockUtil.class);
private static final Long RELEASE_SUCCESS = 1L;
private static final String LOCK_SUCCESS = "OK";
private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 加鎖方法僅針對單實(shí)例 Redis,哨兵、集群模式無法使用
*
* @param lockKey 加鎖鍵
* @param clientId 加鎖客戶端唯一標(biāo)識(采用UUID)
* @param seconds 鎖過期時(shí)間
* @return true標(biāo)識加鎖成功、false代表加鎖失敗
*/
public Boolean tryLock(String lockKey, String clientId, long seconds)
{
try
{
return redisTemplate
.execute((RedisCallback<Boolean>) redisConnection -> {
Jedis jedis = (Jedis) redisConnection.getNativeConnection();
SetParams params =new SetParams();
params.nx();
params.px(seconds);
String result = jedis.set(lockKey, clientId, params);
if (LOCK_SUCCESS.equals(result))
{
return Boolean.TRUE;
}
return Boolean.FALSE;
});
}
catch (Exception e)
{
logger.error("tryLock error",e);
}
return false;
}
/**
*釋放鎖,保持原子性操作,采用了lua腳本
*
* @param lockKey
* @param clientId
* @return
*/
public Boolean unLock(String lockKey, String clientId)
{
try
{
return redisTemplate
.execute((RedisCallback<Boolean>) redisConnection -> {
Jedis jedis = (Jedis) redisConnection.getNativeConnection();
Object result = jedis.eval(RELEASE_LOCK_SCRIPT,
Collections.singletonList(lockKey),
Collections.singletonList(clientId));
if (RELEASE_SUCCESS.equals(result))
{
return Boolean.TRUE;
}
return Boolean.FALSE;
});
}
catch (Exception e)
{
logger.error("unlock error",e);
}
return Boolean.FALSE;
}
}說明:加鎖的原理是基于Redis的NX、PX命令,而解鎖采用的是lua腳本實(shí)現(xiàn)。
模擬秒殺扣減庫存
public int lockStock()
{
String lockKey="lock:stock";
String clientId = UUID.randomUUID().toString();
long seconds =1000l;
try
{
//加鎖
boolean flag=redisLockUtil.tryLock(lockKey, clientId, seconds);
//加鎖成功
if(flag)
{
logger.info("加鎖成功 clientId:{}",clientId);
int stockNum= Integer.valueOf((String)redisUtil.get("seckill:goods:stock"));
if(stockNum>0)
{
stockNum--;
redisUtil.set("seckill:goods:stock",String.valueOf(stockNum));
logger.info("秒殺成功,剩余庫存:{}",stockNum);
}
else
{
logger.error("秒殺失敗,剩余庫存:{}", stockNum);
}
//獲取庫存數(shù)量
return stockNum;
}
else
{
logger.error("加鎖失?。篶lientId:{}",clientId);
}
}
catch (Exception e)
{
logger.error("decry stock eror",e);
}
finally
{
redisLockUtil.unLock(lockKey, clientId);
}
return 0;
}測試代碼
@RequestMapping("/redisLockTest")
public void redisLockTest()
{
// 初始化秒殺庫存數(shù)量
redisUtil.set("seckill:goods:stock", "10");
List<Future> futureList = new ArrayList<>();
//多線程異步執(zhí)行
ExecutorService executors = Executors.newScheduledThreadPool(10);
//
for (int i = 0; i < 30; i++)
{
futureList.add(executors.submit(this::lockStock));
try
{
Thread.sleep(100);
}
catch (InterruptedException e)
{
logger.error("redisLockTest error",e);
}
}
// 等待結(jié)果,防止主線程退出
futureList.forEach(t -> {
try
{
int stockNum =(int) t.get();
logger.info("庫存剩余數(shù)量:{}",stockNum);
}
catch (Exception e)
{
logger.error("get stock num error",e);
}
});
}執(zhí)行結(jié)果如下:

方案優(yōu)化
上述分布式鎖實(shí)現(xiàn)庫存扣減是否存在相關(guān)問題呢?
問題1:扣減庫存邏輯無法保證原子性,
具體的代碼如下:
int stockNum= Integer.valueOf((String)redisUtil.get("seckill:goods:stock"));
if(stockNum>0)
{
stockNum--;
redisUtil.set("seckill:goods:stock",String.valueOf(stockNum));
}這是典型的RMW模型,前面章節(jié)已經(jīng)介紹了具體的實(shí)現(xiàn)方案,可以采用lua腳本和Redis的incry原子性命令實(shí)現(xiàn),這里采用lua腳本來實(shí)現(xiàn)原子性的庫存扣減。
具體實(shí)現(xiàn)如下:
public long surplusStock(String key ,int num)
{
StringBuilder lua_surplusStock = new StringBuilder();
lua_surplusStock.append(" local key = KEYS[1];");
lua_surplusStock.append(" local subNum = tonumber(ARGV[1]);");
lua_surplusStock.append(" local surplusStock=tonumber(redis.call('get',key));");
lua_surplusStock.append(" if (surplusStock- subNum>= -1) then");
lua_surplusStock.append(" return redis.call('incrby', KEYS[1], 0-subNum);");
lua_surplusStock.append(" else ");
lua_surplusStock.append(" return -1;");
lua_surplusStock.append(" end");
List<String> keys = new ArrayList<>();
keys.add(key);
// 腳本里的ARGV參數(shù)
List<String> args = new ArrayList<>();
args.add(Integer.toString(num));
long result = redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
Object nativeConnection = connection.getNativeConnection();
// 單機(jī)模式
if (nativeConnection instanceof Jedis)
{
return (Long) ((Jedis) nativeConnection).eval(lua_surplusStock.toString(), keys, args);
}
return -1l;
}
});
return result;
}問題2:如果加鎖失敗,則會直接訪問,無法重入鎖
因?yàn)閱螜C(jī)版本的鎖是無法重入鎖,所以加鎖失敗就直接返回,此問題的解決方案,可以采用Redisson來實(shí)現(xiàn),關(guān)于Redisson實(shí)現(xiàn)分布式鎖,將在后續(xù)的文章中進(jìn)行詳細(xì)的講解。
總結(jié)
本文主要講解了Spring Boot集成Redis實(shí)現(xiàn)單機(jī)版本分布式鎖,雖然單機(jī)版分布式鎖存在鎖的續(xù)期、鎖的重入問題,但是我們還是需要掌握其原理和實(shí)現(xiàn)方法
到此這篇關(guān)于Spring Boot 實(shí)現(xiàn)Redis分布式鎖原理的文章就介紹到這了,更多相關(guān)Spring Boot Redis分布式鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot使用Redisson實(shí)現(xiàn)分布式鎖(秒殺系統(tǒng))
- SpringBoot中使用redis做分布式鎖的方法
- SpringBoot之使用Redis實(shí)現(xiàn)分布式鎖(秒殺系統(tǒng))
- springboot 集成redission 以及分布式鎖的使用詳解
- Springboot整合Redis實(shí)現(xiàn)超賣問題還原和流程分析(分布式鎖)
- SpringBoot整合Redisson實(shí)現(xiàn)分布式鎖
- 關(guān)于SpringBoot 使用 Redis 分布式鎖解決并發(fā)問題
- SpringBoot RedisTemplate分布式鎖的項(xiàng)目實(shí)戰(zhàn)
- Spring?boot?整合?Redisson實(shí)現(xiàn)分布式鎖并驗(yàn)證功能
相關(guān)文章
三行Java代碼實(shí)現(xiàn)計(jì)算多邊形的幾何中心點(diǎn)
因?yàn)楣ぷ餍枰?jì)算采煤機(jī)工作面的中心點(diǎn),如果套用數(shù)學(xué)的計(jì)算公式,用java去實(shí)現(xiàn),太麻煩了。本文將利用java幾何計(jì)算的工具包,幾行代碼就能求出多變形的中心,簡直yyds!還不快跟隨小編一起學(xué)起來2022-10-10
Spring中的注解@Value("#{}")與@Value("${}")的區(qū)別
這篇文章主要介紹了Spring中的注解@Value(“#{}“)與@Value(“${}“)的區(qū)別到底是什么,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-06-06
idea在用Mybatis時(shí)xml文件sql不提示解決辦法(提示后背景顏色去除)
mybatis的xml文件配置的時(shí)候,有時(shí)候會沒有提示,這讓我們很頭疼,下面這篇文章主要給大家介紹了關(guān)于idea在用Mybatis時(shí)xml文件sql不提示的解決辦法,提示后背景顏色去除的相關(guān)資料,需要的朋友可以參考下2023-03-03
Spring?Boot?集成Elasticsearch模塊實(shí)現(xiàn)簡單查詢功能
本文講解了Spring?Boot集成Elasticsearch采用的是ES模板的方式實(shí)現(xiàn)基礎(chǔ)查詢,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2022-06-06
解析SpringSecurity+JWT認(rèn)證流程實(shí)現(xiàn)
這篇文章主要介紹了解析SpringSecurity+JWT認(rèn)證流程實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07

