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>
說(shuō)明:本文采用jedis來(lái)實(shí)現(xiàn)分布式鎖。
封裝工具類(lèi)
@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; /** * 加鎖方法僅針對(duì)單實(shí)例 Redis,哨兵、集群模式無(wú)法使用 * * @param lockKey 加鎖鍵 * @param clientId 加鎖客戶(hù)端唯一標(biāo)識(shí)(采用UUID) * @param seconds 鎖過(guò)期時(shí)間 * @return true標(biāo)識(shí)加鎖成功、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; } }
說(shuō)明:加鎖的原理是基于Redis的NX、PX命令,而解鎖采用的是lua腳本實(shí)現(xiàn)。
模擬秒殺扣減庫(kù)存
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("秒殺成功,剩余庫(kù)存:{}",stockNum); } else { logger.error("秒殺失敗,剩余庫(kù)存:{}", stockNum); } //獲取庫(kù)存數(shù)量 return stockNum; } else { logger.error("加鎖失?。篶lientId:{}",clientId); } } catch (Exception e) { logger.error("decry stock eror",e); } finally { redisLockUtil.unLock(lockKey, clientId); } return 0; }
測(cè)試代碼
@RequestMapping("/redisLockTest") public void redisLockTest() { // 初始化秒殺庫(kù)存數(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("庫(kù)存剩余數(shù)量:{}",stockNum); } catch (Exception e) { logger.error("get stock num error",e); } }); }
執(zhí)行結(jié)果如下:
方案優(yōu)化
上述分布式鎖實(shí)現(xiàn)庫(kù)存扣減是否存在相關(guān)問(wèn)題呢?
問(wèn)題1:扣減庫(kù)存邏輯無(wú)法保證原子性,
具體的代碼如下:
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腳本來(lái)實(shí)現(xiàn)原子性的庫(kù)存扣減。
具體實(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; }
問(wèn)題2:如果加鎖失敗,則會(huì)直接訪問(wèn),無(wú)法重入鎖
因?yàn)閱螜C(jī)版本的鎖是無(wú)法重入鎖,所以加鎖失敗就直接返回,此問(wèn)題的解決方案,可以采用Redisson來(lái)實(shí)現(xiàn),關(guān)于Redisson實(shí)現(xiàn)分布式鎖,將在后續(xù)的文章中進(jìn)行詳細(xì)的講解。
總結(jié)
本文主要講解了Spring Boot集成Redis實(shí)現(xiàn)單機(jī)版本分布式鎖,雖然單機(jī)版分布式鎖存在鎖的續(xù)期、鎖的重入問(wèn)題,但是我們還是需要掌握其原理和實(shí)現(xiàn)方法
到此這篇關(guān)于Spring Boot 實(shí)現(xiàn)Redis分布式鎖原理的文章就介紹到這了,更多相關(guān)Spring Boot Redis分布式鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(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)超賣(mài)問(wèn)題還原和流程分析(分布式鎖)
- SpringBoot整合Redisson實(shí)現(xiàn)分布式鎖
- 關(guān)于SpringBoot 使用 Redis 分布式鎖解決并發(fā)問(wèn)題
- 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ì)算的工具包,幾行代碼就能求出多變形的中心,簡(jiǎn)直yyds!還不快跟隨小編一起學(xué)起來(lái)2022-10-10Spring中的注解@Value("#{}")與@Value("${}")的區(qū)別
這篇文章主要介紹了Spring中的注解@Value(“#{}“)與@Value(“${}“)的區(qū)別到底是什么,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-06-06idea在用Mybatis時(shí)xml文件sql不提示解決辦法(提示后背景顏色去除)
mybatis的xml文件配置的時(shí)候,有時(shí)候會(huì)沒(méi)有提示,這讓我們很頭疼,下面這篇文章主要給大家介紹了關(guān)于idea在用Mybatis時(shí)xml文件sql不提示的解決辦法,提示后背景顏色去除的相關(guān)資料,需要的朋友可以參考下2023-03-03Spring?Boot?集成Elasticsearch模塊實(shí)現(xiàn)簡(jiǎn)單查詢(xún)功能
本文講解了Spring?Boot集成Elasticsearch采用的是ES模板的方式實(shí)現(xiàn)基礎(chǔ)查詢(xún),本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2022-06-06解析SpringSecurity+JWT認(rèn)證流程實(shí)現(xiàn)
這篇文章主要介紹了解析SpringSecurity+JWT認(rèn)證流程實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07解決Spring?AOP攔截抽象類(lèi)(父類(lèi))中方法失效問(wèn)題
這篇文章主要介紹了解決Spring?AOP攔截抽象類(lèi)(父類(lèi))中方法失效問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11