Redis分布式锁

Redis分布式锁

可通过redis中提供的指令setnx(SET if not exists的简写)  key value实现,也可通过mysql的唯一约束来实现,redis的核心部分是单线程运行的,用了setnx命令之后,只能有一个客户端对某一个key设置值,在没有过期或删除key的时候其他客户端是不能设置这个key的,但是redis的setnx不好控制锁的有效时长问题(锁超时问题:一个线程获取到锁之后,线程阻塞了,无法释放锁,其它线程也就拿不到锁了),现在都是使用redis的一个框架redisson来实现分布式锁

redisson框架会根据hash算法从分布式节点中选择一个节点通过执lua脚本进行加锁,当锁住的一个业务还没有执行完成的时候,redisson引入看门狗机制,看门狗watch dog每隔一段时间就会检查当前业务是否还持有锁,如果活没干完锁快过期了就给锁续期,当业务执行完成之后释放锁就可以了

在高并发情况下,一个业务可能会很快执行完成,客户端1持有锁的时候,客户端2访问不会马上拒绝,它会自旋不断尝试获取锁.客户端1释放锁之后,客户端2可以马上抢锁持有锁,性能也得到了提升

并且redisson实现分布式锁支持锁重入(setnx目前不支持,即同一线程多次获取锁的场景,可能会死锁),避免了死锁的产生.重入会在内部判断是否是当前线程持有的锁,如果是就会计数加一,释放锁就会计数减一.存储数据的时候采用hash结构,redis自身外圈的key可以按照自己的业务进行指定,hash结构自身内圈的key是当前线程的唯一标识,value是当前线程重入的次数.

但是redisson实现分布式锁还是会存在主从一致性问题,比如当线程1加锁成功后,master主节点数据会异步复制到slave从节点,但当持有redis的锁master主节点宕机后,根据哨兵机制选举一个slave从节点为新的master主节点,这时出现一个线程2,再次加速,会在新的master主节点上加锁成功,这时候会出现两个节点同时持有一把锁问题.

虽然可以利用redisson提供的红锁来解决这个问题,但是红锁需要在多个节点都添加锁(红锁要求是在多个redis实例上创建锁,并且大多数(数量过半)都成功创建锁),性能很低且运维成本非常高,因此官方也暂时废弃了红锁

Redis分布式锁_第1张图片

非要做到数据强一致性,可以使用zookeeper实现分布式锁

public class DistributedLock {
    private ZooKeeper zooKeeper;
    private String lockPath;
    private String currentLockNode;

    public DistributedLock(ZooKeeper zooKeeper, String lockPath) {
        this.zooKeeper = zooKeeper;
        this.lockPath = lockPath;
    }

    public void lock() throws KeeperException, InterruptedException {
        // 创建短暂有序节点
        currentLockNode = zooKeeper.create(lockPath + "/lock_", null,
                ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);

        while (true) {
            List children = zooKeeper.getChildren(lockPath, false);
            Collections.sort(children);

            if (currentLockNode.equals(lockPath + "/" + children.get(0))) {
                // 当前节点是最小的节点,获取到了锁
                return;
            } else {
                // 监听前一个节点的删除事件
                String previousNode = children.get(Collections.binarySearch(children,
                        currentLockNode.substring(lockPath.length() + 1)) - 1);
                Stat stat = zooKeeper.exists(lockPath + "/" + previousNode, true);
                if (stat != null) {
                    synchronized (this) {
                        wait();
                    }
                }
            }
        }
    }

    public void unlock() throws KeeperException, InterruptedException {
        zooKeeper.delete(currentLockNode, -1);
    }
}

Redis分布式锁_第2张图片

你可能感兴趣的:(理论知识,redis,分布式)