Spring Cloud:基于Redisson的分布式锁实现

什么是分布式锁

在分布式系统中,为了保证数据的一致性,我们通常需要很多的技术方案支持,比如分布式事务、分布式锁等。其中分布式锁主要是为了解决多线程下资源抢占的问题,原理和平常所讲的锁原理基本一致,目的就是确保在多个线程、进程(服务)并发时,只有一个线程、进程(服务)在同一刻操作这个业务。

分布式锁一般有以下三种实现:

  1. 基于数据库实现分布式锁;

  2. 基于缓存(Redis等)实现分布式锁;

  3. 基于Zookeeper实现分布式锁

本次我们重点讨论基于redis的分布式锁实现。

什么是Redisson

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。

关于Redisson项目的详细介绍可以在官方网站找到。

以下是Redisson的结构:

  • Redisson作为独立节点 可以用于独立执行其他节点发布到分布式执行服务 和 分布式调度任务服务 里的远程任务。

Spring Cloud:基于Redisson的分布式锁实现_第1张图片

Redisson的功能非常强大,这里仅仅用于实现分布式锁,后续我将进一步学习这个框架。

基于Redisson的分布式锁

关于Redisson的分布式锁原理(这里借用他人的一张图说明一下):

Spring Cloud:基于Redisson的分布式锁实现_第2张图片

1、锁机制

线程去获取锁,获取成功, 执行lua脚本,保存数据到redis数据库。

线程去获取锁,获取失败,一直通过while循环尝试获取锁,直到超时获取成功后,执行lua脚本,保存数据到redis数据库。

2、watch dog自动延期机制

如果负责储存这个分布式锁的Redisson节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

3、lua脚本

将业务逻辑进行封装在lua脚本中,然后发送给redis,而且由于redis是单线程的,从而保证业务逻辑执行的原子性

整合Spring Boot

为了在项目中可以自由引用,我创建了一个独立的common-lock项目,封装分布式锁相关的全部逻辑,目录如下:

Spring Cloud:基于Redisson的分布式锁实现_第3张图片

  • DistributedLock和DistributedLockAspect实现了分布式锁注解封装,简化使用

  • config.*目录实现了自定义配置,由于Redisson的配置略显繁琐,所以进行二次封装,只保留基本配置项

  • strategy.*目录实现四种Redis的部署连接方式,分别为:单机模式、集群模式、主从模式和哨兵模式

  • DistributedLockClient为分布式锁工具类

添加引用



      org.redisson
      redisson
      3.15.5



      org.springframework.boot
      spring-boot-starter-aop

Redisson配置

RedissonProperties

/**
 * @description: Redisson配置
 */
@Data
@ConfigurationProperties(prefix = "spring2go.redisson")
public class RedissonProperties {
    /**
     * redis主机地址,ip:port,多个用逗号(,)分隔
     */
    private String address;
    /**
     * 连接类型
     */
    private RedisConnectionType type;
    /**
     * 密码
     */
    private String password;
    /**
     * 数据库(默认0)
     */
    private int database;

    /**
     * 是否装配redisson配置
     */
    private Boolean enabled = true;
}

/**
 * @description: Redis连接方式
 */
@Getter
@AllArgsConstructor
public enum RedisConnectionType {
    /**
     * 单机部署方式(默认)
     */
    STANDALONE("standalone", "单机部署方式"),
    /**
     * 哨兵部署方式
     */
    SENTINEL("sentinel", "哨兵部署方式"),
    /**
     * 集群部署方式
     */
    CLUSTER("cluster", "集群方式"),
    /**
     * 主从部署方式
     */
    MASTERSLAVE("masterslave", "主从部署方式");

    /**
     * 编码
     */
    private final String code;
    /**
     * 名称
     */
    private final String name;
}

Redis连接策略

/**
 * @description: Redisson配置构建接口
 */
public interface RedissonConfigStrategy {

    /**
     * 根据不同的Redis配置策略创建对应的Config
     *
     * @param redissonProperties
     * @return Config
     */
    Config createRedissonConfig(RedissonProperties redissonProperties);

    /**
     * @description 获取redis连接类型
     */
    RedisConnectionType getType();
}

/**
 * @description: 单例模式
 */
@Slf4j
@Component
public class StandaloneRedissonConfigStrategy implements RedissonConfigStrategy {
    @Override
    public Config createRedissonConfig(RedissonProperties redissonProperties) {
        Config config = new Config();
        try {
            String address = redissonProperties.getAddress();
            String password = redissonProperties.getPassword();
            int database = redissonProperties.getDatabase();
            String redisAddr = RedissonConstant.REDIS_CONNECTION_PREFIX + address;
            config.useSingleServer().setAddress(redisAddr);
            config.useSingleServer().setDatabase(database);
            if (StringUtils.isNotEmpty(password)) {
                config.useSingleServer().setPassword(password);
            }
            log.info("初始化Redisson单机配置,连接地址:" + address);
        } catch (Exception e) {
            log.error("单机Redisson初始化错误", e);
            e.printStackTrace();
        }
        return config;
    }

    @Override
    public RedisConnectionType getType() {
        return RedisConnectionType.STANDALONE;
    }
}

/**
 * @description: 哨兵方式Redis连接配置
 */
@Slf4j
@Component
public class SentinelRedissonConfigStrategy implements RedissonConfigStrategy {
    @Override
    public Config createRedissonConfig(RedissonProperties redissonProperties) {
        Config config = new Config();
        try {
            String address = redissonProperties.getAddress();
            String password = redissonProperties.getPassword();
            int database = redissonProperties.getDatabase();
            String[] addrTokens = address.split(",");
            String sentinelAliasName = addrTokens[0];
            // 设置redis配置文件sentinel.conf配置的sentinel别名
            config.useSentinelServers().setMasterName(sentinelAliasName);
            config.useSentinelServers().setDatabase(database);
            if (StringUtils.isNotEmpty(password)) {
                config.useSentinelServers().setPassword(password);
            }
            // 设置哨兵节点的服务IP和端口
            for (int i = 1; i < addrTokens.length; i++) {
                config.useSentinelServers().addSentinelAddress(RedissonConstant.REDIS_CONNECTION_PREFIX + addrTokens[i]);
            }
            log.info("初始化哨兵方式Config,redisAddress:" + address);
        } catch (Exception e) {
            log.error("哨兵Redisson初始化错误", e);
            e.printStackTrace();
        }
        return config;
    }

    @Override
    public RedisConnectionType getType() {
        return RedisConnectionType.SENTINEL;
    }
}

/**
 * @description: 主从方式Redisson配置
 */
@Slf4j
@Component
public class MasterslaveRedissonConfigStrategy implements RedissonConfigStrategy {
    @Override
    public Config createRedissonConfig(RedissonProperties redissonProperties) {
        Config config = new Config();
        try {
            String address = redissonProperties.getAddress();
            String password = redissonProperties.getPassword();
            int database = redissonProperties.getDatabase();
            String[] addrTokens = address.split(",");
            String masterNodeAddr = addrTokens[0];
            // 设置主节点ip
            config.useMasterSlaveServers().setMasterAddress(masterNodeAddr);
            if (StringUtils.isNotEmpty(password)) {
                config.useMasterSlaveServers().setPassword(password);
            }
            config.useMasterSlaveServers().setDatabase(database);
            // 设置从节点,移除第一个节点,默认第一个为主节点
            List slaveList = new ArrayList<>();
            for (String addrToken : addrTokens) {
                slaveList.add(RedissonConstant.REDIS_CONNECTION_PREFIX + addrToken);
            }
            slaveList.remove(0);

            config.useMasterSlaveServers().addSlaveAddress((String[]) slaveList.toArray());
            log.info("初始化主从方式Config,redisAddress:" + address);
        } catch (Exception e) {
            log.error("主从Redisson初始化错误", e);
            e.printStackTrace();
        }
        return config;
    }

    @Override
    public RedisConnectionType getType() {
        return RedisConnectionType.MASTERSLAVE;
    }
}

/**
 * @description: 集群方式Redisson配置
 */
@Slf4j
@Component
public class ClusterRedissonConfigStrategy implements RedissonConfigStrategy {
    @Override
    public Config createRedissonConfig(RedissonProperties redissonProperties) {
        Config config = new Config();
        try {
            String address = redissonProperties.getAddress();
            String password = redissonProperties.getPassword();
            String[] addrTokens = address.split(",");
            // 设置集群(cluster)节点的服务IP和端口
            for (int i = 0; i < addrTokens.length; i++) {
                config.useClusterServers().addNodeAddress(RedissonConstant.REDIS_CONNECTION_PREFIX + addrTokens[i]);
                if (StringUtils.isNotEmpty(password)) {
                    config.useClusterServers().setPassword(password);
                }
            }
            log.info("初始化集群方式Config,连接地址:" + address);
        } catch (Exception e) {
            log.error("集群Redisson初始化错误", e);
            e.printStackTrace();
        }
        return config;
    }

    @Override
    public RedisConnectionType getType() {
        return RedisConnectionType.CLUSTER;
    }
}

自定义策略接口,分别实现四种Redis连接方式:

  1. 单机模式部署

  2. 集群模式部署

  3. 主从模式部署

  4. 哨兵模式部署

然后通过工厂模式动态初始化。

/**
 * @description: Redisson连接方式配置工厂
 */
public class RedissonConfigFactory {

    private Map redissonConfigStrategys;

    public RedissonConfigFactory() {
        redissonConfigStrategys = new HashMap<>();

        Map strategys = SpringContextHolder.getApplicationContext().getBeansOfType(RedissonConfigStrategy.class);
        strategys.forEach((k, v) -> {
            redissonConfigStrategys.put(v.getType().getCode(), v);
        });
    }

    public RedissonClient createRedissonClient(RedissonProperties redissonProperties) {
        RedisConnectionType connectionType = redissonProperties.getType();
        RedissonConfigStrategy redissonConfigStrategy = redissonConfigStrategys.get(connectionType.getCode());

        Config config = redissonConfigStrategy.createRedissonConfig(redissonProperties);
        RedissonClient redissonClient = Redisson.create(config);

        return redissonClient;
    }
}

自定义Util工具类

/**
 * @description: 基于Redisson实现分布式锁
 */
@Slf4j
@RequiredArgsConstructor
public class DistributedLockClient {
    private final RedissonClient redissonClient;

    /**
     * 获取锁
     */
    public RLock getLock(String lockKey) {
        return redissonClient.getLock(lockKey);
    }

    /**
     * 加锁操作
     *
     * @return boolean
     */
    public boolean tryLock(String lockName, long expireSeconds) {
        return tryLock(lockName, 0, expireSeconds);
    }


    /**
     * 加锁操作
     *
     * @return boolean
     */
    public boolean tryLock(String lockName, long waitTime, long expireSeconds) {
        RLock rLock = getLock(lockName);
        boolean getLock = false;
        try {
            getLock = rLock.tryLock(waitTime, expireSeconds, TimeUnit.SECONDS);
            if (getLock) {
                log.info("获取锁成功,lockName={}", lockName);
            } else {
                log.info("获取锁失败,lockName={}", lockName);
            }
        } catch (InterruptedException e) {
            log.error("获取式锁异常,lockName=" + lockName, e);
            getLock = false;
        }
        return getLock;
    }

    /**
     * 锁lockKey
     *
     * @param lockKey
     * @return
     */
    public RLock lock(String lockKey) {
        RLock lock = getLock(lockKey);
        lock.lock();
        return lock;
    }

    /**
     * 锁lockKey
     *
     * @param lockKey
     * @param leaseTime
     * @return
     */
    public RLock lock(String lockKey, long leaseTime) {
        RLock lock = getLock(lockKey);
        lock.lock(leaseTime, TimeUnit.SECONDS);
        return lock;
    }


    /**
     * 解锁
     *
     * @param lockName 锁名称
     */
    public void unlock(String lockName) {
        redissonClient.getLock(lockName).unlock();
    }

}

自定义分布式锁注解

/**
 * @description: Redisson分布式锁注解
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DistributedLock {
    /**
     * 锁Key
     *
     * @return
     */
    String lockKey() default "";

    /**
     * 锁超时时间,默认30000毫秒
     *
     * @return int
     */
    long expireSeconds() default 30000L;

    /**
     * 等待加锁超时时间,默认10000毫秒 -1 则表示一直等待
     *
     * @return int
     */
    long waitTime() default 10000L;
}


/**
 * @description: 分布式锁解析器
 */
@Slf4j
@Aspect
@RequiredArgsConstructor
public class DistributedLockAspect {

    private final RedissonClient redissonClient;

@SneakyThrows
@Around("@annotation(distributedLock)")
public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) {
        Object obj = null;
        log.info("进入RedisLock环绕通知...");

        String lockKey = distributedLock.lockKey();
        RLock rLock = redissonClient.getLock(lockKey);
        boolean res = false;
        //获取超时时间
        long expireSeconds = distributedLock.expireSeconds();
        //等待多久,n秒内获取不到锁,则直接返回
        long waitTime = distributedLock.waitTime();
        //执行aop
        if (rLock != null) {
            try {
                if (waitTime == -1) {
                    res = true;
                    //一直等待加锁
                    rLock.lock(expireSeconds, TimeUnit.MILLISECONDS);
                } else {
                    res = rLock.tryLock(waitTime, expireSeconds, TimeUnit.MILLISECONDS);
                }
                if (res) {
                    obj = joinPoint.proceed();
                } else {
                    log.error("获取锁异常");
                }
            } finally {
                if (res) {
                    rLock.unlock();
                }
            }
        }
        log.info("结束RedisLock环绕通知...");
        return obj;
    }
}

Configuration配置

/**
 * @description: Redisson自动配置
 */
@Configuration
@ConditionalOnClass(RedissonProperties.class)
@EnableConfigurationProperties(RedissonProperties.class)
@Slf4j
//自动装载连接策略
@ComponentScan(basePackages = {"com.spring2go.common.lock.strategy"})
public class RedissonAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(RedissonClient.class)
    public RedissonClient redissonClient(RedissonProperties redissonProperties) {
        RedissonConfigFactory redissonConfigFactory = new RedissonConfigFactory();
        log.info("RedissonManager初始化完成,当前连接方式:" + redissonProperties.getType() + ",连接地址:" + redissonProperties.getAddress());
        return redissonConfigFactory.createRedissonClient(redissonProperties);
    }

    @Bean
    public DistributedLockClient distributeLockClient(RedissonClient redissonClient) {
        return new DistributedLockClient(redissonClient);
    }

    @Bean
    public DistributedLockAspect distributedLockAspect(RedissonClient redissonClient) {
        return new DistributedLockAspect(redissonClient);
    }
}

以上就是整个模块的代码逻辑,代码就是最好的说明。

如何使用

主动声明lock

public void lockDecreaseStock() throws InterruptedException {
    distributedLockClient.tryLock("lock", 10);
    if (stock > 0) {
        stock--;
    }
    Thread.sleep(50);
    log.info("当前库存:" + stock);
    distributedLockClient.unlock("lock");
}

使用注解

@DistributedLock(lockKey="lock",expireSeconds = 10)
public void lockDecreaseStock()  {
    if (stock > 0) {
        stock--;
    }
    Thread.sleep(50);
    log.info("当前库存:" + stock);
}

参考资料

  • redisson wiki

  • Redisson实现分布式锁

你可能感兴趣的:(Spring,Cloud,redis,lock,分布式锁,锁,redisson)