专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

springboot项目中redis分布式锁的使用

现在开发的微服务项目对分布式锁使用比较多,故此对自定实现分布式锁作一些记录。
* 实际项目使用时直接使用Redisson框架的RLock锁即可。

创建springboot redis项目
springboot-redis-demo

添加依赖包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.2.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
        </exclusion>
    </exclusions>
</dependency>

添加配置项application.properties

spring.cache.type=redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=0
spring.redis.password=111111
spring.redis.timeout=0
spring.redis.ssl=false
spring.redis.jedis.pool.max-active=300
spring.redis.jedis.pool.max-wait=-1
spring.redis.jedis.pool.max-idle=300
spring.redis.jedis.pool.min-idle=1
#spring.redis.cluster.nodes=127.0.0.1:6080
#spring.redis.cluster.max-redirects=10
#spring.redis.sentinel.master=127.0.0.1:6080
#spring.redis.sentinel.nodes=192.168.1.248:26379,192.168.1.249:26379,192.168.1.250:26379
#spring.data.redis.repositories.enabled=true

创建redis配置类
配置类添加@EnableFeignClients注解

@Configuration
@EnableCaching
public class RediseConfig extends CachingConfigurerSupport {

    @Autowired
    private RedisProperties redisProperties;
    @Autowired
    private RedisProperties.Pool redisPropertiesPool;

    @Bean
    @ConditionalOnMissingBean(name = "poolConfig")
    public JedisPoolConfig poolConfig() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setBlockWhenExhausted(true);
        jedisPoolConfig.setTestOnBorrow(true);
        jedisPoolConfig.setMaxTotal(redisPropertiesPool.getMaxActive());
        jedisPoolConfig.setMaxIdle(redisPropertiesPool.getMaxIdle());
        jedisPoolConfig.setMinIdle(redisPropertiesPool.getMinIdle());
        jedisPoolConfig.setMaxWaitMillis(redisPropertiesPool.getMaxWait());// 100000

        jedisPoolConfig.setTestOnBorrow(true);
        jedisPoolConfig.setTestOnReturn(true);
        //Idle时进行连接扫描
        jedisPoolConfig.setTestWhileIdle(true);
        //表示idle object evitor两次扫描之间要sleep的毫秒数
        jedisPoolConfig.setTimeBetweenEvictionRunsMillis(30000);
        //表示idle object evitor每次扫描的最多的对象数
        jedisPoolConfig.setNumTestsPerEvictionRun(10);
        //表示一个对象至少停留在idle状态的最短时间,然后才能被idle object evitor扫描并驱逐;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义
        jedisPoolConfig.setMinEvictableIdleTimeMillis(60000);
        return jedisPoolConfig;
    }

    @Bean
    @ConditionalOnMissingBean(name = "jedisPool")
    public JedisPool jedisPool(JedisPoolConfig poolConfig) {
        return new JedisPool(poolConfig,
                redisProperties.getHost(),
                redisProperties.getPort(),
                redisProperties.getTimeout().getNano(),
                redisProperties.getPassword());
    }

    @Bean
    @ConditionalOnMissingBean(name = "redisConnectionFactory")
    public RedisConnectionFactory redisConnectionFactory(JedisPoolConfig poolConfig) {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        //设置redis服务器的host或者ip地址
        redisStandaloneConfiguration.setHostName(redisProperties.getHost());
        redisStandaloneConfiguration.setPort(redisProperties.getPort());
        redisStandaloneConfiguration.setPassword(redisProperties.getPassword());
        redisStandaloneConfiguration.setDatabase(redisProperties.getDatabase());
        //获得默认的连接池构造
        //这里需要注意的是,edisConnectionFactoryJ对于Standalone模式的没有(RedisStandaloneConfiguration,JedisPoolConfig)的构造函数,对此
        //我们用JedisClientConfiguration接口的builder方法实例化一个构造器,还得类型转换
        JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jpcf = (JedisClientConfiguration.JedisPoolingClientConfigurationBuilder) JedisClientConfiguration.builder();
        //修改我们的连接池配置
        jpcf.poolConfig(poolConfig);
        //通过构造器来构造jedis客户端配置
        JedisClientConfiguration jedisClientConfiguration = jpcf.build();
        return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration);
    }

    @Primary
    @Bean
    public CacheManager cacheManager(@Qualifier("redisConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        //.entryTtl(Duration.ofHours(1))       // 设置缓存有效期一小时
        //.disableCachingNullValues();         // 不缓存空值
        return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
                .cacheDefaults(redisCacheConfiguration).build();
    }

    /**
     * 实例化 RedisTemplate 对象
     * @return
     */
    @Bean(name = "redisTemplate")
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = this.jackson2JsonRedisSerializer();

        template.setKeySerializer(redisSerializer);
        template.setValueSerializer(jackson2JsonRedisSerializer);

        template.setHashKeySerializer(redisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);

        template.afterPropertiesSet();
        return template;
    }

    @Bean(name = "stringRedisTemplate")
    @ConditionalOnMissingBean(name = "stringRedisTemplate")
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);

        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = this.jackson2JsonRedisSerializer();

        template.setKeySerializer(redisSerializer);
        template.setValueSerializer(jackson2JsonRedisSerializer);

        template.setHashKeySerializer(redisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);

        template.afterPropertiesSet();
        return template;
    }

    private Jackson2JsonRedisSerializer jackson2JsonRedisSerializer() {
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        return jackson2JsonRedisSerializer;
    }

    /**
     * 对hash类型的数据操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForHash();
    }

    /**
     * 对redis字符串类型数据操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForValue();
    }

    /**
     * 对链表类型的数据操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForList();
    }

    /**
     * 对无序集合类型的数据操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForSet();
    }

    /**
     * 对有序集合类型的数据操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForZSet();
    }

    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                for (Object obj : params) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            }
        };
    }

    @Bean
    public RedisLockRegistry redisLockRegistry(@Qualifier("redisConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
        return new RedisLockRegistry(redisConnectionFactory, "redis_locks", 6000L);
    }

    @Bean
    public Jedis jedis(@Qualifier("jedisPool") JedisPool jedisPool) {
        return jedisPool.getResource();
    }
}

创建RedisLockUtil工具类

@Slf4j
public class RedisLockUtil implements Lock {

    private Jedis jedis = SpringUtils.getBean("jedis");
    private static String REDIS_LOCK_KEY = "REDIS_LOCK_KEY_";
    private static String REQUEST_ID = "redis_lock_id_";
    /**
     * 锁超时时间,防止线程在入锁以后,无限的执行等待
     */
    private int EXPIRE_TIME_MSECS = 60 * 1000;
    private static final Long RELEASE_SUCCESS = 1L;

    public RedisLockUtil(String key){
        this.REDIS_LOCK_KEY = this.REDIS_LOCK_KEY + key;
    }
    public RedisLockUtil(String key, String val){
        this.REDIS_LOCK_KEY = this.REDIS_LOCK_KEY + key;
        this.REQUEST_ID = this.REQUEST_ID  + val;
    }

    @Override
    public boolean tryLock() {
        try {
            if (jedis.exists(REDIS_LOCK_KEY)) {
                Long setNx = jedis.setnx(REDIS_LOCK_KEY, REQUEST_ID);
                if (setNx == 1) {
                    return Boolean.TRUE;
                }
            }
        } catch (Exception e) {
            log.error("redis tryLock Exception", e);
        }
        return Boolean.FALSE;
    }

    @Override
    public void lock() {
        try {
            if (!tryLock()) {
                SetParams params = new SetParams();
                params.px(EXPIRE_TIME_MSECS).nx();
                jedis.set(REDIS_LOCK_KEY, REQUEST_ID, params);
                log.info(">>>>>>redis lock: {}", jedis.get(REDIS_LOCK_KEY));
            }
        } catch (Exception e) {
            log.error("redis lock Exception", e);
        }
    }

    @Override
    public boolean unlock() {
        try {
            if (jedis.exists(REDIS_LOCK_KEY)) {
                String delLock = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                Object result = jedis.eval(delLock, Collections.singletonList(REDIS_LOCK_KEY), Collections.singletonList(REQUEST_ID));
                if (RELEASE_SUCCESS.equals(result)) {
                    return true;
                }
            } else {
                return true;
            }
        } catch (Exception e) {
            log.error("redis unlock Exception", e);
        }
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public Condition newCondition() {
        return null;
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }
}

要确保获得锁时添加锁的自动释放时间是原子操作
SetParams params = new SetParams();
params.px(EXPIRE_TIME_MSECS).nx();
jedis.set(REDIS_LOCK_KEY, REQUEST_ID, params);
xn:意思是SET IF NOT EXIST,即当key不存在时,就进行set操作;若key已经存在,则不做任何操作;
px:意思是给key加一个EXPIRE_TIME_MSECS的过期时间。

创建unit test测试类

@SpringBootTest(classes = SpringbootRedisDemoApplication.class)
public class RedisTests {

    @Autowired
    private RedisUtils redisUtil;
    
    @Test
    public void redisLockUtilTest() throws InterruptedException {
        RedisLockUtil redisLockUtil = new RedisLockUtil("testLock1", "1");
        redisLockUtil.lock();
        TimeUnit.SECONDS.sleep(10);
        redisLockUtil.unlock();
    }
}

测试结果
在锁定的时候redis中可以看到锁的状态

107_1.png

源码示例:https://gitee.com/lion123/springboot-redis-demo

* redis分布式锁过期时间到了但业务没执行完怎么办?
直接使用Redisson框架的RLock,redisson RLock锁获取成功后,就会去注册一个定时任务(一个后台线程)来每隔10秒检查一下锁的过期时间(加锁的锁key默认生存时间是30秒),如果当前线程还持有锁key,那么就会不断的延长锁key的生存时间。

文章永久链接:https://tech.souyunku.com/24898

未经允许不得转载:搜云库技术团队 » springboot项目中redis分布式锁的使用

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们