Redis之锁

2017-08-12 20:16:59来源:CSDN作者:JavaMoo人点击

分享

WATCH命令的性能问题

WATCH命令在更新数据之前会监视要操作的键,如果提交数据前检测到目标键已经被更新过,那么会放弃更新数据,并且重新尝试重新执行上述的操作。如果在并发量大的情况下,系统完成一次更新操作尝试的数目会非常多,用户等待的时间也会变长,所以WATCH,MULTI,EXEC组成的事务不具备可扩展性,所以我们使锁来解决这一问题

redis锁

锁虽然可以解决上述的问题,但是构建正确的锁并不那么容易,大部分redis实现的锁只是基本正确,锁发生故障的时间和方通常难以预料。以下是一些导致锁出现不正确的原因,以及锁在不正确运行时的症状

  1. 持有锁的进程因为操作时间过长导致锁被自动释放,但是进程本身并不知晓这一点,甚至可能释放掉其它进程持有的锁
  2. 一个持有锁的进程崩溃,但其它想要获取锁的进程只能白白浪费等待时间等待锁被释放
  3. 在一个进程持有的锁过期后,其他多个进程同时尝试获取锁,并且都拿到了锁
  4. 一个持有锁线程运行时间过长,导致锁超时而被自动释放,但是线程并不知道,于此同时其它线程同时获取到了这个锁,并都认为自己是获得这个锁的唯一进程

简易锁

//加锁public String acquireLock(Jedis conn, String lockName, long acquireTimeout){        String identifier = UUID.randomUUID().toString();        long end = System.currentTimeMillis() + acquireTimeout;        //如果在指定的时间内没有获取到锁则放弃获取锁        while (System.currentTimeMillis() < end){            if (conn.setnx("lock:" + lockName, identifier) == 1){                return identifier;            }            try {                Thread.sleep(1);            }catch(InterruptedException ie){                Thread.currentThread().interrupt();            }        }        return null;    }//释放锁public boolean releaseLock(Jedis conn, String lockName, String identifier) {        String lockKey = "lock:" + lockName;        while (true){            conn.watch(lockKey);            //释放锁前必须谨慎的判断加锁时的键值和现在的键值是否相同            if (identifier.equals(conn.get(lockKey))){                Transaction trans = conn.multi();                trans.del(lockKey);                List<Object> results = trans.exec();                if (results == null){                    continue;                }                return true;            }            conn.unwatch();            break;        }        return false;    }

细粒度锁

简易锁与WATCH命令都存在一个问题,粒度太大,比如用一个sorted set代表一个市场,分值代表价格,值代表商品,如果要进行某个商品交易会锁住整个市场,实际上锁住一个商品会减小锁竞争同时提升程序的性能

带有超时限制的锁

之前所提到的锁在持有者崩溃的时候不会被释放,为了解决这一问题我们将为锁设置超时时间,为了确保锁在客户端已经崩溃的情况下能够释放,程序会保证在获取锁失败的情况下,检查锁的超时时间,并为未设置超时时间的锁设置超时时间

public String acquireLockWithTimeOut(Jedis conn,String lockName,long acquireTimeOut,long lockTimeOut){        String identifier=UUID.randomUUID().toString();        String lockKey="lock:"+lockName;        int lockExpire=(int)(lockTimeOut/1000);        long end=System.currentTimeMillis()+acquireTimeOut;        while (System.currentTimeMillis()<end) {            //如果成功的拿到锁,设置锁的有效时间,并返回锁            if (conn.setnx(lockKey, identifier)==1) {                conn.expire(lockKey, lockExpire);                return identifier;            }            //如果没有拿到锁,检查锁是否设置了超时时间,如果没有那么设置锁的时间            if (conn.ttl(lockKey)==-1) {                conn.expire(lockKey, lockExpire);            }            try {                TimeUnit.MILLISECONDS.sleep(1);            } catch (InterruptedException e) {                e.printStackTrace();            }        }        return null;    }

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台