JAVA操作REDIS执行原子操作

为什么要使用原子操作

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

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

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

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

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

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

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

1 拿缓存数据 没有初始化

 /**
     * .
     *
     * @param onlyOne 入参 唯一标识key
     * @param type    入参 key类型
     * @return java.util.Map
     * @Description: 作用:  拿缓存数据,没有初始化 0次
     * @Author: LXT
     * @Date: 2022/4/1 14:43
     */
    public Map<String, String> getRedis(String onlyOne, String type) {
        String newKey = type + onlyOne;
        Boolean hasKey = redisTemplate.hasKey(newKey);
        Map<String, String> 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 用来自动计数,并且可以设置超时时间,


/**
 * .
 *
 * @ClassName: RedisAutoCountUtils 
 * @Description: redis加减原子操作工具类
 * @Author: LXT
 * @Date: 2022/4/21 9:12
 */
public class RedisAutoCountUtils {

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

    /**
     * .
     *
     * @param redisTemplate 入参 redis模板
     * @param isAdd         入参 是否增加
     * @param key           入参 key值
     * @param initValue     入参 初始值
     * @param changeValue   入参 更改值
     * @param timeout       入参 超时时间
     * @param unit          入参 时间类型
     * @return int
     * @Description: 作用: 更改int值
     * @Author: LXT
     * @Date: 2022/4/21 10:07
     */
    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;
    }

    /**
     * .
     *
     * @param redisTemplate 入参 redis模板
     * @param isAdd         入参 是否增加
     * @param key           入参 key值
     * @param initValue     入参 初始值
     * @param changeValue   入参 更改值
     * @param timeout       入参 超时时间
     * @param unit          入参 时间类型
     * @return int
     * @Description: 作用: 更改long值
     * @Author: LXT
     * @Date: 2022/4/21 10:07
     */
    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;
    }

    /**
     * .
     *
     * @param redisTemplate 入参  redis模板
     * @param key           入参  缓存键
     * @param isAdd         入参  是否修改
     * @param initValue     入参  初始参数
     * @param changeValue   入参  改变的参数
     * @param timeout       入参  超时时间
     * @param unit          入参  超时时间类型
     * @return int
     * @Description: 作用:  原子加减 内部调用
     * @Author: LXT
     * @Date: 2022/4/21 9:30
     */
    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;
    }

    /**
     * .
     *
     * @param redisTemplate 入参  redis模板
     * @param key           入参  缓存键
     * @param isAdd         入参  是否修改
     * @param initValue     入参  初始参数
     * @param changeValue   入参  改变的参数
     * @param timeout       入参  超时时间
     * @param unit          入参  超时时间类型
     * @return int
     * @Description: 作用:  原子加减 内部调用
     * @Author: LXT
     * @Date: 2022/4/21 9:30
     */
    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()这里设置正负值了 只需要将步进的传工具类即可。
代码风格注释应该都挺简单的 一看就懂那种,不清楚的就自行百度吧。
码码不易,码字更不易啊。借用记得点赞 👍

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐