基於redisson的分散式鎖的簡單註解實現
阿新 • • 發佈:2019-01-07
Redisson依賴:
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>2.2.13</version>
</dependency>
網上關於redis分散式鎖實現的文章很多,本文也參考了很多網上的程式碼,不過我做的是再封一層,利用AOP與註解實現註解形式的分散式鎖,首先定義一個util類,
public classRedisUtils { private static Logger logger= LoggerFactory.getLogger(RedisUtils.class); private static RedisUtils redisUtils; private static RedissonClient redissonClient; private RedisUtils(){} /** * 提供單例模式 * @return */ public static RedisUtils getInstance(){ if(redisUtils==null) synchronized (RedisUtils.class) { if(redisUtils==null) redisUtils=new RedisUtils(); } return redisUtils; } /** * 使用config建立Redisson * Redisson是用於連線Redis Server的基礎類 * @param config * @return */ public staticRedissonClient getRedisson(Config config){ RedissonClient redisson= Redisson.create(config); logger.info("成功連線Redis Server"); return redisson; } /** * 使用ip地址和埠建立Redisson * @param ip * @param port * @return */ public static RedissonClient getRedisson(String ip,String port){ Config config=new Config(); config.useSingleServer().setAddress(ip+":"+port); RedissonClient redisson=Redisson.create(config); logger.info("成功連線Redis Server"+"\t"+"連線"+ip+":"+port+"伺服器"); return redisson; } /** * 關閉Redisson客戶端連線 * @param redisson */ public static void closeRedisson(RedissonClient redisson){ redisson.shutdown(); logger.info("成功關閉Redis Client連線"); } /** * 獲取字串物件 * @param redisson * @param objectName * @return */ public static <T> RBucket<T> getRBucket(RedissonClient redisson,String objectName){ RBucket<T> bucket=redisson.getBucket(objectName); return bucket; } /** * 獲取Map物件 * @param redisson * @param objectName * @return */ public static <K,V> RMap<K, V> getRMap(RedissonClient redisson,String objectName){ RMap<K, V> map=redisson.getMap(objectName); return map; } /** * 獲取有序集合 * @param redisson * @param objectName * @return */ public static <V> RSortedSet<V> getRSortedSet(RedissonClient redisson,String objectName){ RSortedSet<V> sortedSet=redisson.getSortedSet(objectName); return sortedSet; } /** * 獲取集合 * @param redisson * @param objectName * @return */ public static <V> RSet<V> getRSet(RedissonClient redisson,String objectName){ RSet<V> rSet=redisson.getSet(objectName); return rSet; } /** * 獲取列表 * @param redisson * @param objectName * @return */ public static <V> RList<V> getRList(RedissonClient redisson,String objectName){ RList<V> rList=redisson.getList(objectName); return rList; } /** * 獲取佇列 * @param redisson * @param objectName * @return */ public <V> RQueue<V> getRQueue(RedissonClient redisson,String objectName){ RQueue<V> rQueue=redisson.getQueue(objectName); return rQueue; } /** * 獲取雙端佇列 * @param redisson * @param objectName * @return */ public static <V> RDeque<V> getRDeque(RedissonClient redisson,String objectName){ RDeque<V> rDeque=redisson.getDeque(objectName); return rDeque; } /** * 此方法不可用在Redisson 1.2 中 * 在1.2.2版本中可用 * @param redisson * @param objectName * @return */ public static <V> RBlockingQueue<V> getRBlockingQueue(RedissonClient redisson,String objectName){ RBlockingQueue rb=redisson.getBlockingQueue(objectName); return rb; } /** * 獲取鎖 * @param redisson * @param objectName * @return */ public static RLock getRLock(RedissonClient redisson,String objectName){ RLock rLock=redisson.getLock(objectName); return rLock; } /** * 獲取原子數 * @param redisson * @param objectName * @return */ public static RAtomicLong getRAtomicLong(RedissonClient redisson,String objectName){ RAtomicLong rAtomicLong=redisson.getAtomicLong(objectName); return rAtomicLong; } /** * 獲取記數鎖 * @param redisson * @param objectName * @return */ public static RCountDownLatch getRCountDownLatch(RedissonClient redisson,String objectName){ RCountDownLatch rCountDownLatch=redisson.getCountDownLatch(objectName); return rCountDownLatch; } /** * 獲取訊息的Topic * @param redisson * @param objectName * @return */ public static <M> RTopic<M> getRTopic(RedissonClient redisson,String objectName){ RTopic<M> rTopic=redisson.getTopic(objectName); return rTopic; } /** * 獲取包括方法引數上的key * redis key的拼寫規則為 "DistRedisLock+" + lockKey + @DistRedisLockKey<br/> * * @param point * @param lockKey * @return */ public static String getLockKey(ProceedingJoinPoint point, String lockKey) { try { lockKey = "DistRedisLock:" + lockKey; Object[] args = point.getArgs(); if (args != null && args.length > 0) { MethodSignature methodSignature = (MethodSignature)point.getSignature(); Annotation[][] parameterAnnotations = methodSignature.getMethod().getParameterAnnotations(); SortedMap<Integer, String> keys = new TreeMap<>(); for (int i = 0; i < parameterAnnotations.length; i ++) { RedisLockKey redisLockKey = getAnnotation(RedisLockKey.class, parameterAnnotations[i]); if (redisLockKey != null) { Object arg = args[i]; if (arg != null) { keys.put(redisLockKey.order(), arg.toString()); } } } if (keys != null && keys.size() > 0){ for (String key : keys.values()) { lockKey += key; } } } return lockKey; } catch (Exception e) { logger.error("getLockKey error.", e); } return null; } /** * 獲取註解型別 * @param annotationClass * @param annotations * @param <T> * @return */ private static <T extends Annotation> T getAnnotation(final Class<T> annotationClass, final Annotation[] annotations) { if (annotations != null && annotations.length > 0) { for (final Annotation annotation : annotations) { if (annotationClass.equals(annotation.annotationType())) { return (T) annotation; } } } return null; } public static RedissonClient createClient(String address , String pass) { if(redissonClient == null) { synchronized (RedisUtils.class) { if(redissonClient == null) { Config config = new Config(); SingleServerConfig singleSerververConfig = config.useSingleServer(); singleSerververConfig.setAddress(address); singleSerververConfig.setPassword(pass); redissonClient = RedisUtils.getInstance().getRedisson(config); } } } return redissonClient; } }
這個類主要做的是創建於redis的連線與獲取註解的相關資訊,裡面關於redisson的方法是參考的網上其他文章的,很多方法並沒有用上,不過大家可以參考一下自己組合使用。接下來定義兩個註解,一個方法級別的,一個引數級別的,方法級別的作用是標記需要加鎖的方法,引數級別的是標記鎖的key,
方法級別:
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface RedisLock { /** * 鎖的key * 如果想新增非固定鎖,可以在引數上新增@P4jSynKey註解,但是本引數是必寫選項<br/> * redis key的拼寫規則為 "DistRedisLock+" + lockKey + @RedisLOckKey<br/> */ String lockKey(); /** * 持鎖時間 * 單位毫秒,預設5秒<br/> */ long keepMills() default 5 * 1000; /** * 沒有獲取到鎖時,等待時間 * @return */ long maxSleepMills() default 120 * 1000; }
引數級別:
@Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface RedisLockKey { /** * key的拼接順序規則 */ int order() default 0; }
接下來就是AOP的實現了,
@Component @Aspect public class RedisLockAspect { private static Logger logger= LoggerFactory.getLogger(RedisLockAspect.class); @Value("${spring.redis.host}:${spring.redis.port}") String address;@Around("execution(* exercise..*(..))&& @annotation(RedisLock)") public Object lock(ProceedingJoinPoint point) throws Throwable { RLock lock = null; Object object = null; logger.info("into Aspect!"); try { RedisLock redisLock = getDistRedisLockInfo(point); RedisUtils redisUtils = RedisUtils.getInstance(); RedissonClient redissonClient = RedisUtils.createClient(address, null); String lockKey = redisUtils.getLockKey(point, redisLock.lockKey()); lock = redisUtils.getRLock(redissonClient, lockKey); if (lock != null) { Boolean status = lock.tryLock(redisLock.maxSleepMills(), redisLock.keepMills(), TimeUnit.MILLISECONDS); if (status) { object = point.proceed(); } } } finally { if (lock != null) { lock.unlock(); } } return object; } private RedisLock getDistRedisLockInfo(ProceedingJoinPoint point) { try { MethodSignature methodSignature = (MethodSignature) point.getSignature(); Method method = methodSignature.getMethod(); return method.getAnnotation(RedisLock.class); } catch (Exception e) { logger.info(e.getMessage()); } return null; } }
定義一個測試類:
@Component public class LockTest { private static int i = 0; @RedisLock(lockKey = "lockKey") public void add(@RedisLockKey(order = 1) String key, @RedisLockKey(order = 0) int key1) { i++; System.out.println("i=***************************************" + i); } }
用junit寫個測試啟用100個執行緒呼叫測試方法:
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest public class AOPTest { @Autowired LockTest lockTest; @Test public void testDistLockAop() throws InterruptedException { for (int i = 0; i < 100; i++) { new Thread(() -> { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } lockTest.add("***********testDistLockAop", 99); }).start(); } TimeUnit.SECONDS.sleep(20); } @Test public void testDistLock() throws InterruptedException { lockTest.add("============testDistLock", 111111); TimeUnit.SECONDS.sleep(10); } }
執行結果:
perfect! 一個簡單的redis分散式鎖已經完成了,整個專案是一個spring-boot的maven工程。另外因為我用的都是本地的資料庫與redis,所以程式碼中連線redis的地方密碼傳的是null,有密碼的需要換成密碼。
個人習慣,喜歡看程式碼不喜歡看文字,所以解釋的不夠詳細,但主要就三點,redisson的使用,AOP原理,註解實現,稍微研究一下這三塊,以上程式碼還是很容易看懂的,程式碼的改進的地方還很多,鎖的功能並不全,可重入,併發連線數量等還沒有完善。只要仔細研究一下redisson這些都能找到。