数据库实现分布式锁
方案一:根据数据库主键的唯一性约束;
获取锁:向数据库表中插入一条特定键(lock)的数据,如果插入成功则获取锁成功,反之则失败;
释放锁:删除这条记录
复制代码
方案二:利用 update 与版本号
先获取锁的信息
select id, method_name, state,version from method_lock where state=1 and method_name='methodName';
占有锁
update t_resoure set state=2, version=2, update_time=now() where method_name='methodName' and state=1 and version=2;
如果没有更新影响到一行数据,则说明这个资源已经被别人占位了。
复制代码
缺点:
1.强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用
2.锁没有失效时间,一旦解锁失败,就会导致锁一致记录在数据库中,其他线程无法获取到;
3.这把锁是非阻塞的,没有获取到就不会进入排队,要想再次获取锁就要再次触发获取锁的操作。
4.锁是非重入的,同一个线程在没有释放锁之前是无法再次获得锁。
解决方案:
1、数据库集 主备模式
2、没有失效时间,做定时任务,每隔一段时间把数据库中的数据清理一遍
3、非阻塞?搞个循环,知道 insert 成功再返回成功;
4、非重入?在数据库中加个字段,记录当前机器的信息和线程信息,下次在获取时,如果一样,直接分配锁。
redis 实现
watch 利用 watch 实现 redis 乐观锁
乐观锁基于 CAS 思想,是不具有互斥性,不会产生锁等待而消耗资源,但需要反复的重试,但也是因为重试的机制,能编辑哦块的响应,因此可以利用 redis 来实现乐观锁。具体思路:
1、利用 redis 的 watch 功能(),监控 redisKey 的状态
2、获取 redisKey 的值
3、创建 redis 事务
4、给这个 key 的值+1
5、然后执行这个这个事务,如果这个 key 发生变化,则回滚事务
setnx
实现原理,利用redis的setnx命令,如果不存在则设置成功,存在则设置失败。
复制代码
获取锁:
方式一(使用 set 命令)--推荐
/** * 使用redis的set命令实现获取分布式锁 * @param lockKey 可以就是锁 * @param requestId 请求ID,保证同一性 uuid+threadID * @param expireTime 过期时间,避免死锁 * @return */
public boolean getLock(String lockKey,String requestId,int expireTime) {
//NX:保证互斥性 // hset 原子性操作 只要lockKey有效 则说明有进程在使用分布式锁
String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);
if("OK".equals(result)) {
return true; }return false;
}
复制代码
方式 2(使用 setnx 命令实现) -- 并发会产生问题
public boolean getLock(String lockKey,String requestId,int expireTime) {
Long result = jedis.setnx(lockKey, requestId);
if(result == 1) {
//成功设置 进程down 永久有效 别的进程就无法获得锁
jedis.expire(lockKey, expireTime);
return true; }return false;
}
复制代码
释放锁:
不能直接使用 del 命令;可能会删掉其他线程获取到的锁,比如 A 线程获得锁 S,要删除时,刚好过期了,被 B 线程获取到,则删除时会把 B 线程的锁释放掉,最好的就是使用 lua 脚本,获取 key 与释放 key 作为一个原子操作。
redis 锁存在的问题
0、锁过期了,业务还没执行完,锁可能会被其他的线程获取到
1、单机 无法保证高可用
2、主从模式无法保证数据的强一致性,在主机宕机时会造成锁的重复获得。
3、无法续租 在超过过期时间后无法续期,不能继续使用。
4、redis 集群不能保证强一致性,redis 集群时 AP 模型,所以当业务是强一致性时,不能使用 redis 作为分布式锁,可以使用 CP 模型实现:如 zookeeper
zookeeper 实现分布式锁
要清楚zookeeper的分布式锁就要清楚它的几个节点:
复制代码
zk 获取锁过程
当第一个客户端请求过来时,zookeeper客户端会创建一个持久节点locks。如果它(client1)想要获取锁,需要在locks下创建一个顺序临时节点lock1.当创建成功后,客户端client1 会查找locks下面所有的临时顺序节点,判断自己的节点是不是排序最小的一个,如果是则获取锁成功,
这时当第二个客户端client2 来获取锁,创建了lock2,发现不是最小的临时顺序节点,于是获取锁失败,client2它会向排序靠前的节点lock1注册watcher事件,用于监听lock1是否存在,也就是说client2抢锁失败进入等待状态。
复制代码
释放锁:
1、当完成整个业务时手动删除临时节点释放锁
2、出现故障时,客户端与 zk 断开,根据临时节点特性也会删除该节点释放锁
zk 分布式锁的羊群效应
在client1 获取锁lock1之后,当有其他的客户端来获取锁时失败,然后所有的客户端都监听lock1的watcher事件,当lock1释放锁之时,所有的额客户端都去尝试索取锁,虽然最终只有一个获取到锁,但是大量的请求,以及创建节点的操作造成了网络的开销以及服务器的压力影响zk的性能。
解决方案:每个节点都只是监控比自己小的第一个节点。
复制代码
评论