1. 程式人生 > >基於redisson的分散式鎖的簡單註解實現

基於redisson的分散式鎖的簡單註解實現

Redisson依賴:

<!--redisson-->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>2.2.13</version>
</dependency>

網上關於redis分散式鎖實現的文章很多,本文也參考了很多網上的程式碼,不過我做的是再封一層,利用AOP與註解實現註解形式的分散式鎖,首先定義一個util類,

public class 
RedisUtils {     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 static
RedissonClient 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這些都能找到。