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的功能非常强大,这里仅仅用于实现分布式锁,后续我将进一步学习这个框架。

基于Redisson的分布式锁

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

1、锁机制

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

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

2、watch dog自动延期机制

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

3、lua脚本

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

整合Spring Boot

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

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

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

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

  • DistributedLockClient为分布式锁工具类

添加引用

<!--redisson-->
<dependency>
      <groupId>org.redisson</groupId>
      <artifactId>redisson</artifactId>
      <version>3.15.5</version>
</dependency>
<!--添加aop需要添加spring cloud依赖-->
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

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<String> 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<String, RedissonConfigStrategy> redissonConfigStrategys;

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

        Map<String, RedissonConfigStrategy> 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);
}

参考资料

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Cloud分布式事务是用于解决在分布式架构中,多个微服务之间的事务一致性问题的一种解决方案。在高并发的情况下,如果不加就会出现并发冲突的情况,导致数据不一致。Spring Cloud分布式事务使用了分布式锁的概念来实现。 在Spring Cloud中,可以使用DistributedLock和DistributedLockAspect来实现分布式锁的注解封装,从而简化使用。另外,基于Redis分布式锁是一种流行的方案,可以通过在Redis中设置一个特定的键值对来实现的获取和释放。 总结起来,Spring Cloud分布式事务是一种用于解决分布式架构下多个微服务之间事务一致性问题的解决方案,可以使用DistributedLock和DistributedLockAspect来简化使用,并可以选择基于Redis分布式锁方案来实现的获取和释放。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Spring Cloud:基于Redisson分布式锁实现](https://blog.csdn.net/erik_tse/article/details/117017258)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [SpringCloud认识五之分布式锁和分布式事务](https://blog.csdn.net/weixin_41446894/article/details/86260854)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值