基于redis的分布式锁

一、redis分布式锁基本信息

1.详细讲解:

Redis 分布式锁是一种用于控制分布式系统中多个进程对共享资源的并发访问的机制。通过 Redis 的原子操作和过期时间功能,可以实现一个简单而有效的分布式锁。接下来,我们将详细介绍其工作原理、基本操作步骤以及代码实现,并提供详细的注释。

2.基本原理

2.1获取锁:

使用 SET 命令尝试设置一个键,并使用 NX 参数(如果键不存在则设置)和 PX 参数(设置键的过期时间)。
如果 SET 操作返回成功(即 OK),则表示获取锁成功。

2.2释放锁:

释放锁时需要确保只有持有锁的客户端才能释放锁,因此需要在释放锁时检查锁的值是否是当前客户端的标识。
使用 Lua 脚本来保证原子性:检查锁的值并删除锁。

二、代码示例

1.redis加锁以及解锁的工具类

1.1注意事项:

1.1.1 由于RedisTemplate交由spring管理,所以我们工具类也需要注册给spring,之后通过注入方式使用工具类,这样redisTemplate就不会是null;
1.1.2 用脚本的方式执行redisTemplate.execute会出现异常java.lang.UnsupportedOperationException: io.lettuce.core.output.ValueOutput does not support set(long),好像是返回值无法转换,由于redis版本不匹配,我这边redis版本是5.0.14.1;(暂未解决脚本执行后续会修改);

@Slf4j
@Component
public class LockUtil {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    //定义变量
    private final Object RELEASE_SUCCESS = 1;
    private final Object RELEASE_ERROR = 0;


    /**
     * 以阻塞方式的获取锁
     * @param key         key
     * @param value       value
     * @param lockTimeout 锁超时时间
     * @param getTimeout  获取锁超时时间
     * @return
     */
    public boolean lockBlock(String key, String value, long lockTimeout, long getTimeout, TimeUnit timeUnit) {
        long start = System.currentTimeMillis();
        //循环执行是否能加锁成功判断
        while (true) {
            //检测是否超时
            if (System.currentTimeMillis() - start > getTimeout) {
                log.error(Thread.currentThread().getName() + "get lock timeout");
                return false;
            }
            //执行set命令 ,如果返回 true,表示获取锁成功;如果返回 false,表示获取锁失败。
            Boolean absent = redisTemplate.opsForValue().setIfAbsent(key, value, lockTimeout, timeUnit);
            //是否成功获取锁
            if (absent != null && absent) {
                return true;
            } else {
                log.info(Thread.currentThread().getName() + "get lock fail:{},{}", key, value);
            }
        }
    }
    
    /**
     * 解锁:由于我这边无法执行redis脚本,一直返回异常,下面redis操作并非是原子性操作
     * @param key 加锁key
     * @param value 锁value
     * @return 返回是否解锁成功
     */
    public boolean unlock(String key, String value) {
        Object result = 0;
        String o = (String) redisTemplate.opsForValue().get(key);
        if (o != null && o.equals(value)) {
            Boolean delete = redisTemplate.delete(key);
            if (delete){
                result = 1;
            }
        }
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        log.error(Thread.currentThread().getName() + "unlock error");
        return false;

    }
}

2.调用实现

采用多线程方式模拟调用

@Slf4j
@RestController
public class TestController {
    @Resource
    private LockUtil lockUtil = new LockUtil();

    @GetMapping(value = "/redisLock")
    public void testRedis(@RequestParam(value = "key") String key) throws ExecutionException, InterruptedException {
        ExecutorService threadPoolTaskExecutor = Executors.newFixedThreadPool(10);
        CompletableFuture<Void> completableFuture1 = this.getCompletableFuture(threadPoolTaskExecutor, key);
        CompletableFuture<Void> completableFuture2 = this.getCompletableFuture(threadPoolTaskExecutor, key);
        CompletableFuture.allOf(completableFuture1, completableFuture2).get();
        threadPoolTaskExecutor.shutdown();
    }

    private CompletableFuture<Void> getCompletableFuture( ExecutorService threadPoolTaskExecutor,String key){
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
            String value = UUID.randomUUID().toString();
            try {
                if (lockUtil.lockBlock(key, value, 3L, 10L, TimeUnit.SECONDS)) {
                    log.info(Thread.currentThread().getName() + "获取锁成功,value is {}", value);
                    Thread.sleep(2000);
                } else {
                    log.info(Thread.currentThread().getName() + "获取锁失败,value is {}", value);
                }
            } catch (InterruptedException e) {

                log.info(Thread.currentThread().getName() +"获取锁异常,value is {}", value);
            } finally {
                if (lockUtil.unlock(key, value)) {
                    log.info(Thread.currentThread().getName() + "释放锁,value is {}", value);
                }
            }
        }, threadPoolTaskExecutor);
        return  completableFuture;
    }
}

3.测试结果显示

运行结果

4.上面方法解决的问题

4.1通过对于redis key添加过期防止锁无法释放造成死锁;
4.2通过加锁时间限制防止加锁失败一直加锁,造成死锁;
4.3通过value比对保证解的锁是自己持有的;

5.上面方法存在的问题

5.1代码执行超过redis存放时间:

redis释放之后,别人可以获取锁这样锁就相当于失效,解决方案:看门狗机制,我们redisson已经解决上面问题了

5.2代码实现

1.redisson配置

@Configuration
@Slf4j
public class RedissonConfig {
    @Bean
    public RedissonClient createRedissonClient() {
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://127.0.0.1:6379");
                //.setPassword(""); // 如果没有密码,可以省略这一行

        return Redisson.create(config);
    }
}

2.代码调用实现

@RestController
public class TestController {

    @Resource
    private RedissonClient redissonClient;

    @GetMapping(value = "/redisLock")
    public void testRedis(@RequestParam(value = "key") String key) throws ExecutionException, InterruptedException {
        ExecutorService threadPoolTaskExecutor = Executors.newFixedThreadPool(10);
        CompletableFuture<Void> completableFuture1 = this.getCompletableFuture(threadPoolTaskExecutor, key);
        CompletableFuture<Void> completableFuture2 = this.getCompletableFuture(threadPoolTaskExecutor, key);
        CompletableFuture.allOf(completableFuture1, completableFuture2).get();
        threadPoolTaskExecutor.shutdown();
    }

    private CompletableFuture<Void> getCompletableFuture(ExecutorService threadPoolTaskExecutor, String key) {
        RLock lock = redissonClient.getLock(key);
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
            try {
                // 尝试获取锁,等待时间10秒,上锁时间5秒
                if (lock.tryLock(10, 5, TimeUnit.SECONDS)) {
                    log.info(Thread.currentThread().getName() + "获取锁成功");
                    Thread.sleep(2000);
                } else {
                    log.info(Thread.currentThread().getName() + "获取锁失败");
                }
            } catch (InterruptedException e) {

                log.info(Thread.currentThread().getName() + "获取锁异常");
            } finally {
                lock.unlock();
                log.info(Thread.currentThread().getName() + "释放锁成功");
            }
        }, threadPoolTaskExecutor);
        return completableFuture;
    }
}

3.运行结果

在这里插入图片描述
不足之处,望海涵,希望大佬指点;

相关推荐

  1. 分布式(3):Redis基于set命令分布式

    2024-06-18 18:18:04       6 阅读
  2. 基于redis分布式实现方案

    2024-06-18 18:18:04       33 阅读
  3. RedisRedis分布式基本原理和具体实现

    2024-06-18 18:18:04       12 阅读
  4. redis分布式-----基于RedLock算法实现分布式

    2024-06-18 18:18:04       19 阅读
  5. redis分布式-----基于redisson实现分布式

    2024-06-18 18:18:04       17 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-18 18:18:04       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-18 18:18:04       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-18 18:18:04       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-18 18:18:04       18 阅读

热门阅读

  1. 联合类型和交叉类型

    2024-06-18 18:18:04       6 阅读
  2. 每天一个项目管理概念之干系人

    2024-06-18 18:18:04       7 阅读
  3. linux常用指令

    2024-06-18 18:18:04       3 阅读
  4. 2024大数据面试题汇总(更新中。。。)

    2024-06-18 18:18:04       8 阅读
  5. mysql中社区版如何杀死锁

    2024-06-18 18:18:04       5 阅读
  6. TypeScript中的枚举

    2024-06-18 18:18:04       7 阅读
  7. Python网络安全项目开发实战,如何看清Web攻击

    2024-06-18 18:18:04       8 阅读
  8. Ubuntu 22.04 一键安装 Oracle 11GR2 单机

    2024-06-18 18:18:04       6 阅读
  9. Spring Boot框架的原理及应用详解(一)

    2024-06-18 18:18:04       7 阅读
  10. 事件传播机制 与 责任链模式

    2024-06-18 18:18:04       4 阅读
  11. 有哪些技术可代替docker?

    2024-06-18 18:18:04       4 阅读