基于数据库

基于数据库(MySQL)的方案,一般分为3类:基于表记录、乐观锁和悲观锁

基于表记录

用表主键或表字段加唯一性索引便可实现,如下;

CREATE TABLE `database_lock` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`resource` int NOT NULL COMMENT '锁定的资源',
`description` varchar(1024) NOT NULL DEFAULT "" COMMENT '描述',
PRIMARY KEY (`id`),
UNIQUE KEY `uiq_idx_resource` (`resource`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据库分布式锁表';

想获得锁插入一条数据

INSERT INTO database_lock(resource, description) VALUES (1, 'lock');

解锁删除数据:

DELETE FROM database_lock WHERE resource=1;

这种实现方式非常的简单,但是需要注意以下几点:

  • 这种锁没有失效时间,一旦释放锁的操作失败就会导致锁记录一直在数据库中,其它线程无法获得锁。这个缺陷也很好解决,比如可以做一个定时任务去定时清理。
  • 这种锁的可靠性依赖于数据库。建议设置备库,避免单点,进一步提高可靠性。
  • 这种锁是非阻塞的,因为插入数据失败之后会直接报错,想要获得锁就需要再次操作。如果需要阻塞式的,可以弄个for循环、while循环之类的,直至INSERT成功再返回。
  • 这种锁也是非可重入的,因为同一个线程在没有释放锁之前无法再次获得锁,因为数据库中已经存在同一份记录了。想要实现可重入锁,可以在数据库中添加一些字段,比如获得锁的主机信息、线程信息等,那么在再次获得锁的时候可以先查询数据,如果当前的主机信息和线程信息等能被查到的话,可以直接把锁分配给它。
  • 在 MySQL 数据库中采用主键冲突防重,在大并发情况下有可能会造成锁表现象
基于乐观锁

可基于MVCC机制实现

  • 优点:在检测数据冲突时并不依赖数据库本身的锁机制,不会影响请求的性能,当产生并发且并发量较小的时候只有少部分请求会失败

  • 缺点: 唯一癿问题就是对数据表侵入较大,我们

    要为每个表设计一个版本号字段,然后写一条判断 sql 每次进行判断,增加了数据库操作的次数,在高并发要求下,对数据库连接的开销也是无法忍受的。

基于悲观锁

在查询语句后面增加for update, 数据库会在查询过程中给数据库表增加排他锁, 当某条记录被加上排他锁之后,其他线程无法再在该行记录上增加排他锁。

我们可以任务获得排他锁的线程即可获得分布式锁,当获取到锁之后,可以执行方法的业务逻辑,执行完方法后,通过connection.commit()操作来释放锁

注意:在加锁的时候,只有明确地指定主键(或索引)的才会执行行锁,否则MySQL 将会执行表锁

加锁前注意取消自动提交

优点:

  • 简单易于理解
  • 严格保证数据访问的安全

缺点:

  • MySQL会对查询进行优化,如果任务全表扫描效率更高,便使用表锁,导致性能问题
  • 如果一个排他锁长时间不提交,就会占用数据库连接,类似连接变多,就可能把连接池撑爆
  • 悲观锁使用不当还可能产生死锁的情况
  • 每次请求都会额外产生加锁的开销且未获取到锁的请求将会阻塞等待锁的获取,在高并发环境下,容易造成大量请求阻塞,影响系统可用性

基于redis

Java jedis分布式锁例子

依赖(注意版本2.9.0后,但3以上不支持)

<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
public class RedisTool {

    private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX"; /**
* 尝试获取分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
/**
* 1. 使用key来当锁,因为key是唯一的
* 2. value,传的是requestId。通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据
* 3. NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
* 4. PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。
* 5. time,代表key的过期时间
*/
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false; } private static final Long RELEASE_SUCCESS = 1L; /**
* 释放分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
/**
* 使用Lua语言来实现,来确保上述操作是原子性。在eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令。
* 参数KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId
*/
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false; } }

执行上面的set()方法就只会导致两种结果:

  • 当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。
  • 已有锁存在,不做任何操作。
Redisson实现分布式锁

使用流程如下,创建Redisson实例(单机或哨兵模式),然后通过getLock获取锁,后续是进行lock和unlock操作。

// 1. Create config object
Config config = new Config();
config.useClusterServers()
// use "rediss://" for SSL connection
.addNodeAddress("redis://127.0.0.1:7181");
// 2. Create Redisson instance
// Sync and Async API
RedissonClient redisson = Redisson.create(config);
// 3. Get Redis based implementation of java.util.concurrent.locks.Lock
RLock lock = redisson.getLock("myLock");

具体使用例子可参考:https://www.cnblogs.com/milicool/p/9201271.html

基于zookeeper

zookeeper基本锁原理

利用临时节点与watch机制,每个锁占用一个普通节点/lock,当需要获取锁时,在/lock目录下创建一个临时节点,创建成功则表示获取锁成功,失败则watch /lock节点,有删除操作后再去争锁。

临时节点

  • 好处:在于当进程挂掉后能自动上锁的节点自动删除,即取消锁
  • 缺点: 所有取锁失败的进程都监听父节点,很容易发生羊群效应,即当释放锁后所有等待进程一起来创建节点,并发量很大
zookeeper锁优化原理

上锁改为创建临时有序节点,每个上锁的节点均能创建节点成功,只是其序号不同,只有序号最小的可以拥有锁,如果这个节点序号不是最小的则watch序号比本身小的前一个节点。

步骤:

  • 在/lock节点下创建一个有序临时节点(EPHEMERAL_SEQUENTIAL)
  • 判断创建的节点序号是否最小,如果是则获取锁成功。不是则获取锁失败,watch序号比本身小的前一个节点(避免很多线程watch同一个node,导致羊群效应)
  • 当获取锁失败,设置watch后则等待watch事件到来后,再次判断是否序号最小
  • 取锁成功则执行代码,最后释放锁(删除该节点)

优缺点:

  • 优点:有效的解决单点问题,不可重入问题,非阻塞问题,以及锁无法释放问题。实现简单
  • 缺点:性能上可能没有缓存服务高,因为每次在创建锁和释放锁过程中,都要动态创建、销毁临时节点来实现锁功能。zookeeper中创建和删除节点只能通过Leader服务器来执行,然后将数据同步到所有follower机器上。

(图片来自https://mp.weixin.qq.com/s/jn4LkPKlWJhfUwIKkp3KpQ)

开源框架Curator

Curator开源框架对zookeeper分布式锁进行了实现。具体例子可参考:https://www.jianshu.com/p/31335efec309

参考:

https://mp.weixin.qq.com/s/jn4LkPKlWJhfUwIKkp3KpQ

https://blog.csdn.net/u013256816/article/details/92854794

https://www.cnblogs.com/milicool/p/9201271.html

https://mp.weixin.qq.com/s/y_Uw3P2Ll7wvk_j5Fdlusw

https://mp.weixin.qq.com/s/ovBtKTs-ycOWXSpZqRm6BA

https://mp.weixin.qq.com/s/iOtnIEPlEM1crBIgHXDZsg

https://mp.weixin.qq.com/s/95N8mKRreeOwaXLttYCbcQ

基于数据库、redis和zookeeper实现的分布式锁的更多相关文章

  1. 基于redis集群实现的分布式锁,可用于秒杀商品的库存数量管理,有測试代码(何志雄)

    转载请标明出处. 在分布式系统中,常常会出现须要竞争同一资源的情况,本代码基于redis3.0.1+jedis2.7.1实现了分布式锁. redis集群的搭建,请见我的另外一篇文章:<>& ...

  2. 基于zookeeper实现的分布式锁

    基于zookeeper实现的分布式锁 2011-01-27 • 技术 • 7 条评论 • jiacheo •14,941 阅读 A distributed lock base on zookeeper ...

  3. 【spring boot】【redis】spring boot基于redis的LUA脚本 实现分布式锁

    spring boot基于redis的LUA脚本 实现分布式锁[都是基于redis单点下] 一.spring boot 1.5.X 基于redis 的 lua脚本实现分布式锁 1.pom.xml &l ...

  4. Redis中是如何实现分布式锁的?

    分布式锁常见的三种实现方式: 数据库乐观锁: 基于Redis的分布式锁: 基于ZooKeeper的分布式锁. 本地面试考点是,你对Redis使用熟悉吗?Redis中是如何实现分布式锁的. 要点 Red ...

  5. python使用redis实现协同控制的分布式锁

    python使用redis实现协同控制的分布式锁 上午的时候,有个腾讯的朋友问我,关于用zookeeper分布式锁的设计,他的需求其实很简单,就是节点之间的协同合作. 我以前用redis写过一个网络锁 ...

  6. Redis的“假事务”与分布式锁

    关注公众号:CoderBuff,回复"redis"获取<Redis5.x入门教程>完整版PDF. <Redis5.x入门教程>目录 第一章 · 准备工作 第 ...

  7. java使用zookeeper实现的分布式锁示例

    java使用zookeeper实现的分布式锁示例 作者: 字体:[增加 减小] 类型:转载 时间:2014-05-07我要评论 这篇文章主要介绍了java使用zookeeper实现的分布式锁示例,需要 ...

  8. Redis 当成数据库在使用和可靠的分布式锁,Redlock 真的可行么?

    怎样做可靠的分布式锁,Redlock 真的可行么? https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html ...

  9. 使用数据库、Redis、ZK分别实现分布式锁!

    分布式锁三种实现方式: 基于数据库实现分布式锁: 基于缓存(Redis等)实现分布式锁: 基于Zookeeper实现分布式锁: 基于数据库实现分布式锁 悲观锁 利用select - where - f ...

随机推荐

  1. 树莓派自动连接WiFi

    使用sudo raspi-config配置好第一个wifi 然后只需要修改一个文件sudo nano /etc/wpa_supplicant/wpa_supplicant.conf 内容如下: ctr ...

  2. 从内存泄露、内存溢出和堆外内存,JVM优化参数配置参数

    内存泄漏 内存泄漏是指程序在申请内存后,无法释放已申请的内存空间,无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费.内存泄漏最终会导致OOM. 造成内存泄漏 ...

  3. 第二十五章、containers容器类部件GroupBox分组框详解

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.概述 容器部件就是可以在部件内放置其他部件的部件,在Qt Designer中可以使用的容器部件有 ...

  4. Python正则表达式re.search(r'\*{3,8}','*****')和re.search('\*{3,8}','*****')的匹配结果为什么相同?

    老猿做过如下测试: >>> re.search(r'\*{3,100}','*****') <re.Match object; span=(0, 5), match='**** ...

  5. PyQt(Python+Qt)学习随笔:QAbstractItemView的defaultDropAction属性

    老猿Python博文目录 老猿Python博客地址# 一.概述 defaultDropAction属性用于控制QAbstractItemView及其子类的实例视图中拖放时放下的默认操作.该属性的类型为 ...

  6. 剑指offer二刷——数组专题——斐波那契数列

    题目描述 大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0,第1项是1). n<=39 我的想法 斐波那契数列定义:F(0)=0,F(1)=1, ...

  7. WPF中Logical Tree和Visual Tree的区别

    The Logical TreeThe logical tree describes the relations between elements of the user interface. The ...

  8. Acwing 120. 防线

    题目地址 题目简译:给定\(n\)个等差数列,每个等差数列的起点为\(s\),终点为\(e\),差为\(d\).整个序列中至多有一个位置所占数字是奇数.判断奇数位是否存在,如果不存在输出"T ...

  9. 笔记-Recursive Queries

    Recursive Queries \[m_{l,r}=\textrm{id}(\max_{i=l}^r a_i)\\ f(l,r)= \begin{cases} (r-l+1)+f(l,m_{l,r ...

  10. I/O接口

    目录 I/O接口的功能 接口的功能(要解决的问题) 接口的功能(具体操作) I/O接口的基本结构 接口和端口 I/O端口及编址 统一编址 独立编址 I/O接口的类型 小结 接口可以看作是两个部件之间的 ...