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; /** * 加鎖方法僅針對(duì)單實(shí)例 Redis,哨兵、集群模式無法使用 * * @param lockKey 加鎖鍵 * @param clientId 加鎖客戶端唯一標(biāo)識(shí)(采用UUID) * @param seconds 鎖過期時(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; } }
說明:加鎖的原理是基于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("加鎖失敗:clientId:{}",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:如果加鎖失敗,則會(huì)直接訪問,無法重入鎖
因?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)容請(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)超賣問題還原和流程分析(分布式鎖)
- 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-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ì)沒有提示,這讓我們很頭疼,下面這篇文章主要給大家介紹了關(guān)于idea在用Mybatis時(shí)xml文件sql不提示的解決辦法,提示后背景顏色去除的相關(guān)資料,需要的朋友可以參考下2023-03-03Spring?Boot?集成Elasticsearch模塊實(shí)現(xiàn)簡單查詢功能
本文講解了Spring?Boot集成Elasticsearch采用的是ES模板的方式實(shí)現(xiàn)基礎(chǔ)查詢,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2022-06-06解析SpringSecurity+JWT認(rèn)證流程實(shí)現(xiàn)
這篇文章主要介紹了解析SpringSecurity+JWT認(rèn)證流程實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07