redis itheima

缓存问题

核心是如何避免大量请求到达数据库

缓存穿透

既不存在于 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流中的数据会不断更新,所以数据的索引也在变化,因此不能采用传统的分页模式。

相关推荐
ly76894 小时前
Redis 大 Key 与热点 Key 生产治理:发现、拆分、限流与本地缓存的组合策略
redis·限流·本地缓存·大key·热点key
ShineWinsu7 小时前
对于Redis:AOF持久化的解析
linux·数据库·redis·缓存·面试·持久化·aof
仍然.7 小时前
Redis---集群
数据库·redis·缓存
hweiyu009 小时前
Redis命令:HSCAN
redis·缓存
ly768911 小时前
Redis 分布式锁的边界条件:Redlock 争议、锁续期与客户端崩溃后的互斥失效
数据库·redis·分布式·分布式锁·watchdog·redlock
ShineWinsu13 小时前
对于Redis:RDB持久化的解析
linux·数据库·redis·缓存·面试·持久化·rdb
hweiyu001 天前
Redis命令:HTTL
redis·缓存
樱花落木兰1 天前
分布式登录实战:Session 会话共享改造,Redis 存储用户登录状态
java·javascript·数据库·redis·分布式·缓存
ShineWinsu1 天前
对于Redis:Steam、Geospatial、Hyperloglog、Bitmap、Bitfield类型的解析
数据库·c++·redis·分布式·缓存·面试·zset
海绵宝宝转agent1 天前
基于Redis ZSet+AOP+注解实现限流注解算法
数据库·redis·算法