如何用MySQL设计一个分布式锁?

如何用MySQL设计一个分布式锁?

前言

分布式锁想必大家都不陌生,可以用来解决在分布式环境下,多个用户在同一时间读取/更新相同的资源带来的问题。比如秒杀场景下的库存问题、redis key失效情况下请求直接打到MySQL中造成MySQL负载过大的问题,这些问题都可以通过分布式锁来解决。

什么是死锁mysql

关于如何实现分布式锁,大家可能对基于Redis实现比较熟悉,但是往往很多情况是一些并发量不大的项目用不上Redis,Redis往往适用于并发量比较大的场景。但是MySQL基本都是有的,所以今天我来谈谈如何基于MySQL实现我们的分布式锁。

设计目标互斥。不同机器上许多进程/线程中只有一个可以访问特定资源,其他进程/线程应该等到锁被释放才可以用。TTL。从CAP理论我们知道,网络总是不可靠的,任何一台服务器都有可能宕机一段时间。所以我们在设计分布式锁服务的时候,需要考虑到可能有一个持有锁的客户端宕机,无法释放锁,从而阻塞所有等待获取同一个锁的客户端。所以我们需要一种机制,可以在这种情况下自动释放锁来解锁其他客户端。

3. 相关API

高性能公平。先到先得。等待时间最长的人应该下一个获得锁。如果是这样,则该锁被认为是公平锁。否则就是非公平锁。这两种锁在现实中都有实际使用。重入锁。想象一下,一个节点或服务器进程获取了一个锁,开始处理业务逻辑,然后遇到一个代码片段要求再次获取同一个锁,在这种情况下,节点或进程不应死锁,相反,它应该能够再次获取相同的锁什么是死锁mysql,因为它已经持有锁。MySQL如何实现分布式锁?1. 唯一键约束

我们可以使用MySQL的唯一性约束来实现分布式锁,整体的思路如下:

现在我们来简单实现下,创建一个lock表,其中字段有唯一性约束。

CREATE TABLE `lock` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `lock_key` varchar(256) NOT NULL,
  `holder` varchar(256) NOT NULL,
  `creation_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_lock_key` (`lock_key`)
);

获取锁:

INSERT INTO `lock`(`lock_key`, `holder`) VALUES ('project1_uid1', 'server1_ip1_tid1');

释放锁:

DELETE FROM `lock` WHERE `lock_key` = 'project1_uid1';

上面的方案已经基本满足通过MySQL实现分布式锁的基本要求。现在让我们考虑一些特殊情况,看看它是否对分布式系统中的常见故障具有鲁棒性。

如果客户端 A 获取了锁,向 DB 中插入了一行,但后来客户端 A 崩溃了,或者网络分区和客户端 A 无法访问 DB 怎么办?在这种情况下,该行将保留在数据库中,不会被删除。换句话说,对于其他客户端来说什么是死锁mysql,就好像客户端 A 仍然持有锁(即使 A 已经崩溃了!)。其他客户端将无法获取锁,并返回错误。

一种常用的方法是为每个锁分配一个 TTL。这个想法很简单:如果客户端 A 崩溃并且无法释放锁,那么其他人应该执行删除 DB 中的行从而释放锁的工作。假设通常客户端 A 需要 3 分钟才能完成任务。我们可以将 TTL 设置为 5 分钟。然后我们需要构建另一个服务来不断扫描lock表,并删除超过 5 分钟前创建的任何行。但是,还有其他问题:

第一个问题用MySQL很难完全解决。我们可以考虑A在获取到分布式锁后,新起个线程去检查锁是否快要过期了,比如发现TTL还剩下1/3时间,但是A还没有结束,这时候去扩大TTL时间,这就是锁的续签机制。但是在现实中,对于大部分的业务案例如何用MySQL设计一个分布式锁?,我们总是可以设置一个足够大的TTL,使得这种情况很少发生,以至于对公司业务的影响几乎察觉不到。

现在让我们看看第2个问题怎么解决?

2. 使用时间戳+唯一键约束

什么是死锁mysql

我们可以在lock表中添加一列来存储上次获取锁的时间戳。

CREATE TABLE `lock` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `lock_key` varchar(128) NOT NULL,
  `holder` varchar(128) NOT NULL DEFAULT '',
  `version` int(11) not null,
  `creation_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `last_lock_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_lock_key` (`lock_key`)
);

现在我们用${}表示分布式锁的TTL。

获取锁:

当客户端 B 试图获取锁时,我们可以添加“ < ${now} – ${}作为where条件的一部分。

UPDATE `lock` SET `holder` = 'server1_ip1_tid1', `last_lock_time` = ${now} WHERE `lock_key` = 'project1_uid1' and `last_lock_time` < ${now} - ${timeout};

在这种情况下,只有当“ < ${now} – ${}客户端 B 可以获取锁、将 更改为其 ID 并将其重置为当前时间戳时。假设后面客户端 B 挂了,不能释放锁,最坏的情况是等待${}TTL时间以后,其他客户端就能拿到锁。

释放锁:

我们可以把更新为一个很小时间戳,例如‘1970–01–01 00:00:01’。

UPDATE `lock` SET `holder` = '', `last_lock_time` = ${min_timestamp} WHERE `lock_key` = 'project1_uid1' and `holder` = 'server1_ip1_tid1';

在WHERE语句中,我们添加了“ = ‘’,这是为了避免其他客户端不小心释放了当前客户端持有的锁。

成功释放锁后,将其设置为空,并将设置为最小时间戳,以便其他客户端可以轻松获取锁。

现在我们解决了TTL问题,但是在上面的实现中,如果持有锁,其他客户端将需要一直循环重试,等待锁释放后再获取锁。如果分布式锁服务可以通知等待的客户端锁可用,那就更好了,我们思考下在MySQL中该如何实现。

3.使用FOR 实现锁释放通知

MySQL具有行级锁功能,在RC隔离级别下什么是死锁mysql,当我们使用FOR 时,MySQL会为所有符合过滤条件的行加行级锁。当一个客户端会话获得锁时,所有其他客户端都将等待锁。此外,等待客户端唤醒并获取锁的顺序与它们首次尝试获取锁时的顺序相同。只要持有锁的客户端在 SQL 事务内执行逻辑,FOR 就可以执行多次。换句话说,锁是重入锁。

另外,针对FOR ,MySQL还支持两种模式: 和 SKIP 。

通过这两个选项,我们可以实现行为,即客户端尝试获取锁,获取不到锁则立即返回,而不是等待。

我们可以简化我们的lock表以仅包含两个字段:

CREATE TABLE `lock` ( 

什么是死锁mysql

`id` bigint unsigned NOT NULL AUTO_INCREMENT, `lock_key` varchar(128) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uniq_lock_key` (`lock_key`) );

获取锁:

BEGIN;
SELECT * FROM `demo`.`lock` WHERE `lock_key` = 'project1_uid1' FOR UPDATE;

这里关于启动新事务BEGIN 做一个说明,只有在第一次获取锁时才需要它。后续重入时,不要执行BEGIN,否则会启动一个新的事务如何用MySQL设计一个分布式锁?,现有的事务结束,实际上是在事务结束时释放锁。

非阻塞尝试锁():

BEGIN;
SELECT * FROM `demo`.`lock` WHERE `lock_key` = 'project1_uid1' FOR UPDATE NOWAIT;

释放锁:

COMMIT;

提交事务就可以释放锁。

总结

我们现在回头来看看基于MySQL实现分布式锁,是否满足我们一开始定下的设计目标:

1. 互斥,最基本的功能,肯定是可以的。

2. TTL 机制,MySQL 本地管理客户端会话。如果客户端由于机器故障或网络故障而断开连接,MySQL 将自动释放行级锁。

3. 支持所有 3 个 API:获取/尝试/释放锁。

4. 高性能:释放锁时,MySQL只会通知队列中等待的下一个客户端,而不是一次性通知所有客户端,避免雷群问题。

5. 公平。MySQL 行锁本身支持。

6. 重入。MySQL 行锁本身也支持。记住第一次获取锁就开始事务,以后再入时不要再开始新的事务。

看来基本上是没什么问题的,但是还有一点,我们需要提前向lock表中插入资源锁的数据,然后获取/尝试/释放锁的 API 才能按预期工作。

参考

@bb8s/–lock-with-mysql-

公告:
1. 本站所有资源来源于用户上传和网络,如有侵权请联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系站长处理!
6. 本站不售卖代码,资源标价只是站长收集整理的辛苦费!如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。
7. 站长QQ号码 2205675299

资源库 - 资源分享下载网 » 如何用MySQL设计一个分布式锁?

常见问题FAQ

关于资源售价和售后服务的说明?
本站所有资源的标价均为本站收集资源的辛苦费,不代表资源本身的价值。软件是高智慧高价值的商品,不可能是白菜价。本站资源标价只是赞助费用,收取的赞助费仅用来维持本站的日常运营!毕竟收集整理资料需要投入云计算资源和站长大量的精力。
代码有没有售后服务和技术支持?
由于代码的运行具有不可预见性,本站不保证代码完整可运行,不提供技术支持和售后服务。 本站原创代码都是站长自己开发的,可以有偿提供技术支持服务。 网站里标明【亲测】的代码都是站长亲测过的,其他的代码由于精力有限,没有一一测试,不能保证代码就一定能够使用,更没有技术支持服务,下载前请自行斟酌。
有没有搭建服务?
由于搭建服务比较费时费力,所以本站除了原创代码外均不提供搭建服务。本站分享代码纯属兴趣爱好,不以盈利为目的,请勿咨询有没有搭建服务,谢谢理解。
链接地址失效了怎么办?
请带上资源链接地址联系客服,工作时间内我们看到后将第一时间回复。
关于解压密码
本站资源一般都没有加密,如果发现需要解压密码的,那么就输入 hao.35dc.com 试试。

发表评论

资源库,由老程序员细心甄别、精心筛选,只为提供优质的源码资源

关于我们 联系我们