理论系列-分布式锁

理论系列-分布式锁

一)介绍

一般环境下,为了实现并发控制,常使用synchronizedReentrantLock。而在分布式环境下,本地锁无法满足需求,即需要分布式锁,实现多服务竞争锁时,只有一个能成功,在其完成前,其他服务只能等待。

二)特点

  • 互斥性:只有一个服务获取锁
  • 可重入性
  • 锁超时:获取锁的服务出故障,能自动超时删除锁
  • 高效和高可用
  • 支持阻塞和非阻塞:locktrylock
  • 支持公平和非公平锁:按请求加锁的顺序获取锁或无序获取锁

三)常见实现方案

  • MySQL
  • Zookeeper
  • Redis
  • 谷歌的Chubby

四)了解

1. MySQL分布式锁

建表resourceLock
字段类型说明
idint(11) unsigned主键。非空&自增
resource_name字符串资源名。非空&默认’’
node_info字符串机器信息。默认空
countint(11)锁次数。非空&默认’0’
阻塞lock
public void lock(resource) {
    while (true) {
        if (lock.lock(resource)) {
            return;
        }
        // 休眠3秒
        Thread.sleep(3*1000);
    }
}

lock.lock(resource) {
    queryResult = "SQL select * from resourceLock where resource_name="+resource;
    currentNodeInfo = "...";//构造,如IP+线程
    if (queryResult != null) {
        // 已有服务占用资源
        if (currentNodeInfo.equals(queryResult.node_info)) {
            // 当前服务占用:可重入
            "SQL count++";
            return true;
        } else {
            // 其他服务占用
            return false;
        }
    } else {
        // 未被占用
        "SQL insert into resourceLock ..."
    	return true;
    }
}
非阻塞trylock
// 不加超时,立马返回结果
public boolean trylock(resource) {
    return lock.lock();
}

// 加超时
public boolean trylock(resource, long timeout) {
    long endTimeout = System.currentTimeMills + timeout;
    while (true) {
        if (lock.lock(resource)) {
            return;
        }
        // 超时判断
        if (System.currentTimeMills >= endTimeout) {
            return false;
        }
    }
}
 
解锁unlock
	1. 先查询`resource_name`,如果有值,则比较`node_info`
		相同,上锁解锁同一个服务,可执行,count--
			若count减1后等于0,删除节点
		不相同,不允许执行unlock
	2. 无值,代表该资源上无锁
	
lock.unlock(资源resource) {
    queryResult = "SQL select * from resourceLock where resource_name="+resource;
    currentNodeInfo = "...";//构造,如IP+线程
    if (queryResult != null) {
        // 当前资源有锁
        if (currentNodeInfo.equals(queryResult.node_info)) {
            // 解锁和上锁同一个服务
            if (quertResult.count > 1) {
           		// 执行sql:count--
           		...
            } else {
            	// 执行sql:删除该行
            	..
            }
        } else {
            // 解锁和上锁服务不同
            return false;
        }
    } else {
        // 当前资源无锁
        return false;
    }
}
锁超时
开启一个定时任务,计算处理时间,取一个大于该时间的定时检测(如5倍),若锁仍未释放,认为上锁服务挂了,直接释放锁。

或者另开一个服务,专门用户超时访问。每隔5*平均处理时间,前后两次访问,服务为同一个,可认为上锁服务挂了,直接释放锁。
总结说明
说明:node_info可使用机器IP和线程
	 lock.lock的所有操作,应该是原子性,需加事务
	 lock.unlock也要加事务

lock.lock(resource)
	内部是一个sql。
	1. 先查询`resource_name`,如果有值,则比较`node_info`
		相同,可重入锁,`count++`
		不相同,休眠,等待下次请求
	2. 如果无值,插入新的数据,占用锁

lock.unlock(resource)
	1. 先查询`resource_name`,如果有值,则比较`node_info`
		相同,上锁解锁同一个服务,可执行,count--
			若count减1后等于0,删除节点
		不相同,不允许执行unlock
	2. 无值,代表该资源上无锁
适用场景

掘金 再有人问你分布式锁,这篇文章扔给他

优点

理解简单

缺点

实现繁琐,包括:超时、事务等。

性能局限于数据库,并发高时不适用

乐观锁如何实现?

待定

2 Zookeeper分布式锁

基础:Paxos算法

实现原理
/lock加锁目录

/lock/resource_name/临时有序节点(服务名):值为重入次数
使用
InterProcessMutex lock = new InterProcessMutex(client, lock_path);
try {
    lock.acquire();
    // 业务
} finally {
    lock.release();
}

3 Redis分布式锁

实现原理
set, nx, 上锁和解锁同一个服务
1. k-v不存在才加锁
问题1:超时
2. ex+Lua脚本
问题2:业务时间 > 超时时间

Redission
集群主挂

解决

集群都执行setnx,超半数认为成功

解锁,集群全部请求setnx
逐步深入
1. setnx,key是唯一标识,value为(服务,加锁次数)
	- 成功:"OK"
	- 失败:null

2. del

问题一:锁超时。
	获取锁的服务,挂了,没释放锁
	需要一个超时时间,自动释放锁:expire
问题二:并发环境,非原子操作不安全,如setnx和expire分开执行,甚至setnx后挂了。
	Lua脚本
问题三:业务时间 > 超时时间。获取锁的服务还没执行完,锁被释放了
	上锁和解锁为同一个服务:Lua实现
	快过期,业务未执行完:续期。获取锁的线程开启一个守护线程
		定时检测(小于超时).检测到锁快超时,但服务仍在使用,续时

分布式锁的问题

GC

服务A,获取锁

服务A,执行GC(stop-the-world),…,结束(锁释放,服务B获取锁)

服务A恢复,锁

结果:A和B同时持有锁

时钟跳跃

长时间业务处理

如IO等

Chubby分布式锁

待定

参考

https://juejin.im/post/5bbb0d8df265da0abd3533a5
https://juejin.im/post/5b16148a518825136137c8db
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值