2 分布式锁

Wu Jun 2019-12-25 15:59:03
11 分布式 > 03 分布式存储 > 03 Redis

1 Redisson

直接上官方推荐Redisson

<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.9.0</version>
</dependency>

配置

@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Value("${spring.redis.password}")
private String password;

@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient() {
    Config config = new Config();
    config.useSingleServer()
            .setAddress("redis://" + host + ":" + port)
            .setPassword(password);
    return Redisson.create(config);
}

使用

@Autowired
private RedissonClient redissonClient;

public Boolean lock(Long userId) throws InterruptedException {
    String lockKey = generateLockKey(userId);
    RLock lock = redissonClient.getLock(lockKey);
    return lock.tryLock(TIME_OUT_2_SECONDS, EXPIRE_20_SECONDS, TimeUnit.SECONDS);
}

2 自己实现

/**
 * @author wujun
 * @date 2018-10-31 15:37
 */
@Slf4j
@Component
public class UserAssetLock {

    private static final String LOCK_KEY = "lock:asset:%d";
    private static final Long EXPIRE_20_SECONDS_IN_MILLIS = 20 * 1000L;
    private static final Long TIME_OUT_2_SECONDS_IN_NANOS = 2 * 1000 * 1000000L;
    private static final Long SLEEP_TIME_200_MILLIS = 200L;

    @Autowired
    RedisTemplate<Object, Object> redisTemplate;

    public Boolean lock(Long userId) {
        Long startTime = System.nanoTime();
        Boolean getLock = false;
        while ((System.nanoTime() - startTime) < TIME_OUT_2_SECONDS_IN_NANOS) {
            //当前时间未超出等待时间阈值则反复尝试获取锁
            getLock = tryGetLock(userId);
            if (getLock) {
                break;
            }
            tryUnLockInvalidkey(userId);
            trySleep200Millis();
        }
        return getLock;
    }

    /**
     * 释放锁
     * @param userId userId
     */
    public void unLock(Long userId) {
        String lockKey = generateLockKey(userId);
        if (isLocked(userId)) {
            redisTemplate.delete(lockKey);
        }
    }

    /**
     * 尝试获取锁
     * @param userId userId
     * @return 是否获取锁
     */
    private Boolean tryGetLock(Long userId){
        String lockKey = generateLockKey(userId);
        Long expireMillis = System.currentTimeMillis() + EXPIRE_20_SECONDS_IN_MILLIS;
        Boolean getLock = redisTemplate.opsForValue().setIfAbsent(lockKey, expireMillis);
        if (getLock) {
            System.out.println(System.currentTimeMillis());
            redisTemplate.expire(lockKey, EXPIRE_20_SECONDS_IN_MILLIS, TimeUnit.MILLISECONDS);
        }
        return getLock;
    }

    /**
     * 判断是否上锁
     * @param userId userId
     * @return 是否上锁
     */
    private Boolean isLocked(Long userId) {
        String lockKey = generateLockKey(userId);
        return redisTemplate.hasKey(lockKey);
    }

    /**
     * 尝试释放无效锁
     * @param userId userId
     */
    private void tryUnLockInvalidkey(Long userId) {
        if (isInvalid(userId)) {
            unLock(userId);
        }
    }

    /**
     * 判断是否为无效锁
     * 锁存在,超过应失效时间却未失效,则为无效锁
     *
     * @param userId userId
     * @return 是否为无效锁
     */
    private Boolean isInvalid(Long userId) {
        Boolean isInvalid = false;
        if (isLocked(userId)) {
            //确保key存在
            Long shouldExpireMillis = getExpireMillis(userId);
            if (null == shouldExpireMillis) {
                //key存在但过期时间为null
                isInvalid = true;
            } else {
                //当前时间大于应过期的时间,说明key失效了但还没删除,可能是设置key有效期失败导致
                isInvalid = System.currentTimeMillis() > shouldExpireMillis;
            }
        }
        return isInvalid;
    }

    /**
     * 获取应过期时间毫秒值
     * @param userId userId
     * @return 应过期时间
     */
    private Long getExpireMillis(Long userId) {
        String lockKey = generateLockKey(userId);
        return (Long) redisTemplate.opsForValue().get(lockKey);
    }

    /**
     * 休眠200毫秒
     */
    private void trySleep200Millis() {
        try {
            Thread.sleep(SLEEP_TIME_200_MILLIS);
        } catch (InterruptedException e) {
            log.error("获取分布式锁休眠被中断:", e);
        }
    }

    /**
     * 生成key
     * @param userId userId
     * @return key
     */
    private String generateLockKey(Long userId) {
        return String.format(LOCK_KEY, userId);
    }
}