分布式锁的实现方式:
**基于数据库**
**基于redis**
**基于zookeeper**
1.基于数据库实现
两种做法:
-
基于数据库乐观锁
-
基于数据库悲观锁
乐观锁机制是在数据库引入一个版本号version字段来实现
当我们去数据库读取数据的时候会将version版本号读取出来,如果对读出来的数据进行更新再写入操作就会将version版本号加1,同时将版本号更新进去,这个时候如果有另外一个线程去进行数据库操作就会把version版本号字段读出来,当然这个时候version版本号已经发生变化,更新失败.
悲观锁
悲观锁也叫排他锁,在mysql中是基于 for update来实现的,
//锁定的方法-伪代码
public boolean lock(){
connection.setAutoCommit(false)
for(){
result =
select * from user where
id = 100 for update;
if(result){
//结果不为空,
//则说明获取到了锁
return true;
}
//没有获取到锁,继续获取
sleep(1000);
}
return false;
}
//释放锁-伪代码
connection.commit();
上面的示例中,user表中,id是主键,通过 for update 操作,数据库在查询的时候就会给这条记录加上排它锁。
(需要注意的是,在InnoDB中只有字段加了索引的,才会是行级锁,否者是表级锁,所以这个id字段要加索引)
当这条记录加上排它锁之后,其它线程是无法操作这条记录的。
那么,这样的话,我们就可以认为获得了排它锁的这个线程是拥有了分布式锁,然后就可以执行我们想要做的业务逻辑,当逻辑完成之后,再调用上述释放锁的语句即可。
2.基于redis实现分布式锁,
redis从2.6.12版本之后set命令会支持参数
SET user_key user_value NX PX 100
如果user_key不存在的时候才会去设置这个键值,并且将这个键的值设置为user_value,且过期时间为100ms
为什么这个命令可以帮我们实现锁机制呢?
因为这个命令是只有在某个key不存在的时候,才会执行成功。那么当多个进程同时并发的去设置同一个key的时候,就永远只会有一个进程成功。
当某个进程设置成功之后,就可以去执行业务逻辑了,等业务逻辑执行完毕之后,再去进行解锁。
解锁很简单,只需要删除这个key就可以了,不过删除之前需要判断,这个key对应的value是当初自己设置的那个。
3.基于zookeeper实现分布式锁
基于zookeeper实现分布式锁其实就是基于zookeeper的临时节点来完成
原理:当某个客户端要进行逻辑的加锁时,就会在zookeeper上的节点下生成一个临时节点,然后判断自己是否是这些有序节点中最小的一个,如果是则算是获取了锁,如果不是就没有获取到锁,那么需要在序列中找到比自己小的那个节点,并调用exit()方法进行删除,对其注册事件监听,当监听到这个节点被删除之后,就会去判断 自己是否是这些有序节点中最小的一个,如果是则获取到锁,如果不是就重复上述操作.
如图,locker是一个持久节点,node_1/node_2/…/node_n 就是上面说的临时节点,由客户端client去创建的。
client_1/client_2/…/clien_n 都是想去获取锁的客户端。以client_1为例,它想去获取分布式锁,则需要跑到locker下面去创建临时节点(假如是node_1)创建完毕后,看一下自己的节点序号是否是locker下面最小的,如果是,则获取了锁。如果不是,则去找到比自己小的那个节点(假如是node_2),找到后,就监听node_2,直到node_2被删除,那么就开始再次判断自己的node_1是不是序列中最小的,如果是,则获取锁,如果还不是,则继续找一下一个节点。