研究有哪几种方案可以实现分布式锁,技术选型的场景下能用到。
- 本文参考过的文章有分布式锁的几种实现方式
- 方式大致分为3种:基于磁盘存储的关系型数据库MySQL;基于内存的数据库Redis;基于Zookeeper
基于MySQL也有3种方式:基于唯一索引;基于MySQL行锁的乐观锁;基于悲观锁
利用唯一索引,在竞争锁时用insert操作,insert成功表示获取锁成功。下面专门创建一张表实现分布式锁:
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操作竞争锁,通过delete操作释放锁。
优点:实现简单,复杂度低
缺点:
利用mysql自带的行锁机制,前提是本身就要在数据库里面有数据
CREATE TABLE `optimistic_lock` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`resource` int NOT NULL COMMENT '锁定的资源',
`version` int NOT NULL COMMENT '版本信息',
`created_at` datetime COMMENT '创建时间',
`updated_at` datetime COMMENT '更新时间',
`deleted_at` datetime COMMENT '删除时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uiq_idx_resource` (`resource`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据库分布式锁表';
实现的SQL如下:
update optimistic_lock set resource = xx, version = newVersion where id = xx and version = oldVersion;
解释:需要通过主键id去加行锁,否则会走范围锁或者表锁。更新时不仅要更新资源值,也要更新版本信息。查询需要带上版本号信息。
优缺点与唯一索引是宪法方式一样。大量并发下会有大量请求失败或者写压力很高,不适合高并发。
利用MySQL的X排他锁
在查询语句后面增加FOR UPDATE
,数据库会在查询过程中给数据库表增加悲观锁,也称排他锁。当某条记录被加上悲观锁之后,其它线程也就无法再改行上增加悲观锁。
在使用悲观锁时,我们必须关闭MySQL数据库的自动提交属性(参考下面的示例),因为MySQL默认使用autocommit模式
mysql> SET AUTOCOMMIT = 0;
这样在使用FOR UPDATE获得锁之后可以执行相应的业务逻辑,执行完之后再使用COMMIT来释放锁。
优点:
在悲观锁中,每一次行数据的访问都是独占的,只有当正在访问该行数据的请求事务提交以后,其他请求才能依次访问该数据,否则将阻塞等待锁的获取。悲观锁可以严格保证数据访问的安全。
缺点:
每次请求都会额外产生加锁的开销且未获取到锁的请求将会阻塞等待锁的获取,在高并发环境下,容易造成大量请求阻塞,影响系统可用性。另外,悲观锁使用不当还可能产生死锁的情况。
使用排他锁来进行分布式锁的lock,那么一个排他锁长时间不提交,就会占用数据库连接。一旦类似的连接变得多了,就可能把数据库连接池撑爆
详情见笔者写的大厂的Redis分布式锁是如何设计的
利用ZK的临时顺序节点实现分布式锁。
详情见笔者写的SpringBoot基于Zookeeper实现分布式锁
Curator提供的InterProcessMutex
是分布式锁的实现。acquire
方法用户获取锁,release
方法用于释放锁。