redis分布式锁的实现

 在集群系统中,有些资源的调用需要加锁,由于服务是有多个资源对外提供的,资源之间的锁使用JAVA的锁是不可行的,所以需要使用其他的方式来实现加锁,比如订单与库存的锁。

   redis是基于内存的key-value的NOSQL数据库,效率高,单机并发可达1K左右,其中setnx可以很方便实现并发锁机制

下面是基于SpringBoot以及redis实现的分布式锁的工具类,可以在需要加锁的地方加上注解即可灵活使用

/**
 * 该注解(@MethodLock)可以用在方法 上表示给发放加分布式锁
 * 与
 * @author Administrator
 *
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LockMethod {
    /**
     * 锁的key
     * 本参数是必写选项<br/>
     */
    String preKey() default "redisLock";
      /**
     * 持锁时间,超时时间,持锁超过此时间自动丢弃锁<br/>
     * 单位毫秒,默认20秒<br/>
     * 如果为0表示永远不释放锁,知道过程执行完自动释放锁,
     * 在设置为0的情况下toWait为true是没有意义的<br/>
     * 但是没有比较强的业务要求下,不建议设置为0
     */  
    int expireTime() default 20 ;
    /**
     * 当获取锁失败,是继续等待还是放弃<br/>
     * 默认为继续等待
     */  
    boolean toWait() default true;  
//    /**
//     * 没有获取到锁的情况下且toWait()为继续等待,睡眠指定毫秒数继续获取锁,也就是轮训获取锁的时间<br/>
//     * 默认为10毫秒
//     *  
//     * @return
//     */  
//    long sleepMills() default 10;  
    /**
     * 锁获取超时时间:<br/>
     * 没有获取到锁的情况下且toWait()为true继续等待,最大等待时间,如果超时抛出
     * {@link java.util.concurrent.TimeoutException.TimeoutException}
     * ,可捕获此异常做相应业务处理;<br/>
     * 单位毫秒,默认3秒钟,如果设置为0即为没有超时时间,一直获取下去;
     *  
     * @return
     */  
    long maxSleepMills() default 3 * 1000;  

}





@Target({ ElementType.PARAMETER })  
@Retention(RetentionPolicy.RUNTIME)  
@Documented
public @interface LockParameter {
    /**
     * 含有成员变量的复杂对象中需要加锁的成员变量,如一个商品对象的商品ID
     * 也就是复杂对象中的属性名称
     * 比如User user 即为复杂对象  使用对象中的属性 username来作为sunKey
     * isObject=false 时候该功能才起作用
     * @return
     */
    String field() default "";
    /**
     * 是否是基本的简单属性  true表示是简单属性,false 表示是复杂对象
     * true:表示 int,long,String,char 等基本数据类型
     * false:表示对象模型 比如User
     * @return
     */
    boolean isSimple() default true;
}



@Aspect
@Component
public class RedisLockAop {
    
//    @Before("@annotation(CacheLock)")
//     public void requestLimit(JoinPoint point, CacheLock cacheLock)throws Throwable{
//        
//    }
    private static Logger logger = LoggerFactory.getLogger(RedisLockAop.class);
    @Autowired
    private SpringJedisUtilInterface redisUtil;
//    @Autowired
//    private RedisUtil redisUtil;
    /**
     * 通过前置拦截,拦截所有经过CacheLock注解过的方法
     * @param point
     * @param CacheLock
     * @throws Throwable
     */
    @Around("@annotation(LockMethod)")
    public Object requestLimit(ProceedingJoinPoint  joinPoint) throws Throwable{
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        LockMethod methodLock = method.getAnnotation(LockMethod.class);
        if(methodLock ==null){
             throw new RedisLockException("配置参数错误");  
        }
        Object[] args  = joinPoint.getArgs();
         // 得到被代理的方法
        //获得方法中参数的注解
        Annotation[][] annotations = method.getParameterAnnotations();
        //根据获取到的参数注解和参数列表获得加锁的参数
        String sunKey = getLockEndKey(annotations,args);
        String key = methodLock.preKey()+"_"+sunKey+"_lock";
        logger.info(key);
        // 超时时间  (最大的等待时间)
        long maxSleepMills =  methodLock.maxSleepMills();
//        获得锁之后最大可持有锁的时间
        int expire = methodLock.expireTime();
//        是否等待
        boolean toWait = methodLock.toWait();
        boolean lock = false;
        Object obj = null;
        int radomInt = 0;// 每个线程随机等待时间
        try {
//             说明没有得到锁,等待轮训的去获得锁
             while (!lock) {
                 lock =getLock(key, expire);
                  // 得到锁,没有人加过相同的锁  
                 if (lock) {
                     logger.info("线程获得锁开始执行"+"---"+redisUtil);
                     obj = joinPoint.proceed();  
                     break;  
                 }else if(toWait){
                     radomInt = new Random().nextInt(99);
                     maxSleepMills =maxSleepMills- radomInt;
                     logger.info("没获得锁,随机等待毫秒数:"+radomInt+"---"+redisUtil);
                     if(maxSleepMills<=0){
                         logger.info("获取锁资源等待超时"+"---"+redisUtil);
                         break;
//                         throw new CacheLockException("获取锁资源等待超时,等待超时");  
                     }
                     TimeUnit.MILLISECONDS.sleep(radomInt);  
                 }else{
                     break;  
                 }
             }
        } catch (Throwable e) {
            e.printStackTrace();
            logger.info(e.getMessage()+"--"+e);
            throw e;
        }finally{
            if(lock){
                logger.info("执行删除操作");
                redisUtil.del(key );//直接删除
            }
        }
        return obj;
    }

    
    
    
    /**
     *  从方法参数中找出@lockedComplexOnbject的参数,在redis中取该参数对应的锁
     * @param annotations
     * @param args
     * @return
     * @throws RedisLockException
     */
    private String getLockEndKey(Annotation[][] annotations,Object[] args) throws RedisLockException{
        if(null == args || args.length == 0){
            throw new RedisLockException("方法参数为空,没有被锁定的对象");
        }
        
        if(null == annotations || annotations.length == 0){
            throw new RedisLockException("没有被注解的参数");
        }
         SortedMap<Integer, String> keys = new TreeMap<Integer, String>();
         String keyString;
        //直接在多个参数上注解
        for(int i = 0;i < annotations.length;i++){
            for(int j = 0;j < annotations[i].length;j++){
                if(annotations[i][j] instanceof Value){
                       Object arg = args[i];
                       logger.info("--Value args[i]--"+i+"------"+arg);
                }
                if(annotations[i][j] instanceof LockParameter){//注解为LockedComplexObject
                    LockParameter lockParameter = (LockParameter)annotations[i][j];
                    Object arg = args[i];
                    logger.info("--lockParameter args[i]--"+i+"------"+arg);
                    try {
                        if(lockParameter.isSimple()){
                            keys.put(i, String.valueOf(arg));
                        }else{
                            keyString = args[i].getClass().getField(lockParameter.field()).toString();
                            keys.put(i,keyString);
                        }
                        
                    } catch (NoSuchFieldException |SecurityException e) {
                        e.printStackTrace();
                        throw new RedisLockException("注解对象中没有该属性"+lockParameter.field());
                    }
                }
            
            }
        }
        String sunKey ="";
        if (keys != null && keys.size() > 0) {  
              for (String key : keys.values()) {  
                  sunKey = sunKey + key;  
              }  
       }  
        return sunKey;
    }
    
     public boolean getLock(String key ,int expire) throws InterruptedException{
         if(redisUtil.setnx(key, String.valueOf(expire))==1){//1插入成功且key不存在,0未插入,key存在
             redisUtil.expired(key, expire);
             return true;
          }
         return false;
     }
}


测试用例


@Service
public class SecKillImpl implements SeckillInterface{
    public static Map<Long, Long> inventory ;
    static{
        inventory = new HashMap<>();
        inventory.put(10000001L, 10000l);
        inventory.put(10000002L, 10000l);
    }
    
    @Override
    @LockMethod(preKey="Seckill",expireTime=1)
    public void secKill(String arg1, @LockParameter Long arg2, int a) {
        //最简单的秒杀,这里仅作为demo示例
        System.out.println("commodityId"+arg2+"  线程几号="+a);
        reduceInventory(arg2);
    }
    //模拟秒杀操作,姑且认为一个秒杀就是将库存减一,实际情景要复杂的多
    public Long reduceInventory(Long commodityId){
        inventory.put(commodityId,inventory.get(commodityId) - 1);
        return inventory.get(commodityId);
    }



@RunWith(SpringRunner.class)
@SpringBootTest(classes = FirstSpringBootApplication.class,
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SecKillTest {
    private static Long commidityId1 = 10000001L;
    private static Long commidityId2 = 10000002L;

     @Autowired
     private SeckillInterface proxy;
    
    @Test
    public void testSecKill(){
        int threadCount = 1000;
        int splitPoint = 500;
        CountDownLatch endCount = new CountDownLatch(threadCount);
        CountDownLatch beginCount = new CountDownLatch(1);
        SecKillImpl testClass = new SecKillImpl();
        
        Thread[] threads = new Thread[threadCount];
        //起500个线程,秒杀第一个商品
        for(int i= 0;i < splitPoint;i++){
            int  a = i;
            threads[i] = new Thread(new  Runnable() {
                public void run() {
                    try {
                        //等待在一个信号量上,挂起
                        beginCount.await();
                        //用动态代理的方式调用secKill方法
                        proxy.secKill("test", commidityId1,a);
                        endCount.countDown();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            });
            threads[i].start();

        }
        
        for(int i= splitPoint;i < threadCount;i++){
            int a =i;
            threads[i] = new Thread(new  Runnable() {
                public void run() {
                    try {
                        //等待在一个信号量上,挂起
                        beginCount.await();
                        //用动态代理的方式调用secKill方法
                        beginCount.await();
                        proxy.secKill("test", commidityId2,a );
                        //testClass.testFunc("test", 10000001L);
                        endCount.countDown();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            });
            threads[i].start();

        }
        
        
        long startTime = System.currentTimeMillis();
        //主线程释放开始信号量,并等待结束信号量
        beginCount.countDown();
        
        try {
            //主线程等待结束信号量
            endCount.await();
            //观察秒杀结果是否正确
            System.out.println(SecKillImpl.inventory.get(commidityId1));
            System.out.println(SecKillImpl.inventory.get(commidityId2));
//            System.out.println("error count" + CacheLockInterceptor.ERROR_COUNT);
            System.out.println("total cost " + (System.currentTimeMillis() - startTime));
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}


以下是项目下载地址:项目中包含了redis并发锁,redis 的session共享  分布式缓存  druid数据库连接池,druid监控等

源码下载地址:https://git.oschina.net/xufan0711/firstSpringboot/tree/master/  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章