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()这里设置正负值了 只需要将步进的传工具类即可。 代码风格注释应该都挺简单的 一看就懂那种,不清楚的就自行百度吧。 码码不易,码字更不易啊。借用记得点赞 👍
|