Redis之锁

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;
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值