redis分布式锁

pom.xml

<!-- Spring Session redis start  -->
        <!-- Spring Session 依赖start -->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session</artifactId>
            <version>1.3.1.RELEASE</version>
        </dependency>
        <!-- Spring Session 依赖end -->

        <!-- Spring session redis 依赖start -->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
            <version>1.3.1.RELEASE</version>
        </dependency>
        <!-- Spring session redis 依赖end -->

        <!-- spring-data-redis依赖的JAR配置start -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>1.8.8.RELEASE</version>
        </dependency>
        <!-- spring-data-redis依赖的JAR配置end -->

        <!-- jedis依赖的JAR配置start -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
        <!-- jedis依赖的JAR配置end -->
        <!-- Spring Session redis end  -->
RedisConfig
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 43200) // 设置超时为 12 小时
public class RedisConfig {
	static {
		redisHost = ConfigCst.getConfigStringByKey("redis.host", "127.0.0.1");
		redisPort = ConfigCst.getConfigIntByKey("redis.port", 6379);
		redisPass = ConfigCst.getConfigStringByKey("redis.pass", "");
	}
    private static String redisHost;
    private static int redisPort;
    private static String redisPass;
    @Bean
    JedisConnectionFactory jedisConnectionFactory() {
        JedisConnectionFactory factory = new JedisConnectionFactory();
        factory.setHostName(redisHost);
        factory.setPort(redisPort);
        factory.setPassword(redisPass);
        factory.setUsePool(true);
        return factory;
    }

    @Bean
    RedisTemplate< String, Object > redisTemplate() {
        final RedisTemplate< String, Object > template =  new RedisTemplate< String, Object >();
        template.setConnectionFactory( jedisConnectionFactory() );
        template.setKeySerializer( new StringRedisSerializer() );
        template.setHashValueSerializer( new GenericToStringSerializer< Object >( Object.class ) );
        template.setValueSerializer( new GenericToStringSerializer< Object >( Object.class ) );
        return template;
    }
}
RedisLockUtils
 
@Component
public class RedisLockUtils {
    private static final Logger logger = LoggerFactory.getLogger(RedisLockUtils.class);

    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 尝试获取锁
     *
     * @param key        锁的键
     * @param value      锁的值
     * @param expireTime 锁的过期时间
     * @param timeUnit   时间单位
     * @return 是否获取到锁
     */
    public Boolean tryLock(String key, String value, long expireTime, TimeUnit timeUnit) {
        logger.info("tryLock redis -key:" + key);
        logger.info("tryLock redis -value:" + value);
        logger.info("tryLock redis -expireTime:" + expireTime);
        logger.info("tryLock redis -timeUnit:" + timeUnit);

        // 将过期时间转换为秒
        long expireSeconds = timeUnit.toSeconds(expireTime);
        return tryLockInternal(key, value, expireSeconds);
    }

    /**
     * 尝试获取没有超时时间的锁
     *
     * @param key   锁的键
     * @param value 锁的值
     * @return 是否获取到锁
     */
    public Boolean tryLockWithoutTimeout(String key, String value) {
        logger.info("tryLockWithoutTimeout redis -key:" + key);
        logger.info("tryLockWithoutTimeout redis -value:" + value);

        // 传入过期时间为 -1,表示不设置过期时间
        return tryLockInternal(key, value, -1);
    }

    /**
     * 内部通用的尝试获取锁方法
     *
     * @param key           锁的键
     * @param value         锁的值
     * @param expireSeconds 锁的过期时间(秒),-1 表示不设置过期时间
     * @return 是否获取到锁
     * 脚本的作用是:
     * 使用 SET 命令将 ARGV[1] 设置为 KEYS[1] 的值。
     * 使用 NX 选项确保只有在 Key 不存在时才会设置成功。
     * 使用 EX 选项设置 Key 的过期时间为 ARGV[2] 秒。
     */
    private Boolean tryLockInternal(String key, String value, long expireSeconds) {
        Boolean result = (Boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> {
            // 根据是否设置过期时间生成不同的 Lua 脚本
            String script = "local result = redis.call('SET', KEYS[1], ARGV[1], 'NX'" +
                    // >0 设置超时(带有 EX 选项),否则不设置超时时间
                    (expireSeconds > 0 ? ", 'EX', ARGV[2]" : "") + ")" +
                    " if result then" +
                    "   return 1" +
                    " else" +
                    "   return 0" +
                    " end";
            // 执行 Lua 脚本
            return connection.eval(
                    script.getBytes(),
                    ReturnType.BOOLEAN,
                    1, // number of keys
                    // 由于 GenericToStringSerializer 最终会将字符串转换为字节数组,
                    // 因此可以直接传递字符串的字节数组,而不需要显式调用 serialize 方法
                    key.getBytes(),// KEYS[1]
                    value.getBytes(),// ARGV[1]
                    // >0 设置超时(带有 EX 选项),否则不设置超时时间
                    String.valueOf(expireSeconds).getBytes()// ARGV[2]
            );
        });

        logger.info(key + " final result: " + (result != null && result));
        return result != null && result;
    }

    /**
     * 释放锁
     *
     * @param key 锁的键
     */
    public void unlock(String key) {
        redisTemplate.delete(key);
        logger.info("redisTemplate-key:" + key + " remove success");
    }

    /**
     * 释放锁
     *
     * @param key   锁的键
     * @param value 锁的值
     */
    public void unlock(String key, String value) {
        // 在释放锁时验证 value
        String currentValue = (String) redisTemplate.opsForValue().get(key);
        if (value.equals(currentValue)) {
            redisTemplate.delete(key);
            logger.info("锁释放成功 - key: {}", key);
        } else {
            logger.info("锁释放失败 - key: {},value 不匹配", key);
        }
    }

    /**
     * 检查锁是否超时
     *
     * @param key 锁的 Key
     * @return 是否超时(true:已超时;false:未超时)
     */
    public boolean isLockExpired(String key) {
        Long ttl = redisTemplate.getExpire(key, TimeUnit.SECONDS);
        if (ttl == null) {
            // Key 不存在
            logger.info("锁 {} 不存在,视为已超时", key);
            return true;
        }
        if (ttl == -1) {
            // Key 存在,但没有设置过期时间
            logger.info("锁 {} 存在,但没有设置过期时间", key);
            return false;
        }
        if (ttl <= 0) {
            // Key 存在且已超时
            logger.info("锁 {} 已超时,剩余时间: {} 秒", key, ttl);
            return true;
        }
        // Key 存在且未超时
        logger.info("锁 {} 未超时,剩余时间: {} 秒", key, ttl);
        return false;
    }

    /**
     * key 是否存在
     *
     * @param key
     * @return
     */
    public boolean isExistsHasKey(String key) {
        return redisTemplate.hasKey(key);
    }
}
Service
 @Service
public class AServiceImpl extends ServiceImpl<AMapper, A> implements IAService {
    @Resource
    private RedisLockUtils redisLockUtils;
    private static final long EXPIRE_TIME = 60; // 锁的过期时间,单位秒
    private static final String LOCK_KEY = "to_lock";
    
    @Override
    public void toProduction() {
        // 尝试获取锁
        boolean isLocked = redisLockUtils.tryLock(LOCK_KEY, System.currentTimeMillis() + "", EXPIRE_TIME, TimeUnit.SECONDS);
        if (!isLocked) {
            throw new DataException("There is already a publish to production task in progress");
        }
        try {
            // 具体业务流程
        } finally {
            // 释放锁
            redisLockUtils.unlock(LOCK_KEY);
        }
    }
}
posted @   丶Ronnie  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示