侧边栏壁纸
博主头像
此昵称不存在 博主等级

行动起来,活在当下

  • 累计撰写 35 篇文章
  • 累计创建 7 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

基于Redis实现分布式锁

Administrator
2022-10-29 / 0 评论 / 0 点赞 / 76 阅读 / 0 字

基于Redis的分布式锁实现思路

  • 利用set nx ex获取锁,并设置过期时间,保存线程标识
  • 锁释放时先判断线程标识是否与自己的一致,一致则删除 避免误删

特性

  • 利用set nx 满足锁的互斥性
  • 利用set ex 保证故障时锁依然释放,避免死锁,提高安全性
  • 利用Redis集群保证高可用和高并发特性

简单的Redis分布式锁示例代码

获取锁

@Override
    public boolean tryLock(Long timeoutSec) {
        System.out.println("tryLock:" + ID_PREFIX);
        // 获取当前线程的id
        String id = ID_PREFIX + "-" + Thread.currentThread().getId();
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, id, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(aBoolean);
    }

释放锁

@Override
    public void unlock() {
        redisTemplate.delete(KEY_PREFIX + name);
    }

整体示例代码

package com.hmdp.utils;

import cn.hutool.core.lang.UUID;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.concurrent.TimeUnit;

public class SimpleRedisLock implements ILock{

    private StringRedisTemplate redisTemplate;

    private String name;

    public SimpleRedisLock(String name, StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    private final String KEY_PREFIX = "lock:";

    private final String ID_PREFIX = UUID.randomUUID().toString(true);
    @Override
    public boolean tryLock(Long timeoutSec) {
        // 获取当前线程的id
        String id = ID_PREFIX + "-" + Thread.currentThread().getId();
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, id, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(aBoolean);
    }

    @Override
    public void unlock() {
        redisTemplate.delete(KEY_PREFIX + name);
    }
}

上述简易式分布式锁存在的问题

问题

当业务代码执行时间超过锁的过去时间,导致锁被超时释放,另外的线程此时进入代码块获取锁后,第一个线程业务代码执行完成,开始执行释放锁代码,由于删除同名key,导致第二个线程尚未执行完成,锁已被释放,导致后续线程可以一直获取锁,造成并发安全问题

解决方案

为每个锁的内容设置唯一标识,例如 设置UUID+当前线程ID,删除时对内容进行判断,若为内容相同则删除锁,否则不删除

@Override
    public void unlock() {
        System.out.println("unLock:" + ID_PREFIX);
        // ID_PREFIX为UUID 凭借线程号
        String id = ID_PREFIX + "-" + Thread.currentThread().getId();
        // 获取当前redis中锁的值
        String value = redisTemplate.opsForValue().get(KEY_PREFIX + name);
        // 判断是否相等
        if (id.equals(value)){
            redisTemplate.delete(KEY_PREFIX + name);
        }
    }

上述问题依旧存在问题,当代码执行到redisTemplate.delete(KEY_PREFIX + name);前出现了阻塞,则同样会出现误删除的情况,此时必须保证redis分布式锁判断标识与删除的原子性,此处可引入LUA脚本,实现原子性操作,避免锁被误删除的情况
LUA脚本示例如下:

if (redis.call('get', KEYS[1]) == ARGV[1]) then
    -- 释放锁
    return redis.call('del', KEYS[1])
end
return 0

释放锁相关java代码

@Override
    public void unlock() {
        String id = ID_PREFIX + "-" + Thread.currentThread().getId();
        // 调用lua脚本
        redisTemplate.execute(redisScript, Collections.singletonList(KEY_PREFIX + name), id);
    }

最终代码

package com.hmdp.utils;

import cn.hutool.core.lang.UUID;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

public class SimpleRedisLock implements ILock{

    private StringRedisTemplate redisTemplate;

    private String name;

    public SimpleRedisLock(String name, StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
        this.name = name;
    }

    private final String KEY_PREFIX = "lock:";

    private final String ID_PREFIX = UUID.randomUUID().toString(true);

    private static DefaultRedisScript<Long> redisScript;

    static {
        // 加载Lua脚本
        redisScript = new DefaultRedisScript<>();
        redisScript.setLocation(new ClassPathResource("unLock.lua"));
        redisScript.setResultType(Long.class);
    }

    @Override
    public boolean tryLock(Long timeoutSec) {
        // 获取当前线程的id
        String id = ID_PREFIX + "-" + Thread.currentThread().getId();
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, id, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(aBoolean);
    }

    @Override
    public void unlock() {
        String id = ID_PREFIX + "-" + Thread.currentThread().getId();
        // 调用lua脚本
        redisTemplate.execute(redisScript, Collections.singletonList(KEY_PREFIX + name), id);
    }
}

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区