Redis分布式锁 基于GETSET SETNX REDISSON 的实现

Redis分布式锁的应用html

  • 有些场景须要加锁处理,好比:秒杀,全局递增ID,楼层生成等等。大部分的解决方案是基于DB实现的,Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的链接并不存在竞争关系。其次Redis提供一些命令SETNX,GETSET,能够方便实现分布式锁机制。

GETSET方式java

 

http://doc.redisfans.com/string/getset.htmlredis

public final class RedisLockUtil {

    private static final int defaultExpire = 60;
 
    /**
     * 加锁
     * @param key redis key
     * @param expire 过时时间,单位秒
     * @return true:加锁成功,false,加锁失败
     */
    public static boolean lock(String key, int expire) {

        RedisService redisService = SpringUtils.getBean(RedisService.class);
        long status = redisService.setnx(key, "1");
        //若是状态等于一 则 成功 返回值成功 
        if(status == 1) {
            redisService.expire(key, expire);
            return true;
        }
        //不然设置失败
        return false;
    }

    public static boolean lock(String key) {
        return lock2(key, defaultExpire);
    }

    /**
     * 加锁
     * @param key redis key
     * @param expire 过时时间,单位秒
     * @return true:加锁成功,false,加锁失败
     */
    public static boolean lock2(String key, int expire) {

        RedisService redisService = SpringUtils.getBean(RedisService.class);

        long value = System.currentTimeMillis() + expire;//当时时间加过时时间
        long status = redisService.setnx(key, String.valueOf(value));//尝试存一下

        if(status == 1) {
            //若是能够直接存进去就 成功了 获取了锁 
            return true;
        }
        //若是没有设置成功 的后获取锁的value 
        long oldExpireTime = Long.parseLong(redisService.get(key, "0"));
        if(oldExpireTime < System.currentTimeMillis()) {
            //超时
            long newExpireTime = System.currentTimeMillis() + expire;
            //用getset覆盖 (getset理解为先get,再set。get的是旧的值,set的是新的值。)
            long currentExpireTime = Long.parseLong(redisService.getSet(key, String.valueOf(newExpireTime)));
            if(currentExpireTime == oldExpireTime) {
                return true;
            }
        }
        return false;
    }

    public static void unLock1(String key) {
        RedisService redisService = SpringUtils.getBean(RedisService.class);
        redisService.del(key);
    }

    public static void unLock2(String key) {    
        RedisService redisService = SpringUtils.getBean(RedisService.class);    
        long oldExpireTime = Long.parseLong(redisService.get(key, "0"));   
        //oldExpireTime = 原当前时间 + 过时时间 其实不大于当前时间 都已通过期了 
        if(oldExpireTime > System.currentTimeMillis()) {        
            redisService.del(key);    
        }
   }

}

 

在实际使用总大可能是注解,获取切面实现。并发

SETNX方式分布式

http://redisdoc.com/string/setnx.htmlide

如下是http://blog.csdn.net/u010359884/article/details/50310387 实现方式,通过使用和一些业务改造。this

public class CacheLockInterceptor implements InvocationHandler{
    public static int ERROR_COUNT  = 0;
    private Object proxied;

    public CacheLockInterceptor(Object proxied) {
        this.proxied = proxied;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        CacheLock cacheLock = method.getAnnotation(CacheLock.class);
        //没有cacheLock注解,pass
        if(null == cacheLock){
            System.out.println("no cacheLock annotation");          
            return method.invoke(proxied, args);
        }
        //得到方法中参数的注解
        Annotation[][] annotations = method.getParameterAnnotations();
        //根据获取到的参数注解和参数列表得到加锁的参数
        Object lockedObject = getLockedObject(annotations,args);
        String objectValue = lockedObject.toString();
        //新建一个锁
        RedisLock lock = new RedisLock(cacheLock.lockedPrefix(), objectValue);
        //加锁
        boolean result = lock.lock(cacheLock.timeOut(), cacheLock.expireTime());
        if(!result){//取锁失败
            ERROR_COUNT += 1;
            throw new CacheLockException("get lock fail");

        }
        try{
            //加锁成功,执行方法
            return method.invoke(proxied, args);
        }finally{
            lock.unlock();//释放锁
        }

    }
    /**
     * 
     * @param annotations
     * @param args
     * @return
     * @throws CacheLockException
     */
    private Object getLockedObject(Annotation[][] annotations,Object[] args) throws CacheLockException{
        if(null == args || args.length == 0){
            throw new CacheLockException("方法参数为空,没有被锁定的对象");
        }

        if(null == annotations || annotations.length == 0){
            throw new CacheLockException("没有被注解的参数");
        }
        //不支持多个参数加锁,只支持第一个注解为lockedObject或者lockedComplexObject的参数
        int index = -1;//标记参数的位置指针
        for(int i = 0;i < annotations.length;i++){
            for(int j = 0;j < annotations[i].length;j++){
                if(annotations[i][j] instanceof LockedComplexObject){//注解为LockedComplexObject
                    index = i;
                    try {
                        return args[i].getClass().getField(((LockedComplexObject)annotations[i][j]).field());
                    } catch (NoSuchFieldException | SecurityException e) {
                        throw new CacheLockException("注解对象中没有该属性" + ((LockedComplexObject)annotations[i][j]).field());
                    }
                }

                if(annotations[i][j] instanceof LockedObject){
                    index = i;
                    break;
                }
            }
            //找到第一个后直接break,不支持多参数加锁
            if(index != -1){
                break;
            }
        }

        if(index == -1){
            throw new CacheLockException("请指定被锁定参数");
        }

        return args[index];
    }
}

 

/**
     * 加锁
     * 使用方式为:
     * lock();
     * try{
     *    executeMethod();
     * }finally{
     *   unlock();
     * }
     * @param timeout timeout的时间范围内轮询锁
     * @param expire 设置锁超时时间
     * @return 成功 or 失败
     */
    public boolean lock(long timeout,int expire){
        long nanoTime = System.nanoTime();
        timeout *= MILLI_NANO_TIME;
        try {
            //在timeout的时间范围内不断轮询锁
            while (System.nanoTime() - nanoTime < timeout) {
                //锁不存在的话,设置锁并设置锁过时时间,即加锁
                if (this.redisClient.setnx(this.key, LOCKED) == 1) {
                    this.redisClient.expire(key, expire);//设置锁过时时间是为了在没有释放
                    //锁的状况下锁过时后消失,不会形成永久阻塞
                    this.lock = true;
                    return this.lock;
                }
                System.out.println("出现锁等待");
                //短暂休眠,避免可能的活锁
                Thread.sleep(3, RANDOM.nextInt(30));
            } 
        } catch (Exception e) {
            throw new RuntimeException("locking error",e);
        }
        return false;
    }

    public  void unlock() {
        try {
            if(this.lock){
                redisClient.delKey(key);//直接删除
            }
        } catch (Throwable e) {

        }
    }

这也是你们经常使用的方式,可是这种方式的实现存在锁过时释放时,被正确释放。推荐GETSET。.net

 

https://www.cnblogs.com/yjf512/archive/2017/03/22/6597814.html线程

http://blog.csdn.net/u010648555/article/details/70139541指针

若是长时间获取不到,就会获取锁失败,至关于没加锁!

这里还有可能发生其余问题:

(1)并发状况,expire主动释放锁的时候,可能释放的是别人的锁;

好比,这个锁我上了10s,可是我处理的时间比10s更长,到了10s,这个锁自动过时了,被别人取走了,而且对它从新上锁了。那么这个时候,我再调用Redis::del就是删除别人创建的锁了。

(2)Redis服务挂掉,锁失败,至关于没加锁!最好使用主从+哨兵提升 高可用。集群。

 

 

Redis实现分布式锁全局锁—Redis客户端Redisson中分布式锁RLock实现

https://my.oschina.net/haogrgr/blog/469439

相关文章
相关标签/搜索