Redis 可以使用它的原子操作实现分布式锁,基本思路是在 Redis 中创建一个键值对,其中键表示锁的名称,值表示锁的持有者和过期时间。如果多个线程或进程尝试获取同一个锁,则只有一个线程或进程能够成功获取锁,其他线程或进程需要等待锁的释放。
以下是 Redis 实现分布式锁的简单场景示例代码:
public class RedisDistributedLock { private Jedis jedis; private String lockKey; private int expireTime = 60; // 锁的默认过期时间(秒) private int timeout = 10; // 尝试获取锁的超时时间(秒) private boolean locked = false; // 是否已经获取到了锁 public RedisDistributedLock(Jedis jedis, String lockKey) { this.jedis = jedis; this.lockKey = lockKey; } public RedisDistributedLock(Jedis jedis, String lockKey, int expireTime) { this(jedis, lockKey); this.expireTime = expireTime; } public RedisDistributedLock(Jedis jedis, String lockKey, int expireTime, int timeout) { this(jedis, lockKey, expireTime); this.timeout = timeout; } /** * 尝试获取锁,如果获取成功返回 true,否则返回 false */ public boolean tryLock() { long start = System.currentTimeMillis(); while ((System.currentTimeMillis() - start) / 1000 < timeout) { // 尝试设置锁的值,如果成功则返回 true String result = jedis.set(lockKey, String.valueOf(System.currentTimeMillis() + expireTime), "NX", "EX", expireTime); if ("OK".equals(result)) { locked = true; return true; } try { Thread.sleep(100); // 等待一段时间后再重试 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } return false; } /** * 释放锁 */ public void unlock() { if (locked) { jedis.del(lockKey); locked = false; } } }
tryLock 方法尝试获取锁,如果获取成功则返回 true,否则返回 false。在 tryLock 方法中,使用 Redis 的 set 方法来设置锁的值。如果设置成功,说明当前线程或进程获得了锁,设置失败则说明其他线程或进程已经持有了锁。为了避免死锁,需要设置锁的过期时间,如果持有锁的线程或进程意外退出,锁会在过期时间之后自动释放。
unlock 方法用于释放锁,只有获得锁的线程或进程才能够释放锁。在 unlock 方法中,使用 Redis 的 del 方法删除锁的键值对。
注意:Redis 分布式锁也存在一些问题,例如死锁、误删除等。
如何避免这些问题?
1、死锁:如果一个线程或进程在获取锁之后挂了,其他线程或进程会一直等待该锁的释放,导致死锁。为了避免死锁,需要设置合理的锁超时时间,当锁超时后自动释放。
2、误删除:如果一个线程或进程在释放锁之前,锁的超时时间已经过期,那么其他线程或进程可能会误认为该锁已经被释放,从而删除了该锁的键值对,导致其他线程或进程无法获得锁。为了避免误删除,可以在释放锁时先判断当前线程或进程是否持有该锁。
3、粗粒度锁:Redis 分布式锁只能实现粗粒度锁,无法实现细粒度锁。例如,如果需要锁定一条记录的某个字段,使用 Redis 分布式锁无法实现,需要使用其他方式实现。
4、高并发性能:Redis 分布式锁在高并发场景下可能会存在性能问题,需要合理设置锁的超时时间,并使用高性能的 Redis 集群。