SpringBoot3+Redis实现分布式锁

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

相关依赖包

<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配置

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

-- 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

-- 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层实现

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调用示例代码

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实现分布式锁

添加依赖包

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.20.0</version>
</dependency>

配置类

@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;
    }
}

实现类

 	@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();
            }
        }
    }
    
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值