基于redis+lua脚本的分布式锁实现

传统方式实现分布式锁弊端

传统方法的问题:先 SET 再 EXPIRE

在许多使用 Redis 的 Spring Boot 应用中,开发者可能会选择先使用 SET 命令设置一个键,然后使用 EXPIRE 命令为这个键设置过期时间。这种方法简单直观,但存在一些潜在的问题:

  • 非原子操作:SET 和 EXPIRE 两个命令分开执行,它们之间可能会由于各种原因(比如网络延迟或程序崩溃)造成执行中断,这样可能会出现键被设置了但没有设置过期时间的情况,导致锁永久存在。
  • 竞态条件:在高并发的环境中,如果在 SET 和 EXPIRE 之间有其他进程也尝试设置相同的键,可能会造成预期外的行为。例如,一个进程可能会覆盖了另一个进程的锁,但使用了原来的过期时间,这可能会导致锁提前被释放。

Lua 脚本和分布式锁的关系

什么是 Lua 脚本?

Lua 是一种轻量级的脚本语言,以其简洁和易嵌入的特性广受欢迎。它通常被用于游戏开发和嵌入到应用程序中,提供灵活的扩展和自定义功能。在 Redis 中,Lua 脚本可以执行原子操作,这是实现分布式锁的关键。

为什么选择 Lua 脚本来实现分布式锁?

使用 Lua 脚本实现分布式锁具有多个优势:
● 原子性:Redis 可以保证 Lua 脚本的原子性执行,这意味着在脚本执行期间不会有其他命令插入执行。
● 效率:通过减少网络往返次数,Lua 脚本的执行效率非常高。
● 简洁性:使用 Lua 脚本可以用较少的代码实现复杂的逻辑。

分布式锁的实现原理

分布式锁主要用于控制分布式环境中多个节点对共享资源的访问。实现原理包括:
● 锁的创建:如果锁资源可用(即不存在),则创建锁。
● 锁的维持:通过定时更新锁的有效时间来维持锁的持有状态。
● 锁的释放:完成资源访问后,释放锁,使其它节点可以访问资源。

基于redis+Lua 脚本的分布式锁的实现

引入依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

获取分布式锁

@Service
public class RedisLockService {


    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 尝试获取一个分布式锁。
     *
     * @param key        锁的唯一标识符。
     * @param expireTime 锁的过期时间,单位是秒。
     * @return 如果成功获取锁,返回 true,否则返回 false。
     */
    public boolean acquireLock(String key, Long expireTime) {
        //value设置一个随机值
        String value = UUID.randomUUID().toString().replace("-", "");
        // Lua 脚本,用于检查并设置锁
        // 如果 key 不存在,则设置 key 并设置其过期时间,然后返回 1
        // 如果 key 存在,直接返回 0,表示锁已经被其他客户端持有
        String luaScript =
                "if redis.call('exists', KEYS[1]) == 0 then " +
                        "    redis.call('set', KEYS[1], ARGV[1], 'EX', ARGV[2]); " +
                        "    return 1; " +
                        "else " +
                        "    return 0; " +
                        "end;";

        // 创建 RedisScript 对象,用于执行 Lua 脚本
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(luaScript);  // 设置脚本
        redisScript.setResultType(Long.class); // 设置脚本返回类型为 Long

        // 执行 Lua 脚本
        Long result = stringRedisTemplate.execute(redisScript, Collections.singletonList(key), value, String.valueOf(expireTime));

        // 判断是否成功获取锁
        return result != null && result == 1;
    }

}

测试

@RestController
@RequestMapping("/lock")
public class RedisLockController {

    @Autowired
    private RedisLockService redisLockService;

    @GetMapping("/acquireLock")
    public String acquireLock(@RequestParam String key, @RequestParam Long expireTime) {
        boolean isLocked = redisLockService.acquireLock(key, expireTime);
        return isLocked ? "Lock acquired successfully." : "Failed to acquire lock.";
    }
}

访问 http://127.0.0.1:8080/lock/acquireLock?key=user:test11&expireTime=60

成功

在这里插入图片描述

失败

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值