当前位置: 首页 > article >正文

黑马点评(更新中)

黑马点评

  • 1、短信登录 Session实现
    • 1.1 分析
    • 1.2、Session实现的缺点
    • 1.3、其中的问题
      • 1.3.1、session覆盖
      • 1.3.2、在拦截之后remove User的作用
  • 2、用Redis实现短信登录
    • 2.1 分析
    • 2.2 代码以及问题
      • 2.2.1 String问题
      • 2.2.2 刷新问题
      • 2.2.3 注入对象问题
      • 2.2.4 拦截器order问题
  • 3、缓存
    • 3.1 商户信息的redis保存
    • 3.2 redis 商户列表redis缓存
    • 3.3 缓存更新策略
        • 实践 商户信息一致性
    • 3.4 缓存穿透
      • 利用缓存空对象来解决缓存穿透问题
    • 3.5 缓存雪崩
    • 3.6 缓存击穿
      • 需求:修改根据id查询商铺的业务,基于互斥锁方式来解决缓存击穿问题
      • 需求:修改根据id查询商铺的业务,基于逻辑过期时间方式来解决缓存击穿问题
    • 3.7 工具类封装
  • 4、优惠券秒杀
    • 4.1 全局唯一ID
      • 4.1.1 id生成器的特性


1、短信登录 Session实现

1.1 分析

在这里插入图片描述
部分代码:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.2、Session实现的缺点

在这里插入图片描述
redis可以数据共享,且redis是内存存储,key-value结构。

1.3、其中的问题

1.3.1、session覆盖

看弹幕有人提到说如果多个用户登录,会不会出现session覆盖问题,都保存成属性值一样的情况下
回答 :不会,因为请求到服务器中,是基于session-id保存的,每个session-id的属性值相同不会有影响,是基于用户的。

1.3.2、在拦截之后remove User的作用

防止内存泄漏,如果当前线程仍然使用,但是却不在使用theardLocal,那么如果我们没有remove的话,即使key是弱引用,theadLocal 为null,但是key仍然有值,所以最好的办法就是手动remove。

2、用Redis实现短信登录

2.1 分析

在这里插入图片描述

使用redis着重要考虑:
①键要唯一
②如果存储键,方便我们后续的读取。

在这里,保存code时,首先数据类型使用的是String,在 redis中使用phone,刚好登录时,会提交phone和code,根据提交的code可以在redis中查找到code,然后验证。

保存用户信息时,使用的数据类型是Hash,Hash可以存储对象,当然也可以使用String 的json字符串进行保存。key这里使用的是一个随机字符串,那后续如何获取呢?我们可以手动把其放在token中,前端获取后端返回的数据保存token中,接着,每次发送ajax请求时,都会携带到请求头中,key为authorization。
在这里插入图片描述

在这里插入图片描述

2.2 代码以及问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.2.1 String问题

id是Long型,但是BeanToMap的值是String,Object,并且我们使用的是StringRedisTemplate,这要求我们保存到redis中的值为String类型,这时候就需要我们显示的将值转为String。
在这里插入图片描述
基本类型与对象类型: Java 中的基本类型(如 int、long、boolean)和它们的包装类型(如 Integer、Long、Boolean)与 String 之间的转换需要明确的语法,因为 Java 是强类型语言,编译器不会自动执行这些转换。

2.2.2 刷新问题

我们保证一个用户的活跃度,这时候应该是只要其访问了网页就要刷新其时常,所以不应该只在login时刷新,也不应该只在登录校验的几个网页做刷新。而应该在在所有的请求时都做刷新,这时候需要再有一个拦截器。对所有网页都刷新,但是这个拦截器不做登录校验功能,而是如果获取到了用户,把用户信息保存在Local中,不管咋样,都放行,交给登录校验的拦截器。

2.2.3 注入对象问题

使用 new 创建的对象不能通过 Spring 的 @Resource、@Autowired 或其他依赖注入注解进行注入。这是因为 Spring 的依赖注入机制依赖于容器管理的 Bean。通过 new 创建的对象不在 Spring 容器的管理范围内,因此无法享受到容器提供的特性(如生命周期管理、AOP、事务管理等)。但我们可以通过构造函数来注入。

2.2.4 拦截器order问题

哪个先注册在前面先执行哪一个,默认order都是0.可以通过order来决定先执行哪一个,值越小优先级越高。

3、缓存

在这里插入图片描述

3.1 商户信息的redis保存

使用了String
在这里插入图片描述

3.2 redis 商户列表redis缓存

使用了List
在这里插入图片描述

3.3 缓存更新策略

在这里插入图片描述
主动更新时 我们都选择自主更新就是由代码开发者 自己确定更新缓存
在这里插入图片描述
在这里插入图片描述
数据库数据变更时,我们选择删除缓存,而不是更新缓存,删除缓存的好处:数据库数据变更时,我们直接删除缓存,比如更新100次,只用删除一次就行了,查询时在更新缓存,但是如果更新缓存的话,就是导致,数据库数据变更100次那么缓存就要更新100次,如果中间没有读操作,就会导致无效的写操作。
在这里插入图片描述
我们一般都会选择先操作数据库在删除缓存。
第一种情况:先删除缓存,再操作数据库这种误差的机率比较大,因为更新数据库的操作慢,但是写缓存的速度快,在多线程的情况下,这种出现的概率比较大。
但是第二种情况:先操作数据,在删除缓存,由于更新数据库的速度比较慢,所以一般不会这样子进行,并且,即使写入缓存导致不一致了,也可以加入超时策略,作为兜底方案。
在这里插入图片描述

实践 商户信息一致性

查询操作时,添加过期时间
在这里插入图片描述
更新操作时
在这里插入图片描述

3.4 缓存穿透

在这里插入图片描述
查询商铺时的信息修改逻辑
当数据库不存在时,则把其存入到redis当中。存一个空字符串。
查询到时 如果是空值 则直接结束。
记得设置一个过期时间,因为在这中间可能会在数据库中插入信息。

在这里插入图片描述

利用缓存空对象来解决缓存穿透问题

在这里插入图片描述

3.5 缓存雪崩

在这里插入图片描述

3.6 缓存击穿

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

需求:修改根据id查询商铺的业务,基于互斥锁方式来解决缓存击穿问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

需求:修改根据id查询商铺的业务,基于逻辑过期时间方式来解决缓存击穿问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.7 工具类封装

如果每个都需要我们去写缓存击穿缓存穿透的话就很麻烦,我们可以封装一个工具类

在这里插入图片描述
工具类

package com.hmdp.utils;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.hmdp.entity.Shop;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import static com.hmdp.utils.RedisConstants.CACHE_SHOP_KEY;
import static com.hmdp.utils.RedisConstants.LOCK_SHOP_KEY;

/**
 * @ClassName ChcheClicnt
 * @Description TODO
 * @Author lukcy
 * @Date 2024/10/10 17:16
 * @Version 1.0
 */
@Slf4j
@Component
public class ChcheClicnt {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    public  void  set(String key, Object value, Long time, TimeUnit unit){
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value),time,unit);
    }
    private static final ExecutorService CACHAE_REBUILD_EXECUTOR= Executors.newFixedThreadPool(10);
    public void setWithLogicExpire(String key, Object value, Long time, TimeUnit unit){
        RedisData redisData=new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }

    public  <R,ID> R queryWithPassThough(String prifix, ID id, Class<R> type, Function<ID,R> DBcallback,Long time, TimeUnit unit){
        String key=prifix+id;
        //查询redis中是否有
        String Json = stringRedisTemplate.opsForValue().get(key);
        //有直接返回
        if (StrUtil.isNotBlank(Json)) {
            R r = JSONUtil.toBean(Json, type);
            return r;
        }
        //redis中保存的是空值
        if(Json!=null){
            return null;
        }
        //没有 查询数据库
        R r = DBcallback.apply(id);

        //数据库中没有 保存空值设置短的过期时间 返回错误
        if (r == null) {
            stringRedisTemplate.opsForValue().set(key,"",2L, TimeUnit.MINUTES);
            return null;
        }
        //有,保存Json形式在redis中 设置过期时间
        this.set(key,r,time,unit);
        //返回
        return r;
    }

    public <R,ID> R queryWithExpire(String prifix, ID id, Class<R> type, Function<ID,R> DBcallback,Long time, TimeUnit unit){
        String key=prifix+id;
        //查询redis中是否有
        String Json = stringRedisTemplate.opsForValue().get(key);
        //没有 直接返回
        if (StrUtil.isBlank(Json)) {
            return null;
        }
        //有 判断是否过期 把json反序列化为对象
        RedisData redisData = JSONUtil.toBean(Json, RedisData.class);
        LocalDateTime expireTime = redisData.getExpireTime();
        R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        //没有过期
        if(expireTime.isAfter(LocalDateTime.now())){
            return r;
        }
        //过期
        String lockkey=LOCK_SHOP_KEY+id;
        // 尝试获取锁
        if (!tryLock(lockkey)) {
            //获取锁失败
            return r;
        }
        //获取锁成功
        CACHAE_REBUILD_EXECUTOR.submit(()->{
            //开启线程 重建缓存
            //查数据库
            try {
            R r1 = DBcallback.apply(id);
            //存入缓存
            this.setWithLogicExpire(key,r1,time,unit);}
          catch (Exception e) {
                e.printStackTrace();
            } finally {
                deleteLock(lockkey);
            }
        });
        //返回
        return r;
    }
    private boolean tryLock(String key){
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(aBoolean);

    }
    private void deleteLock(String key){
        stringRedisTemplate.delete(key);


    }
}

运用

  public Result queryById(Long id) {
        //缓存穿透 利用缓存空值解决
//        Shop shop=chcheClicnt.queryWithPassThough(CACHE_SHOP_KEY,id,Shop.class,this::getById,CACHE_SHOP_TTL,TimeUnit.MINUTES);
        //缓存击穿 利用互斥锁解决
//        Shop shop = queryWithMeutx(id);
        Shop shop=chcheClicnt.queryWithExpire(CACHE_SHOP_KEY,id,Shop.class,this::getById,CACHE_SHOP_TTL,TimeUnit.MINUTES);
        if(shop==null){
            return Result.fail("该商铺不存在!");
        }
        return Result.ok(shop);
    }

4、优惠券秒杀

4.1 全局唯一ID

在这里插入图片描述
用数据库自增长的话,规律太明显,并且一个系统,生成订单的量会日益增多,一个数据库容量可能不够,如果分到多个表中,由于运用的是数据库的自增长,可能会导致id相同。

4.1.1 id生成器的特性

在这里插入图片描述
在这里插入图片描述
我i们不直接使用redis的自增长,需要拼接一些其他信息,比如时间戳,这需要我们有一个基础的开始时间,还有序列号。
时间戳的话即使是一秒下单的,后续的序列号也有32位,一秒下单量是足够的。
还有一个问题就是reids自增的key,如果我们用的是一个key,即使不同业务用的key不同, 但是相同业务用一个key的话,也会一直自增,redis自增也是有上限的,64位,可能会超。因此我们使用的key精确到天,这样也方便我们统计每天的下单量。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述


http://www.kler.cn/news/343115.html

相关文章:

  • 网站设计公司怎么评估?2024网站定制公司哪家好
  • 大模型在问答领域的探索和实践
  • zabbix7.0配置中文界面
  • EXCELWPS工作表批量重命名(按照sheet1中A列内容)
  • Python 使用函数归纳判断回文质数
  • React父子组件,父组件状态更新,子组件的渲染状况
  • 浙江省发规院产业发展研究所调研组莅临迪捷软件考察调研
  • GR-ConvNet论文 学习笔记
  • 有什么方法可以保护ppt文件不被随意修改呢?
  • 从容应对DDoS攻击:小网站的防守之战
  • 【大数据】大数据治理的全面解析
  • Python | Leetcode Python题解之第463题岛屿的周长
  • JSON 格式化工具:快速便捷地格式化和查看 JSON 数据
  • 简单理解Python代码的重构
  • 重新学习Mysql数据库3:Mysql存储引擎与数据存储原理
  • 音频响度归一化 - python 实现
  • 自动驾驶系统研发系列—如何选择适合自动驾驶的激光雷达?从基础到高端全解读
  • Linux YUM设置仓库优先级
  • 【RabbitMQ——消息应答机制——分布式事务解决方式】
  • Qt中的网络客户端