缓存问题
核心是如何避免大量请求到达数据库
缓存穿透
既不存在于 redis,也不存在于 mysql 的key,被重复请求
java
public Result queryById(Long id) {
String key = CACHE_SHOP_KEY+id;
// 1. redis & mysql
String shopJson = stringRedisTemplate.opsForValue().get(key);
if(StrUtil.isNotBlank(shopJson)){ // 非空字符串, 如"abc"
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return Result.ok(shop);
}
// 空字符串 ""
if (shopJson != null){return Result.fail("not found shop");}
// 2. !redis & !mysql
Shop shop = shopMapper.selectById(id);
if(shop == null){
// 缓存 value=null 的对象
stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
return Result.fail("not fount shop");
}
// 3. !redis & mysql
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
return Result.ok(shop);
}
缓存雪崩
大量key失效(可能是刚好过期,也可能是 Redis 宕机了)
- 给不同的 key 添加 TTL 随机值
- 使用redis集群
- springcloud 分级缓存
缓存击穿
高并发的单key,某种原因过期了
Solution:互斥锁,逻辑过期

视频里用的是递归,我用了 do while,目前是没有问题的
java
public Result queryById(Long id) {
String key = CACHE_SHOP_KEY+id;
String lockKey = LOCK_SHOP_KEY+id;
try {
do {
// 查 redis
String shopJson = stringRedisTemplate.opsForValue().get(key);
if(StrUtil.isNotBlank(shopJson)){ // 非空字符串, 如"abc"
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return Result.ok(shop);
}
if (shopJson != null){return Result.fail("not found shop");}
boolean lock = tryLock(lockKey);
if(!lock){Thread.sleep(50);}
else{break;}
}while (true);
// 2. !redis & !mysql
Shop shop = shopMapper.selectById(id);
if(shop == null){
// 缓存 value=null 的对象
stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
return Result.fail("not fount shop");
}
// 3. !redis & mysql
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
return Result.ok(shop);
}catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
unlock(lockKey);
}
}
线程安全
超卖,一人一单存在线程问题
java
@Transactional
public Result seckillVoucher(long voucherId) {
// 查询优惠券
SeckillVoucher voucher = secKillVoucherService.getById(voucherId);
// 秒杀时间
if(voucher.getBeginTime().isAfter(LocalDateTime.now())){return Result.fail("秒杀尚未开始");}
if(voucher.getEndTime().isBefore(LocalDateTime.now())){return Result.fail("秒杀已经结束");}
// 库存是否足够
if(voucher.getStock()<1)return Result.fail("库存不足");
// 一人一单检查
Long userId = UserHolder.getUser().getId();
Long count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
if(count > 0){
return Result.fail("用户已经购买过了");
}
// 减库存, update table set stock=stock-1 where id=1 and stock>0
boolean flag = secKillVoucherService.update()
.setSql("stock=stock-1").eq("voucher_id", voucherId).gt("stock", 0).update();
if (flag == false) return Result.fail("库存不足");
// 创建订单
VoucherOrder voucherOrder = new VoucherOrder();
long orderId = redisIdWorker.nextId("order");
voucherOrder.setId(orderId); // order id
voucherOrder.setUser_id(userId); // user id
voucherOrder.setVoucher_id(voucherId);
save(voucherOrder);
return Result.ok(orderId);
}
单机环境下的悲观锁,实现一人一单
java
public Result seckillVoucher(long voucherId) {
//查询优惠券
SeckillVoucher voucher = secKillVoucherService.getById(voucherId);
// 秒杀时间
if(voucher.getBeginTime().isAfter(LocalDateTime.now())){return Result.fail("秒杀尚未开始");}
if(voucher.getEndTime().isBefore(LocalDateTime.now())){return Result.fail("秒杀已经结束");}
// 库存是否足够
if(voucher.getStock()<1)return Result.fail("库存不足");
Long userId = UserHolder.getUser().getId();
// 获取锁, 函数内部:提交事务(减库存,建订单), 释放锁
synchronized (userId.toString().intern()){ // 按值加锁: 锁相同用户的多次请求, 而不是锁方法
// 获取代理对象(事务)
VoucherOrderService proxy = (VoucherOrderService) AopContext.currentProxy();
return proxy.createSeckillOrder(voucherId);
}
}
@Transactional
public Result createSeckillOrder(long voucherId) {
Long userId = UserHolder.getUser().getId();
Long count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
if(count > 0){
return Result.fail("用户已经购买过了");
}
// 减库存, update table set stock=stock-1 where id=1 and stock>0
boolean flag = secKillVoucherService.update()
.setSql("stock=stock-1").eq("voucher_id", voucherId).gt("stock", 0).update();
if (flag == false) return Result.fail("库存不足");
// 创建订单
VoucherOrder voucherOrder = new VoucherOrder();
long orderId = redisIdWorker.nextId("order");
voucherOrder.setId(orderId); // order id
voucherOrder.setUser_id(userId); // user id
voucherOrder.setVoucher_id(voucherId);
save(voucherOrder);
return Result.ok(orderId);
}
分布式锁
通过 redis 给不同主机加锁,给锁加标识,避免被其他主机释放
问题1 锁误删情况如下:
改进后如下:
问题二:判断锁是自己创建的,到删除锁这段时间发生了阻塞,导致删除别人的锁,因为key都是一样的,判断是根据value

使用 redis lua 脚本,处理判断和删除的逻辑
feed 流关注推送
Feed流中的数据会不断更新,所以数据的索引也在变化,因此不能采用传统的分页模式。
