关于分布式锁:夜深人静了我们来学一下分布式锁

122次阅读

共计 3378 个字符,预计需要花费 9 分钟才能阅读完成。

记录一下明天的文章开始写的工夫 00:53,夜深人静了,咱们来学一下分布式锁,咱们要悄悄地学习,而后教训所有人。

什么是分布式锁?分布式锁又能够解决哪些问题呢?

在咱们的零碎还没有应用分布式架构的时候,咱们能够用同步锁或者 Lock 锁,来保障多线程并发的时候,同一时间只有一个线程批改共享变量或者执行代码块,然而当咱们当初大部分零碎都是分布式集群部署的,单纯的同步锁和 Lock 锁只能保障单个实例上的数据一致性,多实例就失去了作用。

这个时候就须要应用分布式锁来保障共享资源的原子性,比方咱们电商零碎外面的扣减库存,当单量小的时候问题不大,如果单量很大,同一时间多个实例都在并发解决扣减库存的业务的时候,就可能存在超卖的问题。

分布式锁的实现?

常见的分布式锁有数据库实现分布式锁、Zookeeper 实现分布式锁、Redis 实现分布式锁、Redisson 实现。其中数据库实现分布式锁比较简单,也很容易了解,间接基于数据库实现就能够了,在一些分布式的业务中也常常应用,然而这种形式也是效率最低的,个别是不应用的,咱们就着重介绍一下其余三种形式的实现。

Zookeeper 实现分布式锁

应用 Zookeeper 来实现分布式锁就比拟常见,比方很多我的项目就应用 Zookeeper 作为分布式注册核心,就喜爱用 Zookeeper 来实现分布式锁,这次要是借助于 Zookeeper 的两大个性:程序长期节点、Watch 机制。

程序长期节点:相熟 Zookeeper 的同学都晓得,Zookeeper 提供了多层级的节点命名空间,每个节点都是用斜杠分隔的门路来示意,相似于咱们的文件夹。节点又分为长久节点和长期节点,节点还能够标记为有序,当节点被标记为有序性,这个节点就具备程序自增的特点,咱们就能够借助这个特点来创立咱们所需的节点。

Watch 机制:Watch 机制是 Zookeeper 另一个重要的个性,咱们能够在指定节点上注册一些 Watcher,在一些特定的事件触发的时候,告诉用户这个事件。

Zookeeper 实现分布式锁的过程

咱们先创立一个长久节点作为父节点,每当须要拜访创立分布式锁的时候,就在这个父节点下创立相应的长期的程序子节点,以长期节点名称、父节点名称和顺序号组成特点的名称。在建设子节点后,对父节点下以这个这个子节点名称结尾的子节点进行排序,判断刚建设的节点顺序号是不是最小的,如果是最小的则获取锁,如果不是最小节点,则阻塞期待锁,并且在获取该节点的上一程序节点注册 Watcher,期待节点对应的操作取得锁。

当业务解决完之后,删除该节点,敞开 zk,进而触发 Watcher,开释该锁。

上图就是就是严格依照程序拜访的分布式锁实现,更多的时候咱们引入一些框架来帮忙咱们实现,比方最罕用的 Curator 框架,代码如下:

InterProcessMutex lock = new InterProcessMutex(client, lockPath);
if (lock.acquire(maxWait, waitUnit) ) {
    try {// 业务解决}
    finally{lock.release();
    }
}

Zookeeper 来实现分布式锁人造的劣势就是,Zookeeper 是集群实现的,咱们生产环境个别也是集群部署的,能够防止单点问题,稳定性较好,能保障每次操作都能够开释锁。

毛病就是,频繁的创立删除节点,加上注册 watch 事件,对于 zookeeper 集群的压力比拟大,性能这一块也比不上 Redis 实现的分布式锁。

Redis 实现分布式锁

Redis 实现的分布式锁,最为简单,然而性能确是最佳的,所以在对性能要求更高的零碎里,咱们都抉择应用 Redis 来实现分布式锁。利用 Redis 实现分布式锁,个别都是应用 SETNX 实现,举个简略的例子:

public static boolean getDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

    if ("OK".equals(result)) {return true;}
    return false;
}

SETNX 办法保障设置锁和锁过期工夫的原子性,然而对于锁的过期工夫设置咱们要留神,如果执行业务

工夫比拟长,咱们设置的过期工夫又比拟短的状况下就会造成,业务还没执行完,锁已开释的问题。所以咱们须要依据理论业务解决来评估设置锁的过期工夫,来保障业务能够失常的解决完。

Redisson 实现分布式锁

Redisson 是架设在 Redis 根底上的一个 Java 驻内存数据网格。Redisson 在基于 NIO 的 Netty 框架上,充沛的利用了 Redis 键值数据库提供的一系列劣势,在 Java 实用工具包中罕用接口的根底上,为使用者提供了一系列具备分布式个性的常用工具类。性能也比咱们罕用的 jedis 好一些。

Redisson 不论是单节点模式还是集群模式,都很好的实现了分布式锁,个别用的多的都是集群模式,在集群模式下,Redisson 应用 RedLock 算法,很好的解决了 Master 节点宕机时切换到另外一个 Master 节点过程中多个利用取得锁。

Redisson 集群模式获取锁的实现就是,在不同节点上获取锁,每个节点上获取锁都有超时工夫,如果获取锁超时就认为这个节点不可用,当胜利获取锁的个数超过 Redis 节点的半数,且获取锁耗费的工夫还没超过锁过期工夫,则认为获取锁胜利。获取锁胜利后从新计算锁开释工夫,由原来的锁开释工夫减去获取锁耗费的工夫,如果最终获取锁失败,曾经获取锁胜利的节点也会开释锁。

具体的代码实现:

引入依赖

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.13.1</version>
</dependency>

Redisson 配置文件:

@Bean
public RedissonClient redissonClient() {Config config = new Config();
    config.useClusterServers()
            .setScanInterval(3000) // 集群状态扫描间隔时间,单位是毫秒
            .addNodeAddress("redis://192.168.0.1:6379).setPassword("666")
            .addNodeAddress("redis://192.168.0.2:6379").setPassword("666")
            .addNodeAddress("redis://192.168.0.3:6379")
            .setPassword("666");
    return Redisson.create(config);
}

获取锁操作:

long waitTimeout = 10;
long leaseTime = 1;
RLock lock1 = redissonClient1.getLock("lock1");
RLock lock2 = redissonClient2.getLock("lock2");
RLock lock3 = redissonClient3.getLock("lock3");

RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);

redLock.trylock(waitTimeout,leaseTime,TimeUnit.SECONDS);
try{//...}finally{redLock.unlock();
}

总结

实现分布式锁的形式不止这三种,最简略的就是数据库实现,Zookeeper 实现也绝对比较简单,然而性能最好的还是 Redis 实现,然而可靠性方面,Zookeeper 基于分布式集群,具备人造的劣势,可靠性绝对更高。如果业务场景对性能要求不是很高的时候,优先应用 Zookeeper 实现分布式锁。

如果明天的文章对你有所帮忙,无妨加个关注点个赞反对一下吧!

或者是能够退出我的:Java 学习园地,如果你是零根底学习,对你会很有帮忙。

正文完
 0