redis 分散式鎖-簡易版與 redisson 實驗
阿新 • • 發佈:2021-08-12
1
在原來的redsi測試服務上,繼續來做實驗。點選這裡
在原有的IRedisService介面上新增兩個 分散式的獲取鎖與釋放鎖。
/** * 獲取鎖 * @param lockKey * @param requestId * @param expireTimeSeconds * @return */ boolean getLock(String lockKey, String requestId, long expireTimeSeconds); /** * 釋放鎖 * @param lockKey *@param requestId * @return */ boolean releaseLock(String lockKey, String requestId);
2RedisServiceImpl 實現
@Override public boolean getLock(String lockKey, String requestId, long expireTimeSeconds) { return redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTimeSeconds, TimeUnit.SECONDS); } @Overridepublic boolean releaseLock(String lockKey, String requestId) { String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; return (Boolean) this.redisTemplate.execute(new DefaultRedisScript(script, Boolean.class), Collections.singletonList(lockKey), newObject[]{requestId}); }
getLock
在獲得鎖的時候,opsForValue().setIfAbsent用到了redis的 setnx特性,這是分散式鎖的關鍵一步,
為了保證鎖不被一直佔用,要有時間的限制,獲取鎖同時給了失效時間,保證了原子性。
releaseLock
釋放的同時,也要保證原子性,這裡用了lua指令碼。
3 redis 鎖工具類
因為鎖是一個經常用到的東西,所以為了方便他人的使用,需要改造成工具類。
我們的redis 是一個service tools是一個class 這時不能直接注入。
需要用springUtil
package com.zhouqiang.demo.tools; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * @author :zhouqiang * @date :2021/8/11 17:32 * @description: * @version: $ */ @Component public class SpringUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; public SpringUtil() { } @Override public void setApplicationContext(ApplicationContext arg0) throws BeansException { applicationContext = arg0; } public static <T> T getBean(String id, Class<T> type) { return applicationContext.getBean(id, type); } public static <T> T getBean(String id, Object params) { return (T) applicationContext.getBean(id, new Object[]{params}); } public static <T> T getBean(Class<T> type) { return applicationContext.getBean(type); } public static <T> T getBeanByParams(Class<T> type, Object... objects) { return applicationContext.getBean(type, objects); } }
這時候,就可以寫出完整的 Locl 鎖工具類,放在公共的地方,方便其他同事呼叫。
package com.zhouqiang.demo.tools; import com.zhouqiang.demo.enmu.RedisEnum; import com.zhouqiang.demo.service.redis.IRedisService; import lombok.Getter; import javax.annotation.Resource; import java.util.UUID; /** * @author :zhouqiang * @date :2021/8/11 15:58 * @description:redsi快取鎖 * @version: $ */ public class Lock { private String key; private String requestId; private long second; private Lock(String key, String requestId, long second) { if (second <= 0) { throw new RuntimeException("超時時間必須大於0"); } this.key = key; this.requestId = requestId; this.second = second; } private IRedisService getRedisLockService() { return SpringUtil.getBean(IRedisService.class); } /** * 寫一個單例 * * @param redisEnum * @param keys * @return */ public static Lock getInstance(RedisEnum redisEnum, Object... keys) { return new Lock(redisEnum.getKey(), UUID.randomUUID().toString(), redisEnum.getExpiredTime()); } /** * 得到鎖 */ public boolean getLock() { return getRedisLockService().getLock(key, requestId, second); } /** * 釋放鎖 * * @return */ public boolean releaseLock() { return getRedisLockService().releaseLock(key, requestId); } }
既然寫了工具類,方便呼叫。快取的KEY,也需要規範使用。
還要redis列舉。
package com.zhouqiang.demo.enmu; import lombok.Getter; /** * @author :zhouqiang * @date :2021/8/11 16:27 * @description:快取列舉 * @version: $ */ @Getter public enum RedisEnum { /** * 商品鎖 */ SHOP_CLOCK("SHOP_CLOCK", 10L,"商品鎖"); /** * redis的key */ private final String key; /** * 鍵的過期時間,單位為秒,有效期預設為20秒 */ private final Long expiredTime; /** * key的描述 */ private final String desc; RedisEnum(String key, Long expiredTime, String desc) { this.key = key; this.desc = desc; this.expiredTime = expiredTime; } public static RedisEnum getByKey(String key) { for (RedisEnum value : values()) { if (value.getKey() == key) { return value; } } return null; } }
這樣一套下來,基本上redis鎖是完成了。
4 業務
隨便寫一個業務介面。
然後寫實現類。
package com.zhouqiang.demo.service.redisDemo.impl; import com.zhouqiang.demo.enmu.RedisEnum; import com.zhouqiang.demo.enmu.ResultCodeEnum; import com.zhouqiang.demo.entity.ResultCode; import com.zhouqiang.demo.service.redis.IRedisService; import com.zhouqiang.demo.service.redisDemo.RedisDemoService; import com.zhouqiang.demo.tools.Lock; import org.redisson.Redisson; import javax.annotation.Resource; import java.util.UUID; /** * @author :zhouqiang * @date :2021/8/12 10:51 * @description: * @version: $ */ public class RedisDemoServiceImpl implements RedisDemoService { @Resource private IRedisService iRedisService; @Resource private Redisson redisson; @Override public String redisDemoTest() { String id = UUID.randomUUID().toString(); //防止操作的事務發生異常造成堵塞 最後都會釋放鎖 不影響別人操作 Object redis = null; try { //得到鎖 5秒失效期 boolean lock = Lock.getInstance(RedisEnum.SHOP_CLOCK, id, 5).getLock(); if (lock == false) { return ResultCodeEnum.LOCK_FAIL.getMsg(); } //要操作的事務(舉例) iRedisService.setValue("redis", id); redis = iRedisService.getValue("redis"); } catch (Exception e) { e.printStackTrace(); } finally { //釋放鎖 萬一超時 鎖自己沒了 不能誤刪別人的正在進行的鎖 Lock.getInstance(RedisEnum.SHOP_CLOCK, id).releaseLock(); } if(redis==null){ return ResultCodeEnum.MESSAGE_FAIL.getMsg(); } return redis.toString(); } }
這裡對分散式鎖的考慮是比較多的,需要注意很多事項。
1 異常的捕捉
2 快取的時間
3 鎖的釋放
這裡我還加了返回的列舉,ResultCodeEnum 也做了標準化。
package com.zhouqiang.demo.enmu; import com.zhouqiang.demo.entity.Result; import lombok.Getter; import javax.ws.rs.GET; /** * @author :zhouqiang * @date :2021/8/12 10:34 * @description: * @version: $ */ @Getter public enum ResultCodeEnum { LOCK_SUCCESS(0, "獲取鎖成功!"), LOCK_FAIL(-1, "獲取鎖失敗!"), MESSAGE_FAIL(-1, "訊息失敗!"), ; private int code; private String msg; ResultCodeEnum(int code, String msg) { this.code = code; this.msg = msg; } }
5 測試
最後寫一個介面測試。這裡的返回體用了result封裝。
封裝了這些,是為了標準化一點。也為了以後好維護。
到此,Redis 分散式鎖的就這樣了。
BUT redsi 自己封裝的一個redisson,更加的好用。
6 redission分散式鎖
Redisson 支援單點模式、主從模式、哨兵模式、叢集模式 非常的強大啊。
redisson這個框架重度依賴了Lua指令碼和Netty,程式碼很牛逼,各種Future及FutureListener的非同步、同步操作轉換
實現起來也是非常的容易。
引依賴
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.4.3</version> </dependency>
在業務層只需要幾步,
這樣就省略了我們1-4 所有的操作。簡直就是淚目。
至於redison 有空再去看看原始碼。到此結束!