Java分布式锁的四种实现方式(Redis,Zookeeper,Mysql,Memcached)

释放双眼,带上耳机,听听看~!

1,使用Redis做分布式锁:

利用SETNX添加一个锁,并设置锁的释放时间。

问题:

a,某个机器实例的任务执行时长超时了,超过了锁释放的时间,会造成其他机器实例获取到该锁并执行任务。任务被同时执行。

b,Redis的部署模式:如果是单实例,或者是master-slave模式。 Redis可能会挂(概率很小),或者只是针对master节点加锁,如果master节点故障,发生master,slave切换,锁丢失。

 

解决方案:

1,针对问题a

* 如果任务时间超时,可以设置告警,人工进行干预。

* 或者在当前机器实例上,频繁的去get锁,如果锁属于自己,则延长锁的释放时间。

2,针对问题b

* 如果是master-slave模式,在每个节点都建立锁,如果是Cluster模式,在超过一半的节点上都建立锁。

 

如果业务对分布式锁出错可以容忍,不是那么强烈的一致性,那就使用Redis的简单实现,因为Redis可以支撑

很高的并发。

比如一些周期性定时任务,例如同步数据的,处理异常情况等等,避免多个实例跑浪费机器资源的。

 

如果业务对分布式锁有一定要求,当然不是100%的强一致性,而且并发特别高,可以考试使用

Redisson, 他具有很高的数据一致性,可以达到99.99%,而且性能特别好。

可以使用Redisson的多个Redis实例的分布式锁。


1
2
3
4
5
6
7
8
9
10
11
12
1Config config = new Config();
2config.useClusterServers().addNodeAddress("redis://192.168.31.101:7001")
3.addNodeAddress("redis://192.168.31.101:7002")
4.addNodeAddress("redis://192.168.31.101:7003")
5.addNodeAddress("redis://192.168.31.102:7001")
6.addNodeAddress("redis://192.168.31.102:7002")
7.addNodeAddress("redis://192.168.31.102:7003");
8RedissonClient redisson = Redisson.create(config);
9RLock lock = redisson.getLock("anyLock");
10lock.lock();
11lock.unlock();
12

它的 API 中的 Lock 和 Unlock 即可完成分布式锁:

Redisson 中有一个 Watchdog 的概念,翻译过来就是看门狗,它会在你获取锁之后,每隔 10s 帮你把 Key 的超时时间设为 30s。

Redisson 的“看门狗”逻辑保证了没有死锁发生。(如果机器宕机了,看门狗也就没了。此时就不会延长 Key 的过期时间,到了 30s 之后就会自动过期了,其他线程可以获取到锁)

 

2,基于Zookeeper实现分布式锁

ZK:提供配置管理,分布式协同,命名的中心化服务。

ZK的节点类型:持久节点,临时节点

顺序节点:持久顺序节点,临时顺序节点

 

分布式锁实现原理:为锁创建一个持久化节点,例如:/lock

在这个节点下,每个Client创建临时顺序节点,如:/lock/client-001,/lock/client-002,/lock/client-003

临时节点,如果Client会话结束,或者断开,ZK自动删除临时节点。

Client获取/lock下所有的子节点列表,判断当前创建的子节点列表序号是不是最小,如果是,认为获取该锁。

如果不是,监听比自己小一个的节点。直到获得节点变更通知后重复检查节点序号。

 

这里,为什么监听比自己小一个的节点,而不是最小序号的节点,因为,如果/lock下有1000个节点的话,

当最小节点有变更通知,ZK需要通知其它999个节点。ZK会发生阻塞,Client也没有必要同时去争抢锁。

 

3,基于Mysql数据库实现分布式锁:

悲观锁:select … for update

首先设置Mysql为非auto commit 模式,放在一个事务执行。

  1. 第一步查询锁定一行数据:select * from table where id = 1 for update 锁定 id=1 的行。
  2. 第二步:操作和这一行数据关联的业务,比如:insert, 或者 update 等等。
  3. 第三步:提交事务。id=1 的行解锁。

当然,在事务中,只有SELECT … FOR UPDATE 或LOCK IN SHARE MODE 同一笔数据时会等待其它事务结束后才执行,一般SELECT … 则不受此影响。

 

4,基于Memcached的分布式锁实现,和Redis最简单的分布式锁实现功能效果一致。

Memcached的分布式完全是依赖客户端的一致性哈希算法来达到分布式的存储,在Memcached服务端,所有的操作都是原子性的。

我们利用add函数,add会添加第一个到达的值,并返回true,后续的添加则都会返回false。

利用这个原理,可以先定义一个 锁 LockKEY ,add 成功的认为是得到锁。并且设置[过期超时] 时间,保证宕机后,也不会死锁。

 

 

 

 

 

 

给TA打赏
共{{data.count}}人
人已打赏
安全运维

OpenSSH-8.7p1离线升级修复安全漏洞

2021-10-23 10:13:25

安全运维

设计模式的设计原则

2021-12-12 17:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索