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

相关推荐
haogexiaole7 小时前
Redis优缺点
数据库·redis·缓存
在未来等你7 小时前
Redis面试精讲 Day 27:Redis 7.0/8.0新特性深度解析
数据库·redis·缓存·面试
川石课堂软件测试9 小时前
技术干货|使用Prometheus+Grafana监控Tomcat实例详解
redis·功能测试·单元测试·tomcat·测试用例·grafana·prometheus
两张不够花12 小时前
Shell脚本源码安装Redis、MySQL、Mongodb、PostgreSQL(无报错版)
linux·数据库·redis·mysql·mongodb·postgresql·云计算
Warren9814 小时前
Spring Boot 整合网易163邮箱发送邮件实现找回密码功能
数据库·vue.js·spring boot·redis·后端·python·spring
小花鱼202520 小时前
redis在Spring中应用相关
redis·spring
郭京京20 小时前
redis基本操作
redis·go
似水流年流不尽思念20 小时前
Redis 分布式锁和 Zookeeper 进行比对的优缺点?
redis·后端
郭京京20 小时前
go操作redis
redis·后端·go
Warren981 天前
Spring Boot 拦截器返回中文乱码的解决方案(附全局优化思路)
java·网络·spring boot·redis·后端·junit·lua