线程执行先后顺序和快慢是由操作系统和CPU自动决定的,为了解决这个问题可以使用Redis的事务
什么是事务
事务是指将一个业务逻辑作为一个整体一起执行,其实就是打包一组操作(或者命令)作为一个整体,在事务处理时将顺序执行这些操作,并返回结果,任何一个环节出错,所有的操作都会回滚.
Redis事务
提供一种将多个命令打包,一次性顺序的执行,Redis的事务可以保证只有在执行完事务中的所有命令后,才会继续处理此客户端的其他命令.也就是只有一个用户可以操作事务当中的数据.
开启事务命令
Redis事务四大指令:MULTI,EXEC,DISCARD,WATCH
- MULTI开启一个事务
- EXEC执行一个事务
- DISCARD取消一个事务
- WATCH用于客户端并发情况下,为事务提供一个锁,可以用watch命令来监控一个或多个变量如果在执行事务之前,某个监控项被修改,那么整个事务都会被终止
watch必须写在事务之前,不能写在后面
所以可以使用WATCH来处理抢购的并发情况
public Result<Boolean> snappedUp(@RequestParam("id") Long id) {
//初始化返回数据
Result result = new Result();
result.data(true);
result.setSuccess(true);
//实现购买代码
//先去redis查询一下
Object value = redisTemplate.opsForValue().get(id);
int stock = 0;
//如果redis没有则去数据库查询
if (value == null) {
//去数据库查询该商品的信息
ProductDO product = productDAO.selectById(id);
//将信息缓存到redis里边
stock = product.getStock();
redisTemplate.opsForValue().set(product.getId(), stock);
} else {
stock = (int)value;
}
redisTemplate.execute(new SessionCallback<List<Object>>() {
@Override
public List<Object> execute(RedisOperations operations) throws DataAccessException {
Integer stock = (Integer)operations.opsForValue().get(id);
//判断该商品的库存是否大于1
if (Integer.valueOf(stock) >= 1) {
//监听商品的名字,redis里边的key
operations.watch(id);
//开启事务
operations.multi();
//将该商品的库存自减1
operations.opsForValue().set(id, stock - 1);
//修改mysql数据库库存数量
ProductDO productDO = new ProductDO();
productDO.setId(id);
productDO.setStock(stock - 1);
productDAO.updateStock(productDO);
// 执行事务
List exec = operations.exec();
if (exec.size() > 0) {
// TODO:可以有其它业务逻辑,例如插入订单等,视具体需求而定
result.setMessage("抢购成功");
result.setData(true);
} else {
result.setMessage("抢购失败");
result.setData(false);
}
return exec;
} else {
result.setMessage("商品库存不足");
result.setData(false);
return null;
}
}
});
return result;
}
执行事务
redisTemplate.execute()
是执行器的方法,可以执行一系列的操作,使用Redis事务时:
redisTemplate.execute(new SessionCallback<List<Object>>() {
@Override
public List<Object> execute(RedisOperations operations) throws DataAccessException {
}
});
以上演示解决了并发的问题
Redis过期处理
很多时候服务器经常会用到Redis作为缓存,有很多时候是临时缓存,可能之后就不会在用到了,所以要定期处理
设置过期时间
对Redis数据库中的值可以设置一个过期时间,设置key有效时间Expires字典保存了所有键的过期时间
比如在1000毫秒后过期:
redisTemplate.opsForValue().set("code", "78987", 1000,TimeUnit.MILLISECONDS);
- 1000为过期数
- TimeUnit.MILLISECONDS)为时间单位
删除策略
-
惰性删除
每次查询或者写键时,都会查键是否过期,过期就删除否则返回该键,对CPU友好,对内存不友好 -
定期删除
每隔一段时间对数据库检查,删除里面的过期键. -
定时删除
在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除