SpringBoot3+Redis实现分布式锁

SpringBoot3+Redis+Lua脚本实现分布式锁

相关依赖包

xml 复制代码
<spring-boot.version>3.0.2</spring-boot.version>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

application.yml Redis配置

yml 复制代码
spring:
  data:
    redis:
      host: 192.168.5.133
      port: 6379
      password:
      database: 0
      lettuce:
        pool:
          max-active: 8
          max-idle: 8
          min-idle: 0
          max-wait: "-1ms"

Lua脚本

获取锁脚本 tryLock.lua

bash 复制代码
-- KEYS[1] 是锁的键
-- ARGV[1] 是请求ID
-- ARGV[2] 是锁的过期时间(秒)
local lockKey = KEYS[1]
local requestId = ARGV[1]
local expireTime = tonumber(ARGV[2])

-- 尝试获取锁
local lockValue = redis.call('GET', lockKey)

-- 如果锁不存在,尝试设置锁
if not lockValue then
    if redis.call('SETNX', lockKey, requestId) then
        -- 设置锁的过期时间
        redis.call('EXPIRE', lockKey, expireTime)
        return 1
    end
    return 0
elseif lockValue == requestId then
    -- 如果请求ID与当前锁持有者匹配,延长锁的过期时间
    redis.call('EXPIRE', lockKey, expireTime)
    return 1
else
    -- 锁被其他请求持有,无法获取锁
    return 0
end

释放锁脚本 releaseLock.lua

bash 复制代码
-- KEYS[1] 是锁的键
-- ARGV[1] 是请求ID
local lockKey = KEYS[1]
local requestId = ARGV[1]

-- 获取锁的值
local lockValue = redis.call('GET', lockKey)

-- 检查请求ID是否匹配锁的持有者
if lockValue == requestId then
    -- 删除锁
    redis.call('DEL', lockKey)
    return 1
else
    return 0
end

Service层实现

java 复制代码
package pub.qingyun.service;

import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author CQY
 * @version 1.0
 * @date 2024/7/10 10:29
 **/

@Service
@Slf4j
public class RedisLockService {

    private static final String LOCK_KEY = "distributed-lock";

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    private static final DefaultRedisScript<Long> TRY_LOCK_SCRIPT = new DefaultRedisScript<>();
    private static final DefaultRedisScript<Long> RELEASE_LOCK_SCRIPT = new DefaultRedisScript<>();

    static {
        TRY_LOCK_SCRIPT.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/tryLock.lua")));
        TRY_LOCK_SCRIPT.setResultType(Long.class);

        RELEASE_LOCK_SCRIPT.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/releaseLock.lua")));
        RELEASE_LOCK_SCRIPT.setResultType(Long.class);
    }

    /**
     * 尝试获取分布式锁。
     *
     * @param requestId  请求ID,用于唯一标识锁的持有者。
     * @param expireTime 锁的过期时间(秒)。
     * @return 如果成功获取锁返回true,否则返回false。
     */
    public boolean tryLock(String requestId, int expireTime) {
        Object result = stringRedisTemplate.execute(TRY_LOCK_SCRIPT,
                List.of(LOCK_KEY),
                requestId,
                String.valueOf(expireTime));
        assert result != null;
        return Long.parseLong(result.toString()) == 1L;
    }

    /**
     * 释放分布式锁。
     *
     * @param requestId 请求ID,必须与获取锁时使用的相同。
     * @return 如果锁成功释放返回true,否则返回false。
     */
    public boolean releaseLock(String requestId) {
        Object result = stringRedisTemplate.execute(RELEASE_LOCK_SCRIPT,
                List.of(LOCK_KEY),
                requestId);
        assert result != null;
        return Long.parseLong(result.toString()) == 1L;
    }
}

Controller调用示例代码

java 复制代码
package pub.qingyun.controller;


import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import pub.qingyun.service.RedisLockService;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author CQY
 * @version 1.0
 * @date 2024/7/10 10:43
 **/
@Slf4j
@RestController
public class LuaLockController {

    // Lock timeout in seconds
    private static final int LOCK_TIMEOUT_SECONDS = 30000;

    private final RedisLockService lockService;

    @Autowired
    public LuaLockController(RedisLockService lockService) {
        this.lockService = lockService;
    }

    /**
     * 尝试获取锁并执行一些操作,然后释放锁。
     * 通过尝试获取锁来确保操作的原子性,避免并发问题
     */
    @GetMapping("/performOperation")
    public String performOperation() {
        // 使用UUID作为请求ID
        String requestId = UUID.randomUUID().toString();
        try {
            // 尝试获取锁
            boolean tryLock = lockService.tryLock(requestId, LOCK_TIMEOUT_SECONDS);
            log.info("获取锁[{}][{}]", requestId, tryLock);
            if (tryLock) {
                // 执行关键操作
                log.info("开始执行主任务[{}]...", requestId);
                TimeUnit.SECONDS.sleep(5); // 模拟耗时操作
                log.info("任务[{}]执行完成", requestId);
                return requestId + " completed successfully.";
            } else {
                log.info("无法获取锁,任务[{}]被拒绝", requestId);
                return "无法获取锁,任务[" + requestId + "]被拒绝";
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("Interrupted while performing operation.", e);
            return "任务[" + requestId + "]执行失败";
        } finally {
            // 释放锁
            boolean releaseLock = lockService.releaseLock(requestId);
            log.info("释放锁[{}][{}]", requestId, releaseLock);
        }
    }
}

SpringBoot3+Redisson实现分布式锁

添加依赖包

xml 复制代码
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.20.0</version>
</dependency>

配置类

java 复制代码
@Configuration
package pub.qingyun.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;


/**
 * @author CQY
 * @version 1.0
 * @date 2024/7/5 10:58
 **/
@Configuration
public class RedisConfig {

    @Value("${spring.data.redis.host}")
    private String host;
    @Value("${spring.data.redis.port}")
    private String port;

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://" + host + ":" + port + "");
        return Redisson.create(config);
    }


    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}

实现类

java 复制代码
 	@Resource
    private RedissonClient redissonClient;
    
	public void example() {
        RLock rlock = redissonClient.getLock("myLock");
        try {
            boolean locked = rlock.tryLock(0, 800, TimeUnit.MILLISECONDS);
            if (locked) {
                // TODO
            } else {
                log.warn("Thread[{}]Could not acquire lock.", Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("Error occurred while trying to acquire lock.", e);
        } finally {
            if (rlock.isHeldByCurrentThread()) {
                rlock.unlock();
            }
        }
    }
    
相关推荐
fat house cat_33 分钟前
【redis】线程IO模型
java·redis
qq_463944861 小时前
【Spark征服之路-2.2-安装部署Spark(二)】
大数据·分布式·spark
敖云岚2 小时前
【Redis】分布式锁的介绍与演进之路
数据库·redis·分布式
正在努力Coding2 小时前
kafka(windows)
分布式·kafka
让我上个超影吧4 小时前
黑马点评【基于redis实现共享session登录】
java·redis
懒羊羊大王呀7 小时前
Ubuntu20.04中 Redis 的安装和配置
linux·redis
禺垣8 小时前
区块链技术概述
大数据·人工智能·分布式·物联网·去中心化·区块链
John Song9 小时前
Redis 集群批量删除key报错 CROSSSLOT Keys in request don‘t hash to the same slot
数据库·redis·哈希算法
zhuhit11 小时前
FASTDDS的安全设计
分布式·机器人·嵌入式
暗影八度11 小时前
Spark流水线+Gravitino+Marquez数据血缘采集
大数据·分布式·spark