redis分布式锁redission生产实战

一、为什么要用分布式锁?

        为什么要用分布式锁,首先要搞懂为什么要用锁?程序中使用锁通常是为了实现并发控制和保证数据的一致性,在多线程环境中,多个线程同时访问和修改共享数据,如果没有加锁,可能会导致不正确的结果和数据竞争。

        在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLcok或synchronized)进行互斥控制,但是随着业务的发展需要,原单机系统演化为分布式系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,这时候就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。

二、分布式锁需要具备哪些条件?

  • 互斥性:多线程中,同一时间只能有一个请求执行操作
  • 防止死锁: 分布式锁应该在锁的持有者异常退出或崩溃时能够自动释放
  • 高可用性: 在节点故障时需要能正常工作
  • 高性能:  高并发下保证获取锁的性能
  • 可重入性:允许同一个线程持有锁的情况下多次获取同一个锁,而不会出现死锁或阻塞的情况。这对于递归函数调用等很有用。

三、为什么使用redission做分布式锁?

  1. 高性能:Redisson 是基于 Redis 的分布式对象框架,利用 Redis 的高性能和高并发特性,可以实现高效的分布式锁。Redis 是一个内存数据库,具有快速的读写速度和低延迟,适合用作分布式锁的存储介质。

  2. 分布式支持:Redisson 提供了分布式锁的实现,可以在多个应用程序或多个服务器之间实现分布式的锁机制。它利用 Redis 的分布式特性,可以在不同的节点上协调锁的获取和释放,确保在分布式环境中的数据一致性和并发控制。

  3. 锁的可重入性:Redisson 支持可重入锁,即同一个线程可以多次获取同一个锁而不会发生死锁。这对于一些复杂的业务场景来说非常有用,可以避免由于同一个线程多次获取锁而导致的并发问题。

  4. 锁的失效处理:Redisson 提供了锁的自动失效处理机制,可以设置锁的过期时间,同时redission有Watch Dog机制,确保即使在获取锁后发生异常或程序意外终止时,锁也能自动释放,避免锁的长时间占用。

  5. 支持多种锁类型:Redisson 提供了多种类型的分布式锁,包括公平锁、非公平锁、可重入锁、红锁、读写锁等。这些不同类型的锁可以根据具体的业务需求选择使用,提供了更灵活的锁机制。

四、rediss锁实现

1. 引入maven


org.redisson
redisson
3.16.0

 2. 配置redission

@Configuration @ConditionalOnClass({Redisson.class, RedisOperations.class}) @AutoConfigureBefore(RedisAutoConfiguration.class) @EnableConfigurationProperties({RedissonProperties.class, RedisProperties.class})

public class RedisConfig{
    @Bean public RedissonClient redissonClient() throws IOException {

         String propertiesConfig = redissonProperties.getConfig();

         Config config= Config.fromYAML(propertiesConfig);

         return Redisson.create(config);

         }

}

 3. 配置application.yml

redis:
  database: 0
  host: 127.0.0.1
  port: 6379
  password: yurheh123
  timeout: 6000ms  # 连接超时时长(毫秒)
  lettuce:
    shutdown-timeout: 100 # 关闭超时时间k
    pool:
      maxTotal: 20
      maxWaitMillis: 3000
      max-active: 10000  # 连接池最大连接数(使用负值表示没有限制)
      max-wait: -1ms      # 连接池最大阻塞等待时间(使用负值表示没有限制)
      max-idle: 10      # 连接池中的最大空闲连接
      min-idle: 5       # 连接池中的最小空闲连接
  redisson:
    config: |
      singleServerConfig:
       idleConnectionTimeout: 10000
       connectTimeout: 10000
       timeout: 3000
       retryAttempts: 3
       retryInterval: 1500
       password: yurheh123
       subscriptionsPerConnection: 5
       clientName: null
       address: "redis://127.0.0.1:6379"
       subscriptionConnectionMinimumIdleSize: 1
       subscriptionConnectionPoolSize: 50
       subscriptionConnectionPoolSize: 50
       connectionMinimumIdleSize: 10
       connectionPoolSize: 64
       database: 0
       dnsMonitoringInterval: 5000
      threads: 16
      nettyThreads: 32
      codec: ! {}
      transportMode: "NIO"

4. 封装redission锁

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {

    /**
     * 对象写法
     *
     * @RedisLock("#stock.id") public boolean func(Stock stock) {}
     * 

* Map写法 * @RedisLock("#param[popularizeId] +'_'+ #param[wbUserId]") * public boolean func(Map param) {} *

* List写法 * @RedisLock("#list[0]") public boolean func(List list) {} *

* 联合写法 * @RedisLock("#list[1]+'_'+#list[2]") public boolean func(List list) {} */ java.util.concurrent.TimeUnit unit() default java.util.concurrent.TimeUnit.SECONDS; int waitTime() default 0; //未获得锁的线程 等待锁的时间 超过此时间 放弃等待 boolean isReleaseLock() default true;//业务执行完 是否释放锁 不释放 锁将在leaseTime时间后自动失效 String value(); }

@Slf4j
@Aspect
@Component
@Order(PriorityOrdered.HIGHEST_PRECEDENCE)
public class RedisLockAspect {
    private static final String SPEL_MARK = "#";
    private static final String lockKey = "redisLock";

    @Value("${spring.application.name}")
    private String name;


    private final RedissonClient redissonClient;

    public RedisLockAspect(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    @Pointcut("@annotation(bdhj.common.annotation.RedisLock)")
    public void aspectjMethod() {

    }

    @Around("aspectjMethod()")
    public Object aroundAdvice(ProceedingJoinPoint point) throws Throwable {
        Object[] args = point.getArgs();
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        RedisLock redisLock = method.getAnnotation(RedisLock.class);
        String redisKey = name + ":" + lockKey + ":" + parseExpression(redisLock.value(), method, args);
        RLock lock = redissonClient.getLock(redisKey);
        System.out.println(redisKey);
        boolean isLock = false;
        Object result;
        try {
            isLock = lock.tryLock(redisLock.waitTime(), redisLock.unit());
            if (!isLock) {
                throw new RRException("正在处理 请勿频繁操作!");
            }
            log.info("加锁成功:{}", redisKey);
            result = point.proceed();
        } finally {
            //释放锁
            if (isLock && redisLock.isReleaseLock()) {
                try {
                    if (lock.isLocked() & lock.isHeldByCurrentThread()) {
                        lock.unlock();
                        log.info("解锁成功:{}", redisKey);
                    }
                } catch (IllegalMonitorStateException e) {
                    log.warn("断点或其他 阻塞程序时间超过'RedisLockUtil.tryLock上锁后自动释放锁时间' 导致", e);
                }
            }
        }
        return result;
    }


    private String parseExpression(String redisValue, Method method, Object[] args) {
        if (StringUtils.isBlank(redisValue) || !redisValue.contains(SPEL_MARK)) {
            throw new RRException("加锁请求错误");
        }
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = discoverer.getParameterNames(method);
        if (ObjectUtils.isEmpty(parameterNames)) {
            throw new RRException("加锁请求错误");
        }
        ExpressionParser expressionParser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();
        //把方法参数放入SPEL上下文中
        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], args[i]);
        }
        return expressionParser.parseExpression(redisValue).getValue(context, String.class);
    }
}

 5. 分布式锁的使用

         建议在使用时只需要将上文封装的RedisLock注解到controller的具体方法上,如果锁注解加到impl实现类的方法中,会出现锁和Transactional事务的冲突,导致锁不起作用。

        redission独特的Watch Dog机制可以保证锁不会提前释放并且当服务宕机后仍然会释放锁,不会导致死锁,但是,前提是不能设置锁过期时间,设置锁过期时间后,Watch Dog就会失去作用,具体可以研究底层的lua脚本。

 ————————————————
版权声明:本文为CSDN博主「暗夜里的一束光」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

你可能感兴趣的:(redis,分布式,数据库)