redis 的事务指的是提供一种将多个命令打包,一次性按顺序地执行
redis的事务可以保证只有在执行完事务中的所有命令后,才会继续处理此客户端的其他命令。
也就是说只有一个用户可以操作事务当中的数据
redis 中的事务开始到结束要经历三个阶段
- 开启事务
- 命令入列
- 执行事务/放弃事务
开启事务命令
redis 事务四大指令:MULTI、EXEC、DISCARD、WATCH
MULTI
开启一个事务EXEC
执行一个事务DISCARD
取消一个事务WATCH
用于客户端并发情况下,为事务提供一个锁可以用 watch 命令来监控一个或多个变量如果在执行事务之前,某个监控项被修改了,那么整个事务就会终止执行
watch
必须写在事务的前面,不能写在事务当中
这是一个处理抢购并发的流程图
@GetMapping("/shopping")
@ResponseBody
public Boolean snappedUp(@RequestParam Long id) {
//实现购买代码
//先去 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) redisTemplate.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) {
//可以有其他的业务逻辑,例如插入订单等,视具体的需求
LOG.info("抢购成功");
} else {
LOG.info("抢购失败");
}
return exec;
} else {
LOG.info("商品库存不足");
return null;
}
}
});
return true;
}
一般来说,数据库(MySQL)作为标准的持久化存储,Redis 作为性能跟高的缓存使用,所以有两大步骤:
1. Redis数据初始化
既然 Redis 是缓存,就要考虑什么时候把初始化的数据存入到数据库,可以在抢购前先查询库存再存入Redis中,也可以在项目启动时初始化,在标记为@PostConstruct
的方法中把输入存入Redis
2. 执行事务
redisTemplate.execute()
是执行器方法,可以执行一系列的操作
使用Redis事务时:
redisTemplate.execute(new SessionCallback<List<Object>>) {
@Override
public List<Object> execute(RedisOperations operations) throws DataAccessException {
}
}
基本上是固定写法了。
监听 -> 开启事务 -> 读写数据 -> 执行事务
Redis 过期处理
对存储在 Redis 数据库中的值可以设置一个过期时间
设置 key 的有效时间 Expires 字典保存了所有键的过期时间
格式
删除策略
1.惰性删除
每次查询或写键时,都会检查取得的键是否过期。如果过期就删除,否则就返回该键
2.定期删除
每个一段时间,就对数据库进行检查,删除里面的过期键
3.定时删除
在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除
注册用户的性能优化
将邮箱以及对应的用户数据缓存在 Redis 里,每次校验先从 Redis 里查询用户数据。减少对数据库的访问
当用户数据更新时,也需要实时更新 Redis 数据
例如判断邮箱是否已经被注册过的逻辑
//每次先去 Redis 校验
String pwd = (String) redisTemplate.opsForValue().get(userDO.getEmail());
//当redis没有数据时再去数据库查询
if (StringUtils.isEmpty(pwd)) {
//redis 缓存没有,为了安全起见
UserDO user = userDAO.selectByEamil(userDO.getEmail());
if (user != null) {
LOG.info("邮箱已经被注册过了");
return false;
}
}
redisTemplate.opsForValue().set(userDO.getEmail(), userDO.getPassword());