单系统的时候可以通过 同步锁等机制实现,但是多个服务器多个进程如何实现呢。首先看一下分布式锁的必要条件:
1)原子性:加锁和释放锁的操作必须满足原子性
2)不会产生死锁,有各种原因会导致锁没有被释放从而产生死锁
3)互斥性,某个时间只能有一个线程占有锁,其他线程处于阻塞状态
4)可重入性,也就是释放锁的线程可以再次获取该锁(这个涉及偏向锁等知识)
实现的思路:
利用 Jedis 的 setnx() 方法,设置键值,如果键已经存在,则返回0 。多个线程同时设置,但只有一个能设置成功,也就是返回 1,返回1的线程就获取到分布锁,处理完业务后,需要删除设置的键(要注意只能删除自己所设置的键,删除的时候需要判断此时的锁是不是自己的,有可能要删除的时候,刚好过期了,这个键是另一个线程设置的)
public Long setnx(final String key, final String value) { checkIsInMultiOrPipeline(); client.setnx(key, value); return client.getIntegerReply(); }
但是, 获取锁的线程有可能意外挂掉,此时锁并没有被释放,就会产生死锁。随着阻塞线程的增加堆积,最终导致系统崩溃。此时,可以让获取锁的线程主动设置键的过期时间,但是要注意的是,键的过期时间必须要大于业务处理时间,否则业务没处理完,当有新的线程获取到锁,也会导致问题。
public Long expire(final String key, final int seconds) { checkIsInMultiOrPipeline(); client.expire(key, seconds); return client.getIntegerReply(); }
所以关键问题点在于,这个业务时间和锁的过期时间怎么设置?可以从两个方面考虑,业务不复杂的时候,保证业务时间<过期时间。第二是获取锁的线程开启一个守护线程,用来给快过期的锁“续航”(守护线程执行expire指令)
总结
Redis 来实现一个分布式同步锁的方式,其特点是:
- 加锁和释放锁是原子性的
- 满足互斥性,同一个时刻只能有一个线程可以获取锁和释放锁
- 利用 Redis 的 ttl机制和守护进程的方式来保证不会出现死锁
以上的方案中,我们是假设 Redis 服务端是单集群且高可用的,忽视了以下的问题:
如果某一时刻 Redis master 节点发生了故障,集群中的某个 slave 节点变成 master 节点,在故障迁移(failover)过程中可能出现原 master 节点上的锁没有及时同步到 slave 节点,导致其他线程同时获得锁。对于这个问题,可以参考 Redis 官方推出的 redlock 算法,但是比较遗憾的是,该算法也没有很好地解决锁过期的问题。(PS:不过这种不安全也仅仅是在主从发生 failover 的情况下才会产生,而且持续时间极短,业务系统多数情况下可以容忍。)
原文:https://www.cnblogs.com/wengle520/p/12484931.html