分布式锁比较
基于数据库
1. 基于数据库表
直接创建一张锁表,当要锁住某个方法或资源时,在该表中增加一条记录,想要释放锁的时候就删除这条记录。给某字段添加唯一性约束,如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,可以执行方法体内容。
2. 基于数据库排他锁
如果使用的是MySql的InnoDB引擎,在查询语句后面增加for update
,数据库会在查询过程中(须通过唯一索引查询)给数据库表增加排他锁,我们可以认为获得排它锁的线程即可获得分布式锁,通过 connection.commit() 操作来释放锁。
会引入数据库单点、不可重入、无法保证一定使用行锁(部分情况下MySQL自动使用表锁而不是行锁)、排他锁长时间不提交导致占用数据库连接等问题。
3. 数据库实现分布式锁总结
优点:
直接借助数据库,容易理解。
缺点:
操作数据库需要一定的开销,有一定的性能问题
无失效时间、不阻塞、不可重入
1.建表
CREATE TABLE `methodLock` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法名',
`desc` varchar(1024) NOT NULL DEFAULT '备注信息',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成',
PRIMARY KEY (`id`),
UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';
2.锁方法
insert into methodLock(method_name,desc) values (‘method_name’,‘desc’)
3.释放锁
delete from methodLock where method_name ='method_name'
基于缓存
相比较于基于数据库实现分布式锁的方案来说,基于缓存来实现在性能方面会表现的更好一点。目前有很多成熟的缓存产品,包括Redis、memcached、tair等。
这里以Redis为例举出几种实现方法:
1. 基于 redis 的 setnx()、expire() 方法做分布式锁
setnx 的含义就是 SET if Not Exists
,其主要有两个参数 setnx(key, value)
。该方法是原子的,如果 key 不存在,则设置当前 key 成功,返回 1;如果当前 key 已经存在,则设置当前 key 失败,返回 0。
expire 设置过期时间,要注意的是 setnx 命令不能设置 key 的超时时间,只能通过 expire() 来对 key 设置。
2. 基于 redis 的 setnx()、get()、getset()方法做分布式锁
getset 这个命令主要有两个参数 getset(key,newValue)
,该方法是原子的,对 key 设置 newValue 这个值,并且返回 key 原来的旧值。
3. 基于 Redlock 做分布式锁
Redlock 是 Redis 的作者 antirez 给出的集群模式的 Redis 分布式锁,它基于 N 个完全独立的 Redis 节点(通常情况下 N 可以设置成 5)
4. 基于 redisson 做分布式锁
redisson 是 redis 官方的分布式锁组件,GitHub 地址:https://github.com/redisson/redisson
基于缓存实现分布式锁总结
优点:
性能好
缺点:
锁失效时间要取决于超时时间
1.pom.xml添加依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.8.2</version>
</dependency>
2.添加配置文件
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient getClient(){
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
return redisson;
}
}
3.实现分布式锁
String lockName="bookLock-"+xxx.getUserId()+xxx.getBookNo();//唯一命名
RLock lock=redissonClient.getLock(lockName);//对名称加锁
lock.tryLock(100,10, TimeUnit.SECONDS);
try{
xxx
}catch(Exception e){
log.error("--------出错了{}----------",e.getMessage());
}finally{
if(lock!=null){
lock.unlock();
}
}
基于Zookeeper
大致思想为:每个客户端对某个方法加锁时,在 Zookeeper 上与该方法对应的指定节点的目录下,生成一个唯一的临时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个临时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题
Zookeeper实现分布式锁总结
优点:
可重入,非阻塞、可释放
缺点:
需要动态创建、销毁临时节点来实现锁功能
需要对Zookeeper的原理有所了解
1.pom.xml添加依赖
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.13</version>
<!--自带日志文件会有冲突,要屏蔽一下-->
<exclusions>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
2.添加配置文件
@Configuration
public class ZooConfig {
@Autowired
private Environment env;
@Bean
public CuratorFramework curatorFramework(){
CuratorFramework curatorFramework= CuratorFrameworkFactory.builder().
connectString(env.getProperty("zk.host")).
namespace(env.getProperty("zk.namespace")).
retryPolicy(new RetryNTimes(5,1000)).
build();
curatorFramework.start();
return curatorFramework;
}
}
3.实现分布式锁
//定义Zookeeper客户端CuratorFramework实例
@Autowired
private CuratorFramework client;
//Zookeeper分布式锁的实现原则是由ZNode节点的创建,删除与监听器构成的
//而ZNode节点将对应一个具体的路径-根Unix文件路径类似-需要以/开头
private static final String pathPrefix="/zkLock/";
public void zkLockRobBook(BookDto dto){
InterProcessMutex lock=new InterProcessMutex(client,pathPrefix+dto.getUserId()+dto.getBookNo()+"---lock");
try {
if(lock.acquire(15, TimeUnit.SECONDS)){
xxx....
}
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
1、数据库实现(效率低,不推荐)**
2、redis实现(使用redission实现,但是需要考虑思索,释放问题。繁琐一些)**
3、Zookeeper实现 (使用临时节点,效率高,失效时间可以控制)
4、Spring Cloud 实现全局锁(内置的)
参考文章:
分布式锁的几种实现方式~:
利用Zookeeper实现 - 分布式锁