Redis锁的使用

起因:分布式环境下需对并发进行逻辑一致性控制

架构:springboot2、Redis

IDEA实操

  1. 先新建RedisLock组件

    注:释放锁使用lua脚本保持原子性

    @Component
    @Slf4j
    public class RedisLock { private final RedisTemplate redisTemplate; public RedisLock(RedisTemplate redisTemplate) {
    this.redisTemplate = redisTemplate;
    } /**
    * 如果已经存在返回false,否则返回true
    *
    * @param key
    * @param value
    * @return
    */
    public Boolean setNx(String key, String value, Long expireTime, TimeUnit mimeUnit) { if (key == null || value == null) {
    return false;
    } // 在spiring boot 2 可以直接使用 redisTemplate的setIfAbsent设置key-value和过期时间,是原子性
    Boolean tf = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, mimeUnit); return tf; } /**
    * 获取数据
    *
    * @param key
    * @return
    */
    public Object get(String key) { if (key == null) {
    return null;
    }
    return redisTemplate.opsForValue().get(key);
    } /**
    * 删除
    *
    * @param key
    * @return
    */
    public void remove(Object key) { if (key == null) {
    return;
    } redisTemplate.delete(key);
    } /**
    * 加锁
    *
    * @param key key
    * @param waitTime 等待时间,在这个时间内会多次尝试获取锁,超过这个时间还没获得锁,就返回false
    * @param interval 间隔时间,每隔多长时间尝试一次获的锁
    * @param expireTime key的过期时间
    */
    public Boolean lock(String key, Long waitTime, Long interval, Long expireTime) { String value = UUID.randomUUID().toString().replaceAll("-", "").toLowerCase(); Boolean flag = setNx(key, value, expireTime, TimeUnit.MILLISECONDS); // 尝试获取锁 成功返回
    if (flag) {
    return true;
    } else {
    // 获取失败 // 现在时间
    long newTime = System.currentTimeMillis(); // 等待过期时间
    long loseTime = newTime + waitTime; // 不断尝试获取锁成功返回
    while (System.currentTimeMillis() < loseTime) { Boolean testFlag = setNx(key, value, expireTime, TimeUnit.MILLISECONDS);
    if (testFlag) {
    return true;
    } try {
    Thread.sleep(interval);
    } catch (InterruptedException e) {
    log.error("获取锁异常", e);
    }
    }
    }
    return false;
    } /**
    * 释放锁
    *
    * @param key
    * @return
    */
    public void unLock(String key) {
    remove(key);
    } public Boolean setIfAbsent(String key, String value) {
    Boolean tf = redisTemplate.opsForValue().setIfAbsent(key, value);
    redisTemplate.expire(key, 60, TimeUnit.DAYS);
    return tf;
    } /**
    * 获取分布式锁
    * @param key
    * @param value
    * @param expireTime
    * @return
    */
    public Boolean lock(String key, String value, Long expireTime) {
    Boolean flag = setNx(key, value, expireTime, TimeUnit.MILLISECONDS);
    if (flag == null || !flag) {
    return false;
    } return true;
    } /***
    * lua释放分布式锁
    * @param key
    * @param value
    */
    public void unLock(String key, String value) {
    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
    Long result = (Long) redisTemplate.execute(redisScript, Arrays.asList(key),value);
    if (result == null || result == 0) {
    log.info("释放锁(" + key + "," + value + ")失败,该锁不存在或锁已经过期");
    } else {
    log.info("释放锁(" + key + "," + value + ")成功");
    }
    } /***
    * lua不存在才插入队列
    * @param key
    * @param values
    * jackson序列化后String会变成\"xxx\",不要直接用ARGV去转成number使用
    */
    public void putAllIfAbsent(String key, List<String> values,long timeout,TimeUnit unit) {
    Long rawTimeout = TimeoutUtils.toMillis(timeout, unit);
    String script = "if redis.call('exists', KEYS[1]) == 0 then local listValues = ARGV" +
    " for k,v in ipairs(listValues) do " +
    " redis.call('RPUSH',KEYS[1],v) " +
    " end " +
    " redis.call('expire',KEYS[1],KEYS[2])" +
    " end";
    RedisScript<Void> redisScript = new DefaultRedisScript<>(script);
    redisTemplate.execute(redisScript, Arrays.asList(key,String.valueOf(rawTimeout)),values.toArray());
    }
    }
  2. 业务使用

    进入逻辑前先判断有没有锁

    用try-catch包住业务逻辑,finally释放锁,以防抛错直接占有锁

    if (redisLock.lock(redisLockKey, value, 60 * 1000L)) {
    try {
    return reactiveMongoTemplate.find(query, JrRedPacketTask.class)
    .collectList()
    .flatMap(taskList -> {
    if (taskList.size() > 0) {
    //缓存到redis中
    return reactiveRedisTemplate.opsForValue().set(redisKey, taskList, Duration.ofDays(1))
    .then(Mono.just(taskList));
    }
    return Mono.just(taskList);
    });
    } finally {
    redisLock.unLock(redisLockKey, value);// 释放锁
    } } else {
    try {
    //休息,休息一会儿
    Thread.sleep(8);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }

Redis分布式锁应用的更多相关文章

  1. 利用redis分布式锁的功能来实现定时器的分布式

    文章来源于我的 iteye blog http://ak478288.iteye.com/blog/1898190 以前为部门内部开发过一个定时器程序,这个定时器很简单,就是配置quartz,来实现定 ...

  2. Redis分布式锁

    Redis分布式锁 分布式锁是许多环境中非常有用的原语,其中不同的进程必须以相互排斥的方式与共享资源一起运行. 有许多图书馆和博客文章描述了如何使用Redis实现DLM(分布式锁管理器),但是每个库都 ...

  3. redis分布式锁和消息队列

    最近博主在看redis的时候发现了两种redis使用方式,与之前redis作为缓存不同,利用的是redis可设置key的有效时间和redis的BRPOP命令. 分布式锁 由于目前一些编程语言,如PHP ...

  4. redis咋么实现分布式锁,redis分布式锁的实现方式,redis做分布式锁 积极正义的少年

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...

  5. spring boot redis分布式锁

    随着现在分布式架构越来越盛行,在很多场景下需要使用到分布式锁.分布式锁的实现有很多种,比如基于数据库. zookeeper 等,本文主要介绍使用 Redis 做分布式锁的方式,并封装成spring b ...

  6. Redis分布式锁的正确实现方式

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...

  7. Redis分布式锁---完美实现

    这几天在做项目缓存时候,因为是分布式的所以需要加锁,就用到了Redis锁,正好从网上发现两篇非常棒的文章,来和大家分享一下. 第一篇是简单完美的实现,第二篇是用到的Redisson. Redis分布式 ...

  8. redis分布式锁实践

    分布式锁在多实例部署,分布式系统中经常会使用到,这是因为基于jvm的锁无法满足多实例中锁的需求,本篇将讲下redis如何通过Lua脚本实现分布式锁,不同于网上的redission,完全是手动实现的 我 ...

  9. Redis分布式锁的try-with-resources实现

    Redis分布式锁的try-with-resources实现 一.简介 在当今这个时代,单体应用(standalone)已经很少了,java提供的synchronized已经不能满足需求,大家自然 而 ...

  10. 关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁

    首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...

随机推荐

  1. 分布式文件存储 CephFS的应用场景

    块存储 (适合单客户端使用) 典型设备:磁盘阵列,硬盘. 使用场景: a. docker容器.虚拟机远程挂载磁盘存储分配. b. 日志存储. 文件存储 (适合多客户端有目录结构) 典型设备:FTP.N ...

  2. Docker Compose配置文件详解(V3)

    Docker Compose配置文件是Docker Compose的核心,用于定义服务.网络和数据卷.格式为YAML,默认路径为./docker-compose.yml,可以使用.yml或.yaml扩 ...

  3. MongoDB 副本集的用户和权限一般操作步骤

    步骤总结: 在主节点上添加超管用户,副本集会自动同步 按照仲裁者.副本节点.主节点的先后顺序关闭所有节点服务 创建副本集认证的key文件,复制到每个服务所在目录 修改每个服务的配置文件,增加参数 启动 ...

  4. docker还原挂载在宿主机的mysql数据

    ps:有个项目的数据库之前是docker的mysql镜像,数据都去mysql的镜像里面拿数据,但是目前镜像里面的数据库是空的,所以现在来说明一下如何恢复数据 1.创建数据卷 docker volume ...

  5. 报时机器人的rasa shell执行流程分析

      本文以报时机器人为载体,介绍了报时机器人的对话能力范围.配置文件功能和训练和运行命令,重点介绍了rasa shell命令启动后的程序执行过程. 一.报时机器人项目结构 1.对话能力范围 (1)能够 ...

  6. 把两个数据结构相同的数组(数组下有n个对象)合并成一个数组

    数据拼接 有一个数组 let arr1 = [ {name:''lili",age:14}, {name:''小明",age:16}, {name:''张三",age:2 ...

  7. Flink的异步算子的原理及使用

    1.简介 Flink的特点是高吞吐低延迟.但是Flink中的某环节的数据处理逻辑需要和外部系统交互,调用耗时不可控会显著降低集群性能.这时候就可能需要使用异步算子让耗时操作不需要等待结果返回就可以继续 ...

  8. 云的安全组和网络ACL

    云的安全组和网络ACL 1.流量控制: 安全组是云服务器.数据库等实例级别的流量控制 ​ ACL是子网级别的流量控制 2.规则: 安全组和网络ACL都支持允许规则和拒绝规则 3.状态: 安全组有状态( ...

  9. Nginx通用优化示例

    user nginx; worker_processes auto; #worket_cpu_affinity auto; error_log /var/log/nginx/error.log war ...

  10. 5.websocket原理

      websocket协议原理 1.WebSocket协议是基于TCP的一种新的协议.WebSocket最初在HTML5规范中被引用为TCP连接,作为基于TCP的套接字API的占位符.它实现了浏览器与 ...