zoukankan      html  css  js  c++  java
  • 使用redis事物解决stringRedisTemplate.setIfAbsent()并设置过期时间遇到的问题

    spring-date-redis版本:1.6.2
    场景:在使用setIfAbsent(key,value)时,想对key设置一个过期时间,同时需要用到setIfAbsent的返回值来指定之后的流程,所以使用了以下代码:

    boolean store = stringRedisTemplate.opsForValue().setIfAbsent(key,value);
    if(store){
      stringRedisTemplate.expire(key,timeout); 
      // todo something...  
    }

    这段代码是有问题的:当setIfAbsent成功之后断开连接,下面设置过期时间的代码stringRedisTemplate.expire(key,timeout); 是无法执行的,这时候就会有大量没有过期时间的数据存在数据库。想到一个办法就是添加事务管理,修改后的代码如下:

    stringRedisTemplate.setEnableTransactionSupport(true);
    stringRedisTemplate.multi();
    boolean store = stringRedisTemplate.opsForValue().setIfAbsent(key,value);
    if(store){
      stringRedisTemplate.expire(key,timeout);   
    }
    stringRedisTemplate.exec();
    if(store){
        // todo something...
    }

    这样就保证了整个流程的一致性。本因为这样就可以了,可是事实总是不尽人意,因为我在文档中发现了以下内容:
    图片描述

    加了事务管理之后,setIfAbsent的返回值竟然是null,这样就没办法再进行之后的判断了。

    好吧,继续解决:

    stringRedisTemplate.setEnableTransactionSupport(true);
    stringRedisTemplate.multi();
    String result = stringRedisTemplate.opsForValue().get(key);
    if(StringUtils.isNotBlank(result)){
        return false;
    }
    // 锁的过期时间为1小时
    stringRedisTemplate.opsForValue().set(key, value,timeout);
    stringRedisTemplate.exec();
    
    // todo something...

    上边的代码其实还是有问题的,当出现并发时,String result = stringRedisTemplate.opsForValue().get(key); 这里就会有多个线程同时拿到为空的key,然后同时写入脏数据。


    最终解决方法:

    1. 使用stringRedisTemplate.exec();的返回值判断setIfAbsent是否成功
    stringRedisTemplate.setEnableTransactionSupport(true);
    stringRedisTemplate.multi();
    stringRedisTemplate.opsForValue().setIfAbsent(lockKey,JSON.toJSONString(event));
    stringRedisTemplate.expire(lockKey,Constants.REDIS_KEY_EXPIRE_SECOND_1_HOUR, TimeUnit.SECONDS);
    List result = stringRedisTemplate.exec(); // 这里result会返回事务内每一个操作的结果,如果setIfAbsent操作失败后,result[0]会为false。
    if(true == result[0]){
      // todo something...
    }
    1. 将redis版本升级到2.1以上,然后使用

    图片描述
    直接在setIfAbsent中设置过期时间

    update : 
    java 使用redis的事务时不能直接用Api中的multi()和exec(),这样multi()和exec()两次使用的stringRedisTemplate不是一个connect,会导致死锁,正确方式如下:

        private Boolean setLock(RecordEventModel event) {
            String lockKey = event.getModel() + ":" + event.getAction() + ":" + event.getId() + ":" + event.getMessage_id();
            log.info("lockKey : {}" , lockKey);
            SessionCallback<Boolean> sessionCallback = new SessionCallback<Boolean>() {
                List<Object> exec = null;
                @Override
                @SuppressWarnings("unchecked")
                public Boolean execute(RedisOperations operations) throws DataAccessException {
                    operations.multi();
                    stringRedisTemplate.opsForValue().setIfAbsent(lockKey,JSON.toJSONString(event));
                    stringRedisTemplate.expire(lockKey,Constants.REDIS_KEY_EXPIRE_SECOND_1_HOUR, TimeUnit.SECONDS);
                    exec = operations.exec();
                    if(exec.size() > 0) {
                        return (Boolean) exec.get(0);
                    }
                    return false;
                }
            };
            return stringRedisTemplate.execute(sessionCallback);
        }
  • 相关阅读:
    纳尼?不用码代码,就可回归主流程,一只海豚就可以做到
    教育产品-组件化视觉设计实践
    从整理看视觉设计(网易云课堂我的学习中心-微专业视觉优化)
    搜索意图识别浅析
    如何配置使用Dnsmasq
    如何实现最佳的跨平台游戏体验?Unity成亮解密实时渲染技术!
    PAT 1024. Palindromic Number
    PAT 1023. Have Fun with Numbers
    PAT 1022. Digital Library
    PAT 1021. Deepest Root
  • 原文地址:https://www.cnblogs.com/exmyth/p/11324777.html
Copyright © 2011-2022 走看看