Redisson实现分布式锁

902 阅读2分钟

1. Redisson分布式锁简介

在多线程/多节点场景下,普通的加锁(比如Synchronized或Lock)已经不能满足要求了,需要用到分布式锁。
Redisson帮我们实现了很多细节,使得我们可以很方便地使用分布式锁:

  1. Redisson的所有操作都是通过lua脚本执行的,保证了操作的原子性;
  2. Redisson会默认设置一个key的过期时间为30s,避免加锁后客户端宕机导致的死锁问题;
  3. Redisson提供了一个看门狗机制(watchdog),它会每隔10s把超时时间重新设置为30s,防止某个客户端持有锁超过30s导致key过期,从而被其他线程获取到分布式锁;(如果自己设置了过期时间,就不会触发看门狗机制)
  4. Redisson提供了红锁(RedLock)的实现,用于实现多个Redis实例上的分布式锁:必须在大多数Redis实例上都成功创建锁,才能算整体的RedLock加锁成功。

2. Redisson分布式锁实例

public void deleteCount(String goodId) {
    // 使用UUID进行加锁,防止误解锁
    String uuid = UUID.randomUUID().toString();
    RLock lock = redissonClient.getLock(uuid);
    try {
        // 加锁,没拿到锁则进行自旋
        // redisson默认加锁时长为30s,每10s进行一次续期
        lock.lock();
        mapper.deleteCount(goodId);
    } finally {
        lock.unlock();
    }
}

在每个业务方法里写加解锁操作显得十分冗余,可以使用注解 + 切面的方式进行简化。

先定义一个注解:

/**
 * Redis分布式锁
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributedLock {
    // 锁名称
    String lockName() default "";

    // 过期时间
    int expire() default 0;

    // 时间单位
    TimeUnit timeUnit() default TimeUnit.SECONDS;
}

实现切面:

@Slf4j
@Aspect
@Component
public class DistributedLockAspect {

    @Resource
    private RedissonClient redissonClient;

    @Pointcut("@annotation(com.example.redisdemo.aop.DistributedLock)")
    public void pointCut() {
    }

    @Around("pointCut() && @annotation(distributedLock)")
    public Object distributedLockAspect(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) {
        // 使用UUID进行加锁,防止误解锁
        String uuid = UUID.randomUUID().toString();
        RLock lock = redissonClient.getLock(distributedLock.lockName() + "-" + uuid);
        try {
            // 加锁,没拿到锁则进行自旋
            if (distributedLock.expire() != 0) {
                // 使用注解内设置的过期时间
                lock.lock(distributedLock.expire(), distributedLock.timeUnit());
            } else {
                // redisson默认加锁时长为30s,每10s进行一次续期
                lock.lock();
            }
            return joinPoint.proceed();
        } catch (Throwable throwable) {
            log.error("Failed to get Distributed Lock caused by {}", throwable.getMessage());
        } finally {
            lock.unlock();
        }
        return null;
    }
}

最后使用的时候加一个注解就行:

@DistributedLock(lockName = "deleteCountLock", expire = 30, timeUnit = TimeUnit.SECONDS)
public void deleteCountWithAnnotation(String goodId) {
    mapper.deleteCount(goodId);
}