栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JAVA操作REDIS执行原子操作

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JAVA操作REDIS执行原子操作

JAVA操作REDIS执行原子操作
  • JAVA操作REDIS执行原子操作
    • 为什么要使用原子操作

JAVA操作REDIS执行原子操作 为什么要使用原子操作

众所周知,redis 作为数据库的前置库,给数据库使用节省了很多请求,很多请求再查询缓存后就可以直接返回需要的数据,作为一款查询利器,效率上无懈可击。

但是如果用于记录数据次数,或者记录一些其他数据的时候,就得考虑线程影响问题,俗了就是 先读后写 后读先写 的问题,这个不用解释了吧,不了解的自行百度。这样记录的话,数据肯定是不对的,有可能需要计数100 ,由于先读后写 ,造成最后值不到100。

解决这个问题,也就是需要redis 的原子操作,也类似于数据库的原子性一样,进行多表操作时候 必须保证执行事务之后数据的一致性。

问题出现了,就要想办法去解决,这里使用的是redis 的 increment 来确保数据的原子,使用这个的场景都差不多,首先说下我的业务场景,其他的场景自行判断是否可行,

业务场景,
构建一个抽奖小程序,限制用户每天的参与次数,每天的参与次数就是用redis 计算的,超过之后就不能再进行抽奖了。

类似这种的抽奖活动但不是消耗钻石或者计分,就单单的抽奖,进去就可以随便抽奖,
业务场景已经流程都有了 ,后续就是实际的代码实现 , 也相对简单,
1 缓存拿之前的数据, 没有就初始化,
2 然后点了抽奖后给之前的数据 +1
3 +1后再放回缓存里

就这三步, 一下是相关功能的代码实现

1 拿缓存数据 没有初始化

 
    public Map getRedis(String onlyOne, String type) {
        String newKey = type + onlyOne;
        Boolean hasKey = redisTemplate.hasKey(newKey);
        Map objectHashMap = new HashMap<>(1);
        if (hasKey) {
            String jsonString = redisTemplate.opsForValue().get(newKey);
            objectHashMap.put(newKey, jsonString);
            return objectHashMap;
        }
        objectHashMap.put(newKey, "0");
        return objectHashMap;
    }

这个就是一个公共的方法,要有好多次数的校验 这里就不一一列举了,
然后是 具体的业务判断逻辑,

if (redisValue >= num) {
            return 超出参与次数;
        } else {
            //非原子  先读后写  数据异常
            //Integer integerDay = redisValue + 1;
            //redisTemplate.opsForValue().set(rediskey , integerDay.toString());

            //原子1 新做工具类  √
            RedisAutoCountUtils.optUpValue(redisTemplate, false, rediskey , 100, 1, 0, null);

            //原子2 redis工具类底层
            //redisTemplate.opsForValue().increment(rediskey , 1);
        }

当redis值 大于限制的值后 返回限制信息,也就是不让参与了

然后是对 redis 的数量处理
非原子的代码 这种再线程量大的情况下就容易造成先读后写或者后读先写的情况,
根据这个问题 找到了 redis 底层的一个方法 就是 使用 increment()这个来控制数据的原子性,但是这个只能给key进行加减操作。并不能像普通的那样设置超时时间,

根据这个就引出了工具类 RedisAutoCountUtils 用来自动计数,并且可以设置超时时间,


public class RedisAutoCountUtils {

    private static final Logger logger = LoggerFactory.getLogger(RedisAutoCountUtils.class);

    
    public static int optUpValue(RedisTemplate redisTemplate, boolean isAdd, String key,
                                 Integer initValue, Integer changeValue, long timeout, TimeUnit unit) {

        int count;
        if (Boolean.TRUE.equals(redisTemplate.hasKey(key))) {
            logger.info("有这个key--" + key + "--initValue 设置为null");
            count = optAtomic(redisTemplate, key, isAdd, null, changeValue, timeout, unit);
        } else {
            logger.info("key值不存在的话就获取初始值--" + key + "--initValue 设置为0,或者用传入的参数");
            count = optAtomic(redisTemplate, key, isAdd, initValue == null ? 0 : initValue, changeValue, timeout, unit);
        }
        return count;
    }

    
    public static long optUpValueLong(RedisTemplate redisTemplate, boolean isAdd, String key,
                                      Integer initValue, Integer changeValue, long timeout, TimeUnit unit) {

        long count;
        if (Boolean.TRUE.equals(redisTemplate.hasKey(key))) {
            logger.info("有这个key--" + key + "--initValue 设置为null");
            count = optAtomicLong(redisTemplate, key, isAdd, null, changeValue, timeout, unit);
        } else {
            logger.info("key值不存在的话就获取初始值--" + key + "--initValue 设置为0,或者用传入的参数");
            count = optAtomicLong(redisTemplate, key, isAdd, initValue == null ? 0 : initValue, changeValue, timeout, unit);
        }
        return count;
    }

    
    private static int optAtomic(RedisTemplate redisTemplate, String key, boolean isAdd,
                                 Integer initValue, Integer changeValue, long timeout, TimeUnit unit) {
        RedisAtomicInteger counter;
        if (initValue == null) {
            counter = new RedisAtomicInteger(key, Objects.requireNonNull(redisTemplate.getConnectionFactory()));
        } else {
            counter = new RedisAtomicInteger(key, Objects.requireNonNull(redisTemplate.getConnectionFactory()), initValue);
        }
        //设置超时时间
        if (null != unit) {
            counter.expire(timeout,unit);
        }
        logger.info("根据isAdd,判断是+ 还是- ; ----isAdd 为 " + isAdd);
        int i = isAdd ? counter.addAndGet(+changeValue) : counter.addAndGet(-changeValue);
        logger.info("操作结果," + i);
        return i;
    }

    
    private static int optAtomicLong(RedisTemplate redisTemplate, String key, boolean isAdd,
                                     Integer initValue, Integer changeValue, long timeout, TimeUnit unit) {
        RedisAtomicInteger counter;
        if (initValue == null) {
            counter = new RedisAtomicInteger(key, Objects.requireNonNull(redisTemplate.getConnectionFactory()));
        } else {
            counter = new RedisAtomicInteger(key, Objects.requireNonNull(redisTemplate.getConnectionFactory()), initValue);
        }
        //设置超时时间
        if (null != unit) {
            counter.expire(timeout,unit);
        }
        logger.info("根据isAdd,判断是+ 还是-");
        logger.info("根据isAdd,判断是+ 还是- ; ----isAdd 为 " + isAdd);
        int i = isAdd ? counter.addAndGet(+changeValue) : counter.addAndGet(-changeValue);
        logger.info("操作结果," + i);
        return i;
    }
}

这样根据工具类的 isadd 来判断是加还是减 就不需要再 increment()这里设置正负值了 只需要将步进的传工具类即可。
代码风格注释应该都挺简单的 一看就懂那种,不清楚的就自行百度吧。
码码不易,码字更不易啊。借用记得点赞 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/836835.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号