Mars‘s docMars‘s doc
🏠主页
  • 🍻Activiti

    • 01-核心API
    • 02-监听
    • 03-数据库表介绍
    • 04-常见问题
  • 📊JasperReport

    • 01-JaspersoftStudio工具使用
    • 02-JasperReport集成
    • 03-JasperReport常见问题
  • 🎬JavaScript

    • 01-Node
    • 02-VuePress
    • 03-Vue组件高度宽度
    • 04-显示器和屏幕高度
    • 05-页面地址
    • 06-归纳总结
  • 🍵Java

    • 01-Java8特性
    • 02-多线程
    • 03-Jar包
    • 04-Util
    • 05-validation注解
    • 06-反编译
    • 07-try-with-resource
    • 08-ThreadLocal内存泄漏
    • 09-Jvm
    • 10-Excel
    • 11-Lombook
    • 12-条件注解
    • 13-WebMvcConfigurationSupport
    • 14-WebMvcConfigurer
    • 15-分布式锁
    • 16-Caffeine
    • 17-DynamicDatasource
    • 18-MybatisPlus
    • 19-Swagger
    • 20-BeanPostProcessor
    • 21-Bean初始化
    • 22-ConfigurableApplicationContext
    • 23-常用注解
    • 24-ApplicationListener
    • 25-JavaDoc
    • 26-Spring-Cache
    • 27-StopWatch耗时统计
    • 28-Word
    • 29-Druid
    • 30-OpenFeign
    • 31-反射相关
    • 32-Fastjson
    • 33-Yaml
  • 💻Linux

    • 01-Linux常用命令
    • 02-Linux脚本汇总
    • 03-Yum源
    • 04-Debian
    • 05-Ubuntu
  • 🐋Docker

    • 01-Docker常用命令
    • 02-Dockerfile
    • 03-Swarm
    • 04-Stack
    • 05-Docker常见问题
    • 06-DockerCompose
    • 07-Docker应用用汇总
    • 08-Kasm
    • 09-Rustdesk
  • 🌐Nginx

    • 01-Nginx
  • 📈数据库

    • 01-Mysql
    • 02-Clickhouse
    • 03-Doris
    • 04-DRDS
  • 📉Kettle

    • 01-入门
    • 02-js脚本
    • 03-优化
    • 04-连接组件
    • 05-参数
    • 06-工具
    • 07-日志
    • 08-流程组件
    • 09-输入组件
    • 10-输出组件
    • 11-转换组件
    • 12-驱动
  • 🎨Git

    • 01-Git使用
  • 📝Maven

    • 01-Maven使用
    • 02-Maven配置
  • 🎯Jenkins

    • 01-Jenkins部署
    • 02-Jenkisn常见问题
  • 01-设计模式之禅
  • 02-领域驱动设计
  • 03-JavaScript高级程序设计
  • 🍓树莓派

    • 01-RaspBerry
  • 📘Markdown

    • 01-Markdown语法
    • 02-Markdown表情
    • 03-Markdown代码块语言对照
  • 📇其他

    • 01-HTML XML 转义
    • 02-GitHub
    • 03-Idea
    • 04-Nmon
    • 05-Windows
    • 06-WinSw
GitHub
🏠主页
  • 🍻Activiti

    • 01-核心API
    • 02-监听
    • 03-数据库表介绍
    • 04-常见问题
  • 📊JasperReport

    • 01-JaspersoftStudio工具使用
    • 02-JasperReport集成
    • 03-JasperReport常见问题
  • 🎬JavaScript

    • 01-Node
    • 02-VuePress
    • 03-Vue组件高度宽度
    • 04-显示器和屏幕高度
    • 05-页面地址
    • 06-归纳总结
  • 🍵Java

    • 01-Java8特性
    • 02-多线程
    • 03-Jar包
    • 04-Util
    • 05-validation注解
    • 06-反编译
    • 07-try-with-resource
    • 08-ThreadLocal内存泄漏
    • 09-Jvm
    • 10-Excel
    • 11-Lombook
    • 12-条件注解
    • 13-WebMvcConfigurationSupport
    • 14-WebMvcConfigurer
    • 15-分布式锁
    • 16-Caffeine
    • 17-DynamicDatasource
    • 18-MybatisPlus
    • 19-Swagger
    • 20-BeanPostProcessor
    • 21-Bean初始化
    • 22-ConfigurableApplicationContext
    • 23-常用注解
    • 24-ApplicationListener
    • 25-JavaDoc
    • 26-Spring-Cache
    • 27-StopWatch耗时统计
    • 28-Word
    • 29-Druid
    • 30-OpenFeign
    • 31-反射相关
    • 32-Fastjson
    • 33-Yaml
  • 💻Linux

    • 01-Linux常用命令
    • 02-Linux脚本汇总
    • 03-Yum源
    • 04-Debian
    • 05-Ubuntu
  • 🐋Docker

    • 01-Docker常用命令
    • 02-Dockerfile
    • 03-Swarm
    • 04-Stack
    • 05-Docker常见问题
    • 06-DockerCompose
    • 07-Docker应用用汇总
    • 08-Kasm
    • 09-Rustdesk
  • 🌐Nginx

    • 01-Nginx
  • 📈数据库

    • 01-Mysql
    • 02-Clickhouse
    • 03-Doris
    • 04-DRDS
  • 📉Kettle

    • 01-入门
    • 02-js脚本
    • 03-优化
    • 04-连接组件
    • 05-参数
    • 06-工具
    • 07-日志
    • 08-流程组件
    • 09-输入组件
    • 10-输出组件
    • 11-转换组件
    • 12-驱动
  • 🎨Git

    • 01-Git使用
  • 📝Maven

    • 01-Maven使用
    • 02-Maven配置
  • 🎯Jenkins

    • 01-Jenkins部署
    • 02-Jenkisn常见问题
  • 01-设计模式之禅
  • 02-领域驱动设计
  • 03-JavaScript高级程序设计
  • 🍓树莓派

    • 01-RaspBerry
  • 📘Markdown

    • 01-Markdown语法
    • 02-Markdown表情
    • 03-Markdown代码块语言对照
  • 📇其他

    • 01-HTML XML 转义
    • 02-GitHub
    • 03-Idea
    • 04-Nmon
    • 05-Windows
    • 06-WinSw
GitHub
  • 🏫技术相关

    • 🍻Activiti

      • 01-核心API
      • 02-监听
      • 03-数据库表介绍
      • 04-常见问题
    • 📊JasperReport

      • 01-JaspersoftStudio工具使用
      • 02-JasperReport集成
      • 03-JasperReport常见问题
    • 🎬JavaScript

      • 01-Node
      • 02-VuePress
      • 03-Vue组件高度宽度
      • 04-显示器和屏幕高度
      • 05-页面地址
      • 06-归纳总结
    • 🍵Java

      • 01-Java8特性
      • 02-多线程
      • 03-Jar包
      • 04-Util
      • 05-validation注解
      • 06-反编译
      • 07-try-with-resource
      • 08-ThreadLocal内存泄漏
      • 09-Jvm
      • 10-Excel
      • 11-Lombook
      • 12-条件注解
      • 13-WebMvcConfigurationSupport
      • 14-WebMvcConfigurer
      • 15-分布式锁
      • 16-Caffeine
      • 17-DynamicDatasource
      • 18-MybatisPlus
      • 19-Swagger
      • 20-BeanPostProcessor
      • 21-Bean初始化
      • 22-ConfigurableApplicationContext
      • 23-常用注解
      • 24-ApplicationListener
      • 25-JavaDoc
      • 26-Spring-Cache
      • 27-StopWatch耗时统计
      • 28-Word
      • 29-Druid
      • 30-OpenFeign
      • 31-反射相关
      • 32-Fastjson
      • 33-Yaml
  • 🏢服务器

    • 💻Linux

      • 01-Linux常用命令
      • 02-Linux脚本汇总
      • 03-Yum源
      • 04-Debian
      • 05-Ubuntu
    • 🐋Docker

      • 01-Docker常用命令
      • 02-Dockerfile
      • 03-Swarm
      • 04-Stack
      • 05-Docker常见问题
      • 06-DockerCompose
      • 07-Docker应用用汇总
      • 08-Kasm
      • 09-Rustdesk
    • 🌐Nginx

      • 01-Nginx
  • 🏩数据相关

    • 📈数据库

      • 01-Mysql
      • 02-Clickhouse
      • 03-Doris
      • 04-DRDS
    • 📉Kettle

      • 01-入门
      • 02-js脚本
      • 03-优化
      • 04-连接组件
      • 05-参数
      • 06-工具
      • 07-日志
      • 08-流程组件
      • 09-输入组件
      • 10-输出组件
      • 11-转换组件
      • 12-驱动
  • 🏬管理工具

    • 🎨Git

      • 01-Git使用
    • 📝Maven

      • 01-Maven使用
      • 02-Maven配置
    • 🎯Jenkins

      • 01-Jenkins部署
      • 02-Jenkisn常见问题
  • 🏯书籍笔记

    • 01-设计模式之禅
    • 02-领域驱动设计
    • 03-JavaScript高级程序设计
  • 🏦其他

    • 🍓树莓派

      • 01-RaspBerry
    • 📘Markdown

      • 01-Markdown语法
      • 02-Markdown表情
      • 03-Markdown代码块语言对照
    • 📇其他

      • 01-HTML XML 转义
      • 02-GitHub
      • 03-Idea
      • 04-Nmon
      • 05-Windows
      • 06-WinSw

分布式锁

分布式系统解决一个方法或属性,同一时间在多机、多线程、高并发场景只能被一个线程执行的方案。

目录

  • 如何实现
  • 如何使用
  • 注意事项

如何实现

  1. pom.xml 添加Redisson依赖
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.12.3</version>
</dependency>
  1. 配置 redisson

LockRedisProperties .java

@Data
@ToString
@ConfigurationProperties(prefix = "lock.redis", ignoreUnknownFields = true)
public class LockRedisProperties {

    private int database;

    /**
     * 等待节点回复命令的时间。该时间从命令发送成功时开始计时
     */
    private int timeout;

    private String password;

    private String mode;

    private String nodes;

    private String connTimeout;

    /**
     * 池配置
     */
    private LockRedisPoolProperties pool;

    /**
     * 集群 信息配置
     */
    private LockRedisClusterProperties cluster;

}

LockRedisPoolProperties.java

@Data
@ToString
public class LockRedisPoolProperties {
   
   private int maxIdle;

   private int minIdle;

   private int maxActive;

   private int maxWait;

   private int connTimeout;

   private int soTimeout;

   /**
    * 池大小
    */
   private int size;

}

LockRedisClusterProperties.java

@Data
@ToString
public class LockRedisClusterProperties {

    /**
     * 集群状态扫描间隔时间,单位是毫秒
     */
    private int scanInterval;

    /**
     * 集群节点
     */
    private String nodes;

    /**
     * 默认值: SLAVE(只在从服务节点里读取)设置读取操作选择节点的模式。 可用值为: SLAVE - 只在从服务节点里读取。
     * MASTER - 只在主服务节点里读取。 MASTER_SLAVE - 在主从服务节点里都可以读取
     */
    private String readMode;

    /**
     * (从节点连接池大小) 默认值:64
     */
    private int slaveConnectionPoolSize;

    /**
     * 主节点连接池大小)默认值:64
     */
    private int masterConnectionPoolSize;

    /**
     * (命令失败重试次数) 默认值:3
     */
    private int retryAttempts;

    /**
     *命令重试发送时间间隔,单位:毫秒 默认值:1500
     */
    private int retryInterval;

    /**
     * 执行失败最大次数默认值:3w
     */
    private int failedAttempts;
}
  1. 构建RedissonClient

LockCacheConfiguration .java

@Configuration
@EnableConfigurationProperties(LockRedisProperties.class)
public class LockCacheConfiguration {

    private final static Logger LOGGER = LoggerFactory.getLogger(LockCacheConfiguration.class);

    @Autowired
    private LockRedisProperties redisProperties;

    @Configuration
    @ConditionalOnClass({Redisson.class})
    @ConditionalOnExpression("'${lock.redis.mode}'=='single' or '${lock.redis.mode}'=='cluster' or '${lock.redis.mode}'=='sentinel'")
    protected class RedissonSingleClientConfiguration {

        /**
         * 单机模式 redisson 客户端
         */
        @Bean
        @ConditionalOnProperty(name = "lock.redis.mode", havingValue = "single")
        RedissonClient redissonSingle() {

            Config config = new Config();
            String node = redisProperties.getNodes();
            node = node.startsWith("redis://") ? node : "redis://" + node;
            SingleServerConfig serverConfig = config.useSingleServer();

            serverConfig.setAddress(node)
                        // 命令等待超时,单位:毫秒
                        .setTimeout(redisProperties.getTimeout())
                        // 连接空闲超时,单位:毫秒
                        .setIdleConnectionTimeout(redisProperties.getPool().getSoTimeout())
                        // 连接超时,单位:毫秒
                        .setConnectTimeout(redisProperties.getPool().getConnTimeout())
                        // 连接池大小
                        .setConnectionPoolSize(redisProperties.getPool().getMaxIdle())
                        // 最小空闲连接数
                        .setConnectionMinimumIdleSize(redisProperties.getPool().getMinIdle());

            if (StrUtil.isNotBlank(redisProperties.getPassword())) {
                serverConfig.setPassword(redisProperties.getPassword());
            }

            return Redisson.create(config);
        }

        /**
         * 集群模式的 redisson 客户端
         */
        @Bean
        @ConditionalOnProperty(name = "lock.redis.mode", havingValue = "cluster")
        RedissonClient redissonCluster() {

            LOGGER.info("cluster redisProperties:{}", redisProperties.getCluster());

            Config config = new Config();
            String[] nodes = redisProperties.getNodes().split(",");
            List<String> newNodes = new ArrayList<>(nodes.length);
            Arrays.stream(nodes).forEach((index) -> newNodes.add(
                    index.startsWith("redis://") ? index : "redis://" + index));

            ClusterServersConfig serverConfig = config.useClusterServers();
            serverConfig.addNodeAddress(newNodes.toArray(new String[0]))
                        .setScanInterval(redisProperties.getCluster().getScanInterval())
                        // 连接空闲超时,单位:毫秒
                        .setIdleConnectionTimeout(redisProperties.getPool().getSoTimeout())
                        // 连接超时,单位:毫秒
                        .setConnectTimeout(redisProperties.getPool().getConnTimeout())
                        .setFailedSlaveCheckInterval(redisProperties.getCluster().getFailedAttempts())
                        // 命令失败重试次数
                        .setRetryAttempts(redisProperties.getCluster().getRetryAttempts())
                        // 命令重试发送时间间隔,单位:毫秒
                        .setRetryInterval(redisProperties.getCluster().getRetryInterval())
                        // 主节点连接池大小
                        .setMasterConnectionPoolSize(redisProperties.getCluster().getMasterConnectionPoolSize())
                        // 从节点连接池大小
                        .setSlaveConnectionPoolSize(redisProperties.getCluster().getSlaveConnectionPoolSize())
                        // 命令等待超时,单位:毫秒
                        .setTimeout(redisProperties.getTimeout());
            if (StrUtil.isNotBlank(redisProperties.getPassword())) {
                serverConfig.setPassword(redisProperties.getPassword());
            }

            return Redisson.create(config);
        }
    }

}
  1. 添加配置,根据 redis 服务情况合理配置参数

单机:

lock:
  redis:
    database: 0
    mode: single
    nodes: 192.168.174.105:32341
    password: redis
    timeout: 30000
    pool:
      minIdle: 1
      maxIdle: 100
      soTimeout: 10000

集群:

lock:
  redis:
    database: 0
    mode: cluster
    nodes: 192.168.0.103:6380,192.168.0.103:6381,192.168.0.103:6382,192.168.0.103:6383,192.168.0.103:6384,192.168.0.103:6385
    password:
    timeout: 30000
    pool:
      minIdle: 1
      maxIdle: 100
      soTimeout: 10000
    cluster:
      scanInterval: 10
      masterConnectionPoolSize: 64
      slaveConnectionPoolSize: 64

如何使用

LockTest.java

@Slf4j
@Component
@ConditionalOnBean(RedissonClient.class)
public class LockTest implements ApplicationRunner {

    @Resource
    RedissonClient redissonClient;

    public String lock(String lockName) {
        lockName = StrUtil.blankToDefault(lockName, "lock");
        RLock lock = redissonClient.getLock(lockName);
        try {
            // true if lock is successfully acquired, otherwise false if lock is already set.
            boolean lockStatus = lock.tryLock(10, 100, TimeUnit.SECONDS);
            if (lockStatus) {
                System.out.println("进程"+System.getProperty("PID")+"加锁成功");
            }else{
                System.out.println("进程"+System.getProperty("PID")+"获取锁失败");
            }
        } catch (InterruptedException e) {
            log.error("加锁失败",e);
        }
        finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()){
                lock.unlock();
                System.out.println("解锁完成");
            }
        }
        return System.getProperty("PID");
    }

    /**
     * Callback used to run the bean.
     *
     * @param args incoming application arguments
     * @throws Exception on error
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        lock("lock-test");
    }
}

注意事项

  1. 加锁:
  • 建议设置合理的重试次数和超时时间,以防止死锁情况的发生;
  • 避免使用分布式锁来保证高频率的临界区访问,这样会导致锁的频繁获取和释放,降低系统性能;
  • 分布式锁不应该成为系统性能的瓶颈,需要合理评估锁的使用场景和性能影响;
  • 加锁使用的 key 需要按业务设置,保证不同业务加锁使用的 key 唯一性。
  1. 解锁:
  • 判断锁是否是当前线程所加,避免其他线程出现误解锁情况,造成业务事故。
Edit this page
Last Updated:
Contributors: wangxiaoquan
Prev
14-WebMvcConfigurer
Next
16-Caffeine