其他分享
首页 > 其他分享> > 单机锁和分布式锁及实现

单机锁和分布式锁及实现

作者:互联网

目录

单机锁

锁是解决并发问题的一种手段,从操作系统到应用代码都有它的身影。

  1. 单核时期,同一时间只能做一件事,大家依序执行:顺序执行;
  2. 单核性能提高了,事情的过程太慢了,核(cpu)只能干等了;
    • 为了不让核闲下来,人们发明了进程,用进程来对应一个任务,由操作系统来进行调度,采取分时的方式(把cpu的时间分成很多片段),一个时间片只能执行某个进程中的指令,从用户的角度来看,任务就是并发的在处理;
    • 然而一个进程中的子任务会因为某些子任务的长耗时而阻塞着,同时它无需依赖长耗时的子任务,如此是否能进一步提升进程的执行效率呢,如是发明了线程,进程中的子任务也并发执行了,为了解决数据的正确性问题,发明了互斥锁机制
  3. 现在多核了,更进一步的提效,任务可以实现并行了。

并发:cpu的时间通过调度器分配给任务执行,视觉上的并行;
并行:一个任务一个核,同时执行;
锁:解决数据正确性问题

独享锁

独享锁,互斥锁:锁⼀次只能被⼀个资源所持有,其他的按照设计的策略行事,java中Synchronized而言,会进入Entry set列表等待;

共享锁

共享锁,读写锁:锁一些可以被多个资源持有

读写锁中写写是互斥的

公平锁

公平锁是指按照申请锁的顺序来获取锁;
⾮公平锁是指获取锁的顺序并不是按照申请锁的顺序,有可能后申请的⽐先申请的优先获取锁,有可能会造成优先级反转或者饥饿现象。

乐观锁

对共享数据的操作很少或者不会发生修改的。采取的策略是更新数据的时候尝试更新,如果不对继续重试,如CAS。

分布式锁

原本单机的任务因业务发展需要分解到多机执行,又或者多机上的不同任务共享一部分业务数据等,如上文所述:解决数据正确性问题产生了分布式锁概念和编程策略。

利用现成的锁

如下利用数据库提供的锁:
创建一张锁表lockTable

  • 数据库的高可用性保证,集群化;
  • 锁拥有者挂了,锁无法释放,额外增加任务超时清理锁数据;
  • 锁的重入,可以增加一列标识所有者身份;
public boolean lock(){
    connection.setAutoCommit(false)
    while(true){
        try{
            result = select * from lockTable where ... for update;
            if(result==null){
                return true;
            }
        }catch(Exception e){
        }
        sleep(1000);
    }
    return false;
}
public void unlock(){
    connection.commit();
}
  • 数据库的高可用性保证,集群化;
  • 锁拥有者挂了,锁会自动释放;
  • select语句在执行失败时一直处于阻塞状态;
  • 锁的重入,可以增加一列标识所有者身份;
  • 一个排他锁长时间不提交,占用数据库连接资源;

Redis

基于redis的事务性操作实现分布式锁。

加锁然后设置过期时间,防止任务挂了锁没释放:

127.0.0.1:6379> SETNX lock $uuid    // 加锁
(integer) 1
127.0.0.1:6379> EXPIRE lock 10  // 10s后自动过期
(integer) 1

要保证两条命令的事务性操作(要么全成功要么全失败)

在2.6.12 之后,redis扩展了SET命令参数,用这一条命令就可以了

// 一条命令保证原子性执行
127.0.0.1:6379> SET lock $uuid EX 10 NX
OK

$uuid:加锁时设置只有自己知道的标识,防止锁释放错误

// 锁是自己的,才释放
if(redis.get("lock") == $uuid){
  redis.del("lock")
}

上述释放锁是两个命令执行的,存在事务性问题,可以通过Lua脚本来解决:

// 判断锁是自己的,才释放
if redis.call("GET",KEYS[1]) == ARGV[1]
then
    return redis.call("DEL",KEYS[1])
else
    return 0
end

redis 处理每一个请求是单线程执行的,在执行一个 Lua 脚本时,其它请求必须等待,直到这个 Lua 脚本处理完成,这样一来,GET + DEL 之间就不会插入其它命令了,要么DEL成功,要么就是没有DEL,客户端添加确认成功与否逻辑即可

锁的过期时间不好评估问题
可以设计这样的方案:加锁时,先设置一个过期时间,然后我们开启一个守护线程,定时去检测这个锁的失效时间,如果锁快要过期了,操作共享资源还未完成,那么就自动对锁进行续期,重新设置过期时间。

库Redisson已封装实现

redis集群化可以参考Redis的作者提出一种解决方案Redlock。

zookeeper

基于zookeeper临时有序节点可以实现分布式锁。
过程如下:

  1. 客户端 1 和 2 都尝试创建临时节点,例如 /lock;
  2. 假设客户端 1 先到达,则加锁成功,客户端 2 加锁失败;
  3. 客户端 1 操作共享资源;
  4. 客户端 1 删除 /lock 节点,释放锁;

使用 Zookeeper,无法保证进程 GC、网络延迟异常场景下的安全性。

客户端与zk服务器维护一个Session,这个Session会依赖客户端定时心跳来维持连接,如果zk长时间收不到客户端的心跳,就认为这个 Session 过期了,会把这个临时节点删除。

参考:
https://blog.csdn.net/wuzhiwei549/article/details/80692278
https://mp.weixin.qq.com/s/JwXtmEzlHfkMhaoCWzIWJQ

标签:执行,加锁,单机,lock,锁及,redis,任务,分布式,客户端
来源: https://blog.csdn.net/m0_60725291/article/details/119480664