前言
对多线程有所了解的朋友一般都会熟悉一个概念:锁。
在多线程并发场景下,要保证在同一时刻只有一个线程可以操作某个业务、数据或者变量,通常需要使用加锁机制。比如synchronized
或Lock
等。
而随着架构演进、业务发展,我们的应用往往都不是只部署在一台服务器上,而是使用分布式集群架构,同时存在多台相同的应用。
比如某电商网站在进行商品销售时,因为商品的数量是有限的,每个用户购买一件商品,需要将商品的库存减1;
但是我们想一下,在“双十一”这种火爆的活动时,可能会有大量的用户购买同一件商品,同时对该商品库存减1,如果不加锁,则极有可能会造成商品超卖情况。
要保证在这种分布式场景下,共享数据的安全性和一致性,则需要使用分布式锁。上面例子中的商品库存就是共享数据。
什么是分布式锁
顾名思义,分布式锁是指在分布式场景下,保证同一时刻对共享数据只能被一个应用的一个线程操作。用来保证共享数据的安全性和一致性。
分布式锁应该满足哪些要求
现在我们来分析一下,我们要实现一个分布式锁的话,需要满足哪些要求呢?
- 首先最基本的,我们要保证同一时刻只能有一个应用的一个线程可以执行加锁的方法,或者说获取到锁;(一个应用线程执行)
- 然后我们这个分布式锁可能会有很多的服务器来获取,所以我们一定要能够高性能的获取和释放;(高性能)
- 不能因为某一个分布式锁获取的服务不可用,导致所有服务都拿不到或释放锁,所以要满足高可用要求;(高可用)
- 假设某个应用获取到锁之后,一直没有来释放锁,可能服务本身已经挂掉了,不能一直不释放,导致其他服务一直获取不到锁;(锁失效机制,防止死锁)
- 一个应用如果成功获取到锁之后,再次获取锁也可以成功;(可重入性)
- 在某个服务来获取锁时,假设该锁已经被另一个服务获取,我们要能直接返回失败,不能一直等待。(非阻塞特性)
以上是所有分布式锁要满足的一些基本要求。
实现方式有哪些
那么我们可以采取哪种方式来实现分布式锁呢?目前常见的方式主要有以下三种:
- 基于数据库实现
- 基于
ZooKeeper
实现 - 基于
Redis
实现
接下来我们看看这三种方式具体都是怎样实现分布式锁的。
基于数据库
使用数据库实现分布式锁,有两种方式。
第一种是基于数据库表实现。
比如我们有如下表来保存分布式锁记录:
CREATE TABLE `methodLock` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`method_name` varchar(64) NOT NULL COMMENT '锁定的方法名',
`desc` varchar(1024) NOT NULL DEFAULT '备注信息',
`update_time` timestamp NOT NULL DEFAULT now() ON UPDATE now() COMMENT '保存时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uidx_method_name` (`method_name `)
) ENGINE=InnoDB COMMENT='分布式锁定的方法';
复制代码
当我们的某个服务要执行某段需要分布式锁定的方法时,则执行插入语句,在该表中插入一条记录。
insert into methodLock(method_name,desc) values ('saleProduct','出售产品减库存');
复制代码
因为我们在定义表时,method_name
添加了唯一约束,如果在我们插入记录时,有多个服务都要执行这个操作,那么数据库可以保证只能有一个服务成功,我们认为只有插入成功的那个服务获取到了锁,可以继续执行该方法。
当方法执行完毕后,需要释放锁,则执行一条删除语句,将插入表中的记录删除。
delete from methodLock where method_name = 'saleProduct';
复制代码
另一种是基于数据库排他锁。
除了上面的通过插入删除的方式外,借助排它锁实现分布式