干货,springboot自定义注解实现分布式锁详解
生活随笔
收集整理的這篇文章主要介紹了
干货,springboot自定义注解实现分布式锁详解
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
背景
在互聯網的很多場景下,會產生資源競爭,如果是單機環境,簡單加個鎖就能解決問題;但是在集群環境下(分布式環境),多個客戶端在一個很短的時間內競爭同一服務端資源(如搶購場景),或者同一客戶端重復提交請求,如果請求不具備冪等性,就需要用到分布式鎖的解決方案。
背景知識
關于分布式鎖,可以看看我之前的文章《基于Spring boot 2.1 使用redisson實現分布式鎖》,當時只是利用redisson的API實現了功能,現在,我們要利用自定義注解方式,讓分布鎖功能更方便使用。關于自定義注解,請移步《深入理解Java:注解(Annotation)自定義注解入門》
解決方案
1、引入redis和redisson的依賴
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><exclusions><exclusion><groupId>redis.clients</groupId><artifactId>jedis</artifactId></exclusion><exclusion><groupId>io.lettuce</groupId><artifactId>lettuce-core</artifactId></exclusion></exclusions> </dependency> <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId> </dependency> <!--spring2.X集成redis所需common-pool2,使用jedis必須依賴它 --> <dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId> </dependency> <dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.10.5</version> </dependency>2、接口RedissonLocker.java,定義分布式鎖的一些常用API
public interface RedissonLocker {RLock lock(String lockKey);RLock lock(String lockKey, int timeout);RLock lock(String lockKey, TimeUnit unit, int timeout);boolean tryLock(String lockKey, TimeUnit unit, LockConstant lockTime);boolean fairLock(String lockKey, TimeUnit unit, LockConstant lockTime);void unlock(String lockKey);void unlock(RLock lock);}3、實現類RedissonLockerImpl.java,接口RedissonLocker的實現類
/*** 基于Redisson實現分布式鎖* * @author * 2019年4月3日* * {@link https://github.com/redisson/redisson/wiki}**/ @Component public class RedissonLockerImpl implements RedissonLocker {@Autowiredprivate RedissonClient redissonClient;@Autowiredprivate RedisTemplate<Object, Object> redisTemplate;/*** key 值是否存在* * @param key* @return*/public boolean existKey(String key) {return redisTemplate.hasKey(key);}/************************** 可重入鎖 **************************//*** 拿不到lock就不罷休,不然線程就一直block 沒有超時時間,默認30s* * @param lockKey* @return*/@Overridepublic RLock lock(String lockKey) {RLock lock = redissonClient.getLock(lockKey);lock.lock();return lock;}/*** 自己設置超時時間* * @param lockKey 鎖的key* @param timeout 秒 如果是-1,直到自己解鎖,否則不會自動解鎖* @return*/@Overridepublic RLock lock(String lockKey, int timeout) {RLock lock = redissonClient.getLock(lockKey);lock.lock(timeout, TimeUnit.SECONDS);return lock;}/*** 自己設置超時時間* * @param lockKey 鎖的key* @param unit 鎖時間單位* @param timeout 超時時間* */@Overridepublic RLock lock(String lockKey, TimeUnit unit, int timeout) {RLock lock = redissonClient.getLock(lockKey);lock.lock(timeout, unit);return lock;}/*** 嘗試加鎖,最多等待waitTime,上鎖以后leaseTime自動解鎖* * @param lockKey 鎖key* @param unit 鎖時間單位* @param waitTime 等到最大時間,強制獲取鎖* @param leaseTime 鎖失效時間* @return 如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他線程獲取),則返回false*/@Overridepublic boolean tryLock(String lockKey, TimeUnit unit, LockConstant lockTime) {RLock lock = redissonClient.getLock(lockKey);try {boolean existKey = existKey(lockKey);if (existKey) {// 已經存在了,就直接返回return false;}return lock.tryLock(lockTime.getWaitTime(), lockTime.getLeaseTime(), unit);} catch (InterruptedException e) {e.printStackTrace();}return false;}/************************** 公平鎖 **************************//*** 嘗試加鎖,最多等待waitTime,上鎖以后leaseTime自動解鎖* * @param lockKey 鎖key* @param unit 鎖時間單位* @param waitTime 等到最大時間,強制獲取鎖* @param leaseTime 鎖失效時間* @return 如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他線程獲取),則返回false*/public boolean fairLock(String lockKey, TimeUnit unit, LockConstant lockTime) {RLock fairLock = redissonClient.getFairLock(lockKey);try {boolean existKey = existKey(lockKey);if (existKey) {// 已經存在了,就直接返回return false;}return fairLock.tryLock(lockTime.getWaitTime(), lockTime.getLeaseTime(), unit);} catch (InterruptedException e) {e.printStackTrace();}return false;}/*** 嘗試加鎖,最多等待waitTime,上鎖以后leaseTime自動解鎖* * @param lockKey 鎖key* @param unit 鎖時間單位* @param waitTime 等到最大時間,強制獲取鎖,默認是三秒鐘* @param leaseTime 鎖失效時間* @return 如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他線程獲取),則返回false*/public boolean fairLock(String lockKey, TimeUnit unit, int leaseTime) {RLock fairLock = redissonClient.getFairLock(lockKey);try {boolean existKey = existKey(lockKey);if (existKey) {// 已經存在了,就直接返回return false;}return fairLock.tryLock(3, leaseTime, unit);} catch (InterruptedException e) {e.printStackTrace();}return false;}/*** 釋放鎖* * @param lockKey 鎖key*/@Overridepublic void unlock(String lockKey) {try {RLock lock = redissonClient.getLock(lockKey);lock.unlock();} catch (Exception e) {}}/*** 釋放鎖*/@Overridepublic void unlock(RLock lock) {try {lock.unlock();} catch (Exception e) {}}}4、自定義注解 RlockRepeatSubmit
/*** 防止重復提交的注解* * @author * 2019年6月18日**/ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD }) @Documented public @interface RlockRepeatSubmit {/*** 分布式鎖枚舉類* @return*/LockConstant lockConstant();}5、枚舉LockConstant.java,主要用于使用注解時傳參,來區分不同的業務類型
/*** 分布式鎖枚舉類**/ public enum LockConstant {CASHIER("cashierLock:", 3, 300, "請勿重復點擊收銀操作!!"), // 收銀鎖SUBMIT_ORDER("submitOrderLock:", 3, 30, "請勿重復點擊下單!!"), // 下單鎖......COMMON_LOCK("commonLock:", 3, 120, "請勿重復點擊");// 通用鎖常量private String keyPrefix; // 分布式鎖前綴private int waitTime;// 等到最大時間,強制獲取鎖private int leaseTime;// 鎖失效時間private String message;// 加鎖提示// 構造方法private LockConstant(String keyPrefix, int waitTime, int leaseTime, String message) {this.keyPrefix = keyPrefix;this.waitTime = waitTime;this.leaseTime = leaseTime;this.message = message;}// 省略getter,setter}6、RlockRepeatSubmitAspect.java,定義AOP類,解析自定義注解RlockRepeatSubmit,進行分布式鎖操作
/*** * 防止重復提交分布式鎖攔截器* * @author * 2019年6月18日**/ @Aspect @Component public class RlockRepeatSubmitAspect {@Resourceprivate RedissonLockerImpl redissonLocker;/**** 定義controller切入點攔截規則,攔截RlockRepeatSubmit注解的業務方法*/@Pointcut("@annotation(xx.xxx.RlockRepeatSubmit)")public void pointCut() {}/*** AOP分布式鎖攔截* * @param request* @param response* @param handler* @return* @throws Exception*/@Around("pointCut()")public Object rlockRepeatSubmit(ProceedingJoinPoint joinPoint) throws Throwable {// 獲取類里面的方法MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();Method targetMethod = methodSignature.getMethod();RlockRepeatSubmit repeatSubmit = targetMethod.getAnnotation(RlockRepeatSubmit.class);// 如果添加了RlockRepeatSubmit這個注解,需要添加分布式鎖if (Objects.nonNull(repeatSubmit)) {// 獲取參數Object[] args = joinPoint.getArgs();// 進行一些參數的處理,比如獲取訂單號,操作人id等......HttpServletRequest request = getRequest();if (null != request && request.getParameterMap().containsKey("accessToken")) {StringBuffer lockKeyBuffer = new StringBuffer();LockConstant lockConstant = repeatSubmit.lockConstant();lockKeyBuffer.append(lockConstant.getKeyPrefix());String accessToken = request.getParameterMap().get("accessToken")[0];// 當前用戶的tokenString path = request.getServletPath();// 使用用戶的token和請求路徑作為唯一的標識,如果有orderNo,加上orderNo作為唯一標識lockKeyBuffer.append("RlockRepeat");String token = accessToken + "." + path;if (null != orderNo) {lockKeyBuffer.append(token.hashCode());lockKeyBuffer.append(".");lockKeyBuffer.append(orderNo);} else {lockKeyBuffer.append(token);}// 公平加鎖,lockTime后鎖自動釋放boolean isLocked = false;try {isLocked = redissonLocker.fairLock(lockKeyBuffer.toString(), TimeUnit.SECONDS, lockConstant);if (isLocked) { // 如果成功獲取到鎖就繼續執行// 執行進程return joinPoint.proceed();} else { // 未獲取到鎖//異常處理......}} catch (Exception e) {//異常處理......} finally {if (isLocked) { // 如果鎖還存在,在方法執行完成后,釋放鎖redissonLocker.unlock(lockKeyBuffer.toString());}}}}return joinPoint.proceed();}public static HttpServletRequest getRequest() {ServletRequestAttributes ra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();return ra.getRequest();}}7、在controller層的業務方法使用自定義注解
@RestController @Slf4j @RequestMapping("/order") public class OrderController {/*** 業務方法** @param requestForm* @return* @throws Throwable*/@RlockRepeatSubmit(lockConstant = LockConstant.SUBMIT_ORDER)@RequestMapping(value = "/xxx", method = RequestMethod.POST)public String submitOrder(Order order) throws Throwable {//業務方法......}}OK,大功告成,以后如果哪個業務方法需要加分布式鎖,直接在方法上加上自定義注解,并在枚舉里定義相關屬性即可,是不是很簡單?
PS:關于怎么利用JMeter模擬并發請求測試業務方法和分布式鎖,下次再講。
總結
以上是生活随笔為你收集整理的干货,springboot自定义注解实现分布式锁详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 面试官:聊聊你对分布式锁技术方案的理解
- 下一篇: 通过女票的淘宝历程,大白话讲解大数据各个