SpringBoot实现分布式锁

基于 Redis 的分布式锁

利用 SETNX 和 SETEX

基本命令主要有:

SETNX(SET If Not Exists):当且仅当 Key 不存在时,则可以设置,否则不做任何动作。

SETEX:可以设置超时时间

其原理为:通过 SETNX 设置 Key-Value 来获得锁,随即进入死循环,每次循环判断,如果存在 Key 则继续循环,如果不存在 Key,则跳出循环,当前任务执行完成后,删除 Key 以释放锁。

这种方式可能会导致死锁,为了避免这种情况,需要设置超时时间。

下面,请看具体的实现步骤。

1.创建一个 Maven 工程并在 pom.xml 加入以下依赖:

2.创建启动类 Application.java:

3.添加配置文件 application.yml:

4.创建全局锁类 Lock.java:

5.创建分布式锁类 DistributedLockHandler.java:

6.最后创建 HelloController 来测试分布式锁。

7.测试。

启动 Application.java,连续访问两次浏览器:http://localhost:8080/index,控制台可以发现先打印了一次“执行方法”,说明后面一个线程被锁住了,5秒后又再次打印了“执行方法”,说明锁被成功释放。

通过这种方式创建的分布式锁存在以下问题:

高并发的情况下,如果两个线程同时进入循环,可能导致加锁失败。

SETNX 是一个耗时操作,因为它需要判断 Key 是否存在,因为会存在性能问题。

因此,Redis 官方推荐 Redlock 来实现分布式锁。

利用 Redlock

通过 Redlock 实现分布式锁比其他算法更加可靠,继续改造上一例的代码。

1.pom.xml 增加以下依赖:

2.增加以下几个类:

3.修改 HelloController:

4.按照上节的测试方法进行测试,我们发现分布式锁也生效了。

Redlock 是 Redis 官方推荐的一种方案,因此可靠性比较高。

基于数据库的分布式锁

基于数据库表

它的基本原理和 Redis 的 SETNX 类似,其实就是创建一个分布式锁表,加锁后,我们就在表增加一条记录,释放锁即把该数据删掉,具体实现,我这里就不再一一举出。

它同样存在一些问题:

没有失效时间,容易导致死锁;

依赖数据库的可用性,一旦数据库挂掉,锁就马上不可用;

这把锁只能是非阻塞的,因为数据的 insert 操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作;

这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据库中数据已经存在了。

乐观锁

基本原理为:乐观锁一般通过 version 来实现,也就是在数据库表创建一个 version 字段,每次更新成功,则 version+1,读取数据时,我们将 version 字段一并读出,每次更新时将会对版本号进行比较,如果一致则执行此操作,否则更新失败!

悲观锁(排他锁)

实现步骤见下面说明。

1.创建一张数据库表:

2.通过数据库的排他锁来实现分布式锁。

基于 MySQL 的 InnoDB 引擎,可以使用以下方法来实现加锁操作:

3.我们可以认为获得排它锁的线程即可获得分布式锁,当获取到锁之后,可以执行方法的业务逻辑,执行完方法之后,再通过以下方法解锁:

基于 Zookeeper 的分布式锁

ZooKeeper 简介

ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google Chubby 的一个开源实现,是 Hadoop 和 Hbase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

分布式锁实现原理

实现原理为:

建立一个节点,假如名为 lock 。节点类型为持久节点(Persistent)

每当进程需要访问共享资源时,会调用分布式锁的 lock() 或 tryLock() 方法获得锁,这个时候会在第一步创建的 lock 节点下建立相应的顺序子节点,节点类型为临时顺序节点(),通过组成特定的名字 name+lock+顺序号。

在建立子节点后,对 lock 下面的所有以 name 开头的子节点进行排序,判断刚刚建立的子节点顺序号是否是最小的节点,假如是最小节点,则获得该锁对资源进行访问。

假如不是该节点,就获得该节点的上一顺序节点,并监测该节点是否存在注册监听事件。同时在这里阻塞。等待监听事件的发生,获得锁控制权。

当调用完共享资源后,调用 unlock() 方法,关闭 ZooKeeper,进而可以引发监听事件,释放该锁。

实现的分布式锁是严格的按照顺序访问的并发锁。

代码实现

我们继续改造本文的工程。

1.创建 DistributedLock 类:

2.改造 HelloController.java:

3.按照本文 Redis 分布式锁的方法测试,我们发现同样成功加锁了。

总结

通过以上的实例可以得出以下结论:

通过数据库实现分布式锁是最不可靠的一种方式,对数据库依赖较大,性能较低,不利于处理高并发的场景。

通过 Redis 的 Redlock 和 ZooKeeper 来加锁,性能有了比较大的提升。

针对 Redlock,曾经有位大神对其实现的分布式锁提出了质疑,但是 Redis 官方却不认可其说法,所谓公说公有理婆说婆有理,对于分布式锁的解决方案,没有最好,只有最适合的,根据不同的项目采取不同方案才是最合理的。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180604G1KKJG00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券