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实例的分布式锁。
Config config = new Config();
config.useClusterServers().addNodeAddress("redis://192.168.31.101:7001")
.addNodeAddress("redis://192.168.31.101:7002")
.addNodeAddress("redis://192.168.31.101:7003")
.addNodeAddress("redis://192.168.31.102:7001")
.addNodeAddress("redis://192.168.31.102:7002")
.addNodeAddress("redis://192.168.31.102:7003");
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("anyLock");
lock.lock();
lock.unlock();
它的 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 模式,放在一个事务执行。
- 第一步查询锁定一行数据:select * from table where id = 1 for update 锁定 id=1 的行。
- 第二步:操作和这一行数据关联的业务,比如:insert, 或者 update 等等。
- 第三步:提交事务。id=1 的行解锁。
当然,在事务中,只有SELECT … FOR UPDATE 或LOCK IN SHARE MODE 同一笔数据时会等待其它事务结束后才执行,一般SELECT … 则不受此影响。
4,基于Memcached的分布式锁实现,和Redis最简单的分布式锁实现功能效果一致。
Memcached的分布式完全是依赖客户端的一致性哈希算法来达到分布式的存储,在Memcached服务端,所有的操作都是原子性的。
我们利用add函数,add会添加第一个到达的值,并返回true,后续的添加则都会返回false。
利用这个原理,可以先定义一个 锁 LockKEY ,add 成功的认为是得到锁。并且设置[过期超时] 时间,保证宕机后,也不会死锁。