单Redis实例实现分布式锁

单Redis实例实现分布式锁

参考:

Redis 中文官方文档:http://www.redis.cn/topics/distlock.html

存在竞态

安全和活性失效保障

按照我们的思路和设计方案,算法只需具备3个特性就可以实现一个最低保障的分布式锁。

  1. 安全属性(Safety property): 独享(相互排斥)。在任意一个时刻,只有一个客户端持有锁。

  2. 活性A(Liveness property A): 无死锁。即便持有锁的客户端崩溃(crashed)或者网络被分裂(gets partitioned),锁仍然可以被获取。

  3. 活性B(Liveness property B): 容错。 只要大部分Redis节点都活着,客户端就可以获取和释放锁

获取锁使用命令:

SET resource_name my_random_value NX PX 30000

key 要全局唯一,而value 要是随机字符串,可以使用UUID(可能存在相同,最好使用自旋锁生成),因为后面要判断value是否是自己的value,来删除key

删除锁

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

1、引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis Lettuce 模式 连接池 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

配置 redis

# 配置redis
spring.redis.host=47.107.243.178
spring.redis.port=6379
#spring.redis.password=123456
spring.redis.database=0
spring.redis.timeout=3000ms
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=20
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.lettuce.pool.max-wait=3000ms
# 连接池中的最大空闲连接(负数没有限制)
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0

2、编写 RedisLock

/**
 * redis 单体 分布式锁
 */
@Component
public class RedisLock {
    
    

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 加锁
     * */
    public boolean tryLock(String key, String value) {
    
    
        int expirationTIME = 10;
        Boolean isLocked = stringRedisTemplate.opsForValue().
                setIfAbsent(key, value, expirationTIME, TimeUnit.SECONDS);
        if (isLocked == null) {
    
    
            return false;
        }
        return isLocked;
    }
    public boolean tryLock(String key, String value,int  expirationTime,TimeUnit timeUnit) {
    
    
        Boolean isLocked = stringRedisTemplate.opsForValue().
                setIfAbsent(key, value, expirationTime, timeUnit);
        if (isLocked == null) {
    
    
            return false;
        }
        return isLocked;
    }

    /**
     * 解锁
     * */
    public Boolean unLock(String key, String value) {
    
    
        // 执行 lua 脚本
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        // 指定 lua 脚本
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/unLock.lua")));
        // 指定返回类型
        redisScript.setResultType(Long.class);
        // 参数一:redisScript,参数二:key列表,参数三:arg(可多个)
        Long result = stringRedisTemplate.execute(redisScript, Collections.singletonList(key), value);
        return result != null && result > 0;
    }

}

3、注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RedLock {
    
    
    //调用接口的名称
    String key();

    // 过期时间
    int expiration() default 10;

    // 时间类型
    TimeUnit timeUnit() default TimeUnit.SECONDS;
}

4、AOP 配置redis 分布式锁

/**
 * redis 分布式锁 aop
 * */
@Aspect
@Slf4j
@Component
public class RedisLockAspect {
    
    

    @Autowired
    RedisLock redisLock;

    /**
     * 配置切入点
     */
    @Pointcut("@annotation(cn.patrick.adminpatrick.admin.annotation.RedLock)")
    public void redisLockPointcut() {
    
    
        // 该方法无方法体,主要为了让同类中其他方法使用此切入点
    }

    @Around("redisLockPointcut()")
    public Object redisLockAround(ProceedingJoinPoint  joinPoint) throws Throwable {
    
    
        Object result = null;
        // 获取注解上的值
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        RedLock redLockAnnotation = method.getAnnotation(RedLock.class);
        String value = UUID.randomUUID().toString().replace("-", "");
        String key = redLockAnnotation.key();
        int expiration = redLockAnnotation.Expiration();
        Random random = new Random();
        try {
    
    
            // 自旋锁
            while (true) {
    
    
                if (redisLock.tryLock(key,value,expiration,TimeUnit.SECONDS)) {
    
    
                    //log.info("[{}]成功获取锁", key);
                    break;
                }
                //log.info("[{}]获取锁失败",key);
                int sleepTime = random.nextInt(500);
                Thread.sleep(sleepTime);
            }
            result = joinPoint.proceed();

        } catch (Exception e) {
    
    
            throw new RuntimeException(e.getMessage());
        } finally {
    
    
            redisLock.unLock(key,value);
        }
        return result;
    }
}

5、测试

猜你喜欢

转载自blog.csdn.net/Dig_hoof/article/details/108444246