微服务-分布式锁(一)-MySQL方案
2021/7/26 2:12:36
本文主要是介绍微服务-分布式锁(一)-MySQL方案,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
1 基于唯一索引(insert
)实现
记录锁的乐观锁方案。基于数据库的实现方式的核心思想是:在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。
1.1 优缺点
优点
- 实现简单、易于理解
缺点
- 没有线程唤醒,获取失败就被丢掉了
- 没有超时保护,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁
- 这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用
- 并发量大的时候请求量大,获取锁的间隔,如果较小会给系统和数据库造成压力
- 这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错,没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作
- 这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁,因为数据中数据已经存在了
- 这把锁是非公平锁,所有等待锁的线程凭运气去争夺锁
1.2 实现方案
DROP TABLE IF EXISTS `method_lock`; CREATE TABLE `method_lock` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `lock_key` varchar(64) NOT NULL DEFAULT '' COMMENT '锁的键值', `lock_timeout` datetime NOT NULL DEFAULT NOW() COMMENT '锁的超时时间', `remarks` varchar(255) NOT NULL COMMENT '备注信息', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uidx_lock_key` (`lock_key`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';
① 获取锁:想要执行某个方法,就使用这个方法名向表中插入数据
INSERT INTO method_lock (lock_key, lock_timeout, remarks) VALUES ('methodName', '2021-07-19 18:20:00', '测试的methodName');
② 释放锁:释放锁的时候就删除记录
DELETE FROM method_lock WHERE lock_key ='methodName';
1.3 问题与解决
- 强依赖数据库可用性,是一个单点(部署双实例)
- 没有失效时间,一旦解锁失败,就会导致死锁(添加定时任务扫描表)
- 一旦插入失败就会直接报错,不会进入排队队列(使用while循环,成功后才返回)
- 是非重入锁,同一线程在没有释放锁之前无法再次获得该锁(添加字段记录机器和线程信息,查询时相同则直接分配)
- 非公平锁(建中间表记录等待锁的线程,根据创建时间排序后进行依次处理)
- 采用唯一索引冲突防重,在大并发情况下有可能会造成锁表现象(采用程序生产主键进行防重)
2 基于表字段版本号实现
版本号对比更新的乐观锁方案。一般是通过为数据库表添加一个 version
字段来实现读取出数据时,将此版本号一同读出。之后更新时,对此版本号加 1
,在更新过程中,会对版本号进行比较,如果是一致的,没有发生改变,则会成功执行本次操作;如果版本号不一致,则会更新失败。实际就是个CAS
过程。
2.1 优缺点
缺点
- 该方式使原本一次的update操作,必须变为2次操作:select版本号一次、update一次。增加了数据库操作的次数
- 如果业务场景中的一次业务流程中,多个资源都需要用保证数据一致性,那么如果全部使用基于数据库资源表的乐观锁,就要让每个资源都有一张资源表,这个在实际使用场景中肯定是无法满足的。而且这些都基于数据库操作,在高并发的要求下,对数据库连接的开销一定是无法忍受的
- 乐观锁机制往往基于系统中的数据存储逻辑,因此可能会造成脏数据被更新到数据库中
3 基于排他锁(for update
)实现
基于排它锁的悲观锁方案。通过在select语句后增加for update
来获取锁,数据库会在查询过程中给数据库表增加排他锁。当某条记录被加上排他锁之后,其他线程无法再在该行记录上增加排他锁,我们可以认为获得排它锁的线程即可获得分布式锁。释放锁通过connection.commit();
操作,提交事务来实现。
3.1 优缺点
优点
- 实现简单、易于理解
缺点
- 排他锁会占用连接,产生连接爆满的问题
- 如果表不大,可能并不会使用行锁
- 同样存在单点问题、并发量问题
3.2 实现方案
建表脚本
CREATE TABLE `methodLock` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `lock_key` varchar(64) NOT NULL DEFAULT '' COMMENT '锁的键值', `lock_timeout` datetime NOT NULL DEFAULT NOW() COMMENT '锁的超时时间', `remarks` varchar(255) NOT NULL COMMENT '备注信息', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY ( `id` ), UNIQUE KEY `uidx_lock_key` ( `lock_key ` ) USING BTREE ) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '锁定中的方法';
加解锁操作
/** * 加锁 */ public boolean lock() { // 开启事务 connection.setAutoCommit(false); // 循环阻塞,等待获取锁 while (true) { // 执行获取锁的sql String sql = "select * from methodLock where lock_key = xxx for update"; // 创建prepareStatement对象,用于执行SQL ps = conn.prepareStatement(sql); // 获取查询结果集 int result = ps.executeQuery(); // 结果非空,加锁成功 if (result != null) { return true; } } // 加锁失败 return false; } /** * 解锁 */ public void unlock() { // 提交事务,解锁 connection.commit(); }
更多JAVA、高并发、微服务、架构、解决方案、中间件的总结在:https://github.com/yu120/lemon-guide
这篇关于微服务-分布式锁(一)-MySQL方案的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-04部署MySQL集群项目实战:新手入门教程
- 2024-11-04如何部署MySQL集群资料:新手入门指南
- 2024-11-02MySQL集群项目实战:新手入门指南
- 2024-11-02初学者指南:部署MySQL集群资料
- 2024-11-01部署MySQL集群教程:新手入门指南
- 2024-11-01如何部署MySQL集群:新手入门教程
- 2024-11-01部署MySQL集群学习:新手入门教程
- 2024-11-01部署MySQL集群入门:新手必读指南
- 2024-10-23BinLog入门:新手必读的MySQL二进制日志指南
- 2024-10-23Binlog入门:MySQL数据库的日志管理指南