赞
踩
setnx、Redisson、RedLock 都可以实现分布式锁,从易到难得排序为:setnx < Redisson < RedLock。一般情况下,直接使用 Redisson 就可以啦,有很多逻辑框架的作者都已经考虑到了。
下面的锁实现可以用在测试或者简单场景,但是它存在以下问题,使其不适合用在正式环境。
public class RedisLock { @Autowired private StringRedisTemplate redisTemplate; /** * 加锁 */ private boolean tryLock(String key) { Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS); return BooleanUtil.isTrue(flag); } /** * 解锁 */ private void unlock(String key) { redisTemplate.delete(key); } }
lua 脚本是原子的,不管写多少 lua 脚本代码,redis 都是通过一条命令去执行的。
下述代码使用了 lua 脚本进行加锁/解锁,保证了加锁和解锁的时候都是原子性的,是一种相对较好的 Redis 分布式锁的实现方式。
它支持获得锁的线程才能释放锁,如果线程 1 因为锁过期而丢掉了锁,然后线程 2 拿到了锁。此时线程 1 的业务代码执行完以后,也无法释放掉线程 2 的锁,解决了误删除的问题。
public class RedisLock { private final StringRedisTemplate redisTemplate; public RedisDistributedLock(StringRedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } public boolean tryLock(String lockKey, String lockValue, long expireTimeInSeconds) { try { //加锁成功返回 true,加锁失败返回 fasle。效果等同于 redisTemplate.opsForValue().setIfAbsent String luaScript = "if redis.call('set', KEYS[1], ARGV[1], 'NX', 'EX', ARGV[2]) then return 1 else return 0 end"; RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class); Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), lockValue, String.valueOf(expireTimeInSeconds)); return result != null && result == 1; } catch (Exception e) { // Handle exceptions return false; } } public void unlock(String lockKey, String lockValue) { try { //拿到锁的线程才可以释放锁,lockValue 可以设置为 uuid。 String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class); redisTemplate.execute(redisScript, Collections.singletonList(lockKey), lockValue); } catch (Exception e) { // Handle exceptions } } }
Redisson 是一个基于 Java 的客服端,通过 Redisson 我们可以快速安全的实现分布式锁。Redisson 框架具有可重入锁的支持、分布式锁的实现、锁的自动续期、红锁支持等多种特点,给我们开发过程中带来了极大的便利。
@Component public class RedisLock { @Resource private RedissonClient redissonClient; /** * lock(), 拿不到lock就不罢休,不然线程就一直block */ public RLock lock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.lock(); return lock; } /** * leaseTime为加锁时间,单位为秒 */ public RLock lock(String lockKey, long leaseTime) { RLock lock = redissonClient.getLock(lockKey); lock.lock(leaseTime, TimeUnit.SECONDS); return null; } /** * timeout为加锁时间,时间单位由unit确定 */ public RLock lock(String lockKey, TimeUnit unit, long timeout) { RLock lock = redissonClient.getLock(lockKey); lock.lock(timeout, unit); return lock; } /** * @param lockKey 锁 key * @param unit 单位 * @param waitTime 等待时间 * @param leaseTime 锁有效时间 * @return 加锁成功? true:成功 false: 失败 */ public boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime) { RLock lock = redissonClient.getLock(lockKey); try { return lock.tryLock(waitTime, leaseTime, unit); } catch (InterruptedException e) { return false; } } /** * unlock */ public void unlock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); if (lock.isLocked() && lock.isHeldByCurrentThread()) { lock.unlock(); } } /** * unlock * @param lock 锁 */ public void unlock(RLock lock) { lock.unlock(); } }
RedLock 又叫做红锁,是 Redis 官方提出的一种分布式锁的算法,红锁的提出是为了解决集群部署中 Redis 锁相关的问题。
比如当线程 A 请求锁成功了,这时候从节点还没有复制锁。此时主节点挂掉了,从节点成为了主节点。线程 B 请求加锁,在原来的从节点(现在是主节点)上加锁成功。这时候就会出现线程安全问题。
下图是红锁的简易思路。红锁认为 (N / 2) + 1 个节点加锁成功后,那么就认为获取到了锁,通过这种算法减少线程安全问题。简单流程为:
@Service public class MyService { private final RedissonClient redissonClient; @Autowired public MyService(RedissonClient redissonClient) { this.redissonClient = redissonClient; } public void doSomething() { RLock lock1 = redissonClient.getLock("lock1"); RLock lock2 = redissonClient.getLock("lock2"); RLock lock3 = redissonClient.getLock("lock3"); RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3); redLock.lock(); try { // 业务逻辑 } finally { redLock.unlock(); } } }
我是 xiucai,一位后端开发工程师。
如果你对我感兴趣,请移步我的个人博客,进一步了解。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。