【黑马点评】项目知识点及面经整理

该文章已生成可运行项目,

1 短信登录(Session,Redis,JWT验证)

1.1 JWT和Session的区别

可以理解为session中,每个用户在首次登录后,服务器会为他分配一个id,同时id对应的数据会存放在对应的服务器中。之后用户每次登录,都会把这个id传给服务器,由服务器去找到id对应的session信息。
sessionID相当于一个钥匙,然后用户信息相当于仓库里的内容。用户第一次登录会为用户分配一个仓库来存储用户相关的信息,同时为了告知用户是哪个仓库,所以会返回给他sessionID,之后用户再次访问服务器的时候,就可以通过钥匙(sessionID)来直接访问他对应的仓库(获取用户信息),但是这样由于用户信息是需要放在服务器内存中的,这样存在多服务器共享问题,以及服务器重启session数据丢失问题。

jwt的话则是,用户登录时,服务器不是分配id,而是分配一个包含用户信息和有效期的jwt。之后客户端每次请求都会带上自己的信息,就不需要服务器再去查询,服务器只需要验证这个jwt是否有效即可。
JWT 则将用户信息本身经过加密等处理后作为一个自包含的 “仓库”,客户端携带它进行请求,服务器只需利用密钥和加密算法进行验证即可获取用户信息,无需在服务器端额外存储状态信息,更适合分布式系统和微服务架构。

1.2 Session

Session 机制依赖于服务器端的存储。当用户首次登录时,服务器会创建一个session,并生成一个唯一的sessionID,然后将这个 ID 返回给客户端(通常是通过Cookie)。客户端在后续的请求中会携带这个sessionID,服务器根据sessionID来识别用户并获取其会话信息;
具体而言。用户在请求时候,会从cookie中携带着sessionId到后台,后台通过sessionId从session中拿到用户信息,如果没有session信息,则进行拦截,如果有session信息,则将用户信息保存到threadLocal中,并且放行
在这里插入图片描述

  1. 发送验证码
    ● 用户在提交手机号后,会校验手机号是否合法,如果不合法,则要求用户重新输入手机号
    ● 如果手机号合法,后台此时生成对应的验证码,同时将验证码进行保存,然后再通过短信的方式将验证码发送给用户
  2. 短信验证码登录、注册
    ● 用户将验证码和手机号进行输入,后台从session中拿到当前验证码,然后和用户输入的验证码进行校验,如果不一致,则无法通过校验,如果一致,则后台根据手机号查询用户,如果用户不存在,则为用户创建账号信息,保存到数据库,无论是否存在,都会将用户信息保存到session中,方便后续获得当前登录信息
  3. 校验登录状态
    ● 用户在请求时候,会从cookie中携带者sessionId到后台,后台通过sessionId从session中拿到用户信息,如果没有session信息,则进行拦截,如果有session信息,则将用户信息保存到threadLocal中,并且放行
  4. 优点:
    ○ 相对简单易用,许多服务器端框架都对 session 提供了良好的支持。
    ○ 可以方便地在服务器端存储和管理用户的状态信息。
  5. 缺点:
    ○ 多台服务器部署时,需要进行 session 共享的配置,增加了系统的复杂性。
    ○ 存储在服务器内存中,会占用服务器资源,当并发用户数量较大时,可能会导致服务器内存压力增大。
    ○ 如果服务器重启,所有的 session 数据可能会丢失,用户需要重新登录。

1.3 Redis

1.3.1 基于Redis实现登录验证

(1) key-value设计
key要具有唯一性+方便携带。手机号是敏感信息,不适合存储到redis中从页面带过来。因此key的话,这里在后端生成一个随机串作为token,让前端带来token后端校验。
(2) 整体访问流程
注册完成后,用户点击登录,后端检验用户提交的手机号和验证码是否一致。
一致:根据手机号查询用户信息,不存在则新建,将用户保存到redis中,生成token作为redis的key
校验登录状态时:请求会携带着token进行访问,后端从redis中取出token对应的value,判断是否存在,不存在则拦截,存在则保存到threadlocal中放行。

1.3.2 登录拦截

(1) 第一种拦截方案
拦截器会先获取token,根据token查询Redis的用户是否存在,不存在则拦截,存在的话会将用户信息保存到ThreadLocal中,之后刷新token有效期,放行。

这样会存在问题,假如用户在登录后长时间不访问需要拦截的路径,此时用户token将得不到刷新,导致token过期。一旦用户尝试访问需要拦截的路径,就会发现自己需要重新登陆,因为token已经失效。
(2) 登录拦截器的优化
之前的拦截器只会拦截需要登录的路径,所以用户不访问需要登录的路径时,就不会拦截。
在这里,第一个拦截器负责通用的处理逻辑。处理所有路径,确保用户的token能得到刷新。第二个拦截器则拦截未登录的用户,防止未授权的用户访问敏感资源。

因此添加一个拦截器,拦截所有路径,
拦截器1:拦截所有路径,同时获取token,根据token查询Redis中的用户,保存到ThreadLocal中后刷新token有效期,放行。
拦截器2:拦截需要登录的路径:查询ThreadLocal中的用户,不存在,则拦截,存在,则继续。

1.4 JWT

1.4.1 JWT的有效验证

(1) 验证签名
jwt通常由三部分组成:头部,payload和签名。
头部包含了token的类型和加密算法,payload包含了用户的身份信息和其他声明,如过期时间等。
签名:使用头部中指定的加密算法和payload进行计算得到。服务器验证jwt时,会使用相同的加密算法和密钥,对头部和载荷重新计算,并与JWT中的签名比较。一致说明签名有效,没有被篡改。
(2) 检查过期时间
JWT 的载荷中通常包含一个过期时间(exp)声明。服务器在验证 JWT 时,会检查当前时间是否超过了 JWT 中的过期时间。如果超过了,则说明 JWT 已经过期,不再有效。

1.4.2 JWT定义

JWT 是一种无状态的认证机制,它通过在客户端存储令牌(Token)来实现认证。当用户登录时,服务器会生成一个包含用户信息和有效期的 JWT,并将其返回给客户端。客户端在后续的请求中会携带这个 JWT,服务器通过验证 JWT 的有效性来识别用户。

  1. 工作原理:
    ● 用户登录时,服务器验证用户信息通过后,生成一个 JWT。假设这个 JWT 包含用户 ID、用户名和过期时间等信息。例如:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c。
    ● 服务器将这个 JWT 返回给客户端,客户端可以将其存储在浏览器的 localStorage 中。
    ● 当用户浏览商品页面时,每次请求都会在请求头部中携带这个 JWT。服务器接收到请求后,对 JWT 进行验证。如果验证通过,就可以确定用户的身份和状态。
    ● 由于 JWT 是无状态的,每个服务器都可以独立验证 JWT,无需进行 Session 共享的配置。即使服务器重启,只要 JWT 在有效期内,用户仍然可以正常访问资源。而且可以在 JWT 中包含一些特定的用户信息,比如用户的购物车状态等,更加灵活。
  2. 优点:
    ● 无状态:服务器不需要存储用户的状态信息,所有的信息都包含在 JWT 中,因此可以很容易地进行水平扩展。
    ● 跨域支持:由于 JWT 是作为请求的一部分发送的,因此可以在不同的域之间进行身份验证。
    ● 安全性高:JWT 可以使用数字签名来保证其完整性和真实性,防止被篡改。
  3. 缺点:
    ● 一旦 JWT 被签发,在有效期内无法撤销,除非等到其过期。
    ● 如果 JWT 被泄露,攻击者可以使用它来冒充用户身份,因此需要采取一些措施来保护 JWT 的安全,如加密存储在客户端等。

2 热点数据缓存

2.1 缓存介绍

缓存是一种临时存储数据的技术,用于加快数据的访问速度,减少对底层数据库的频繁访问,从而提高系统的性能和响应时间
(1)为什么要使用缓存。一句话:因为速度快,好用
缓存数据存储于代码中,而代码运行在内存中,内存的读写性能远高于磁盘,缓存可以大大降低用户访问并发量带来的服务器读写压力
实际开发过程中,企业的数据量,少则几十万,多则几千万,这么大数据量,如果没有缓存来作为"避震器",系统是几乎撑不住的,所以企业会大量运用到缓存技术;
但是缓存也会增加代码复杂度和运营的成本:
(2)如何使用缓存
实际开发中,会构筑多级缓存来使系统运行速度进一步提升,例如:本地缓存与redis中的缓存并发使用
浏览器缓存:主要是存在于浏览器端的缓存
应用层缓存:可以分为tomcat本地缓存,比如之前提到的map,或者是使用redis作为缓存
数据库缓存:在数据库中有一片空间是 buffer pool,增改查数据都会先加载到mysql的缓存中
CPU缓存:当代计算机最大的问题是 cpu性能提升了,但内存读写速度没有跟上,所以为了适应当下的情况,增加了cpu的L1,L2,L3级的缓存

2.2 缓存更新策略

缓存更新是redis为了节约内存而设计出来的一个东西,主要是因为内存数据宝贵,当我们向redis插入太多数据,此时就可能会导致缓存中的数据过多,所以redis会对部分数据进行更新,或者把他叫为淘汰更合适。
内存淘汰:redis自动进行,当redis内存达到咱们设定的max-memery的时候,会自动触发淘汰机制,淘汰掉一些不重要的数据(可以自己设置策略方式)
超时剔除:当我们给redis设置了过期时间ttl之后,redis会将超时的数据进行删除,方便咱们继续使用缓存
主动更新:我们可以手动调用方法把缓存删掉,通常用于解决缓存和数据库不一致问题
在这里插入图片描述

2.3 数据库缓存不一致解决方案

由于我们的缓存的数据源来自于数据库,而数据库的数据是会发生变化的,因此,如果当数据库中数据发生变化,而缓存却没有同步,此时就会有一致性问题存在,其后果是:
用户使用缓存中的过时数据,就会产生类似多线程数据安全问题,从而影响业务,产品口碑等;怎么解决呢?有如下几种方案
Cache Aside Pattern 人工编码方式:缓存调用者在更新完数据库后再去更新缓存,也称之为双写方案 (我们使用CacheAside方式)
Read/Write Through Pattern : 由系统本身完成,数据库与缓存的问题交由系统本身去处理
Write Behind Caching Pattern :调用者只操作缓存,其他线程去异步处理数据库,实现最终一致

操作缓存和数据库时有三个问题需要考虑:
如果采用第一个方案,那么假设我们每次操作数据库后,都操作缓存,但是中间如果没有人查询,那么这个更新动作实际上只有最后一次生效,中间的更新动作意义并不大,我们可以把缓存删除,等待再次查询时,将缓存中的数据加载出来

  • 删除缓存还是更新缓存?

    • 更新缓存:每次更新数据库都更新缓存,无效写操作较多
    • 删除缓存:更新数据库时让缓存失效,查询时再更新缓存
  • 如何保证缓存与数据库的操作的同时成功或失败?

    • 单体系统,将缓存与数据库操作放在一个事务
    • 分布式系统,利用TCC等分布式事务方案

应该具体操作缓存还是操作数据库,我们应当是先操作数据库,再删除缓存,原因在于,
第一种方案,在两个线程并发来访问时,假设线程1先来,他先把缓存删了,此时线程2过来,他查询缓存数据并不存在,此时他写入缓存,当他写入缓存后,线程1再执行更新动作时,实际上线程2写入的就是旧的数据,线程1的新的数据被旧数据覆盖了。会存在数据不一致的情况。

  • 先操作缓存还是先操作数据库?
    • 先删除缓存,再操作数据库
    • 先操作数据库,再删除缓存(使用这种方法)

2.4 缓存穿透,击穿,雪崩

2.4.1 缓存穿透 (商铺id查询 ->空对象)

缓存穿透:客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。
常见的解决方案有两种:

  • 缓存空对象
    • 优点:实现简单,维护方便
    • 缺点:额外的内存消耗,可能造成短期的不一致
  • 布隆过滤
    • 优点:内存占用较少,没有多余key
    • 缺点:
      • 实现复杂,存在误判可能
  • 增强id的复杂度,避免被猜测id规律
  • 做好数据的基础格式校验
  • 加强用户权限校验
  • 做好热点参数的限流
    缓存空对象思路分析:当我们客户端访问不存在的数据时,先请求redis,但是此时redis中没有数据,此时会访问到数据库,但是数据库中也没有数据,这个数据穿透了缓存,直击数据库,我们都知道数据库能够承载的并发不如redis这么高,如果大量的请求同时过来访问这种不存在的数据,这些请求就都会访问到数据库,简单的解决方案就是哪怕这个数据在数据库中也不存在,我们也把这个数据存入到redis中去,这样,下次用户过来访问这个不存在的数据,那么在redis中也能找到这个数据就不会进入到缓存了
    在这里插入图片描述

在原来的逻辑中,我们如果发现这个数据在mysql中不存在,直接就返回404了,这样是会存在缓存穿透问题的
现在的逻辑中:如果这个数据不存在,我们不会返回404 ,还是会把这个数据写入到Redis中,并且将value设置为空,欧当再次发起查询时,我们如果发现命中之后,判断这个value是否是null,如果是null,则是之前写入的数据,证明是缓存穿透数据,如果不是,则直接返回数据。

布隆过滤:布隆过滤器其实采用的是哈希思想来解决这个问题,通过一个庞大的二进制数组,走哈希思想去判断当前这个要查询的这个数据是否存在,如果布隆过滤器判断存在,则放行,这个请求会去访问redis,哪怕此时redis中的数据过期了,但是数据库中一定存在这个数据,在数据库中查询出来这个数据后,再将其放入到redis中,

假设布隆过滤器判断这个数据不存在,则直接返回
这种方式优点在于节约内存空间,存在误判,误判原因在于:布隆过滤器走的是哈希思想,只要哈希思想,就可能存在哈希冲突

2.4.2 缓存雪崩(随机TTL+二级缓存)

缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
解决方案:
● 给不同的Key的TTL添加随机值 (同一时段,所以给不同key设置不同的TTL)
● 利用Redis集群提高服务的可用性
● 给缓存业务添加降级限流策略 (微服务)
● 给业务添加多级缓存

(1) Redis+Caffeine实现应用层二级缓存

Redis 作为分布式缓存:

  • Redis 具有高性能、丰富的数据结构和可扩展性,适合作为分布式缓存存储大量的数据。它可以在多服务器环境下共享缓存数据,提高系统的整体性能。
  • 可以根据数据的特点选择合适的数据结构来存储数据,如使用哈希表存储对象、使用有序集合进行排行榜等操作。
  • 配置 Redis 的持久化机制,以防止数据丢失。同时,考虑使用 Redis 的集群或主从复制来提高可用性和可扩展性。
    Caffeine 作为本地缓存:
  • Caffeine 是一个高效的本地缓存库,可以在应用程序内部实现缓存,减少对外部缓存服务的依赖,提高缓存的访问速度。
  • Caffeine 支持自动过期功能,可以根据设定的时间自动清除过期的缓存数据,减少内存占用。
  • 可以根据数据的访问频率和大小来调整 Caffeine 的缓存配置,如缓存的大小、过期时间等。

实现二级缓存架构

  1. 数据存储流程:
    ○ 当应用程序需要访问数据时,首先从 Caffeine 本地缓存中查找数据。如果数据在 Caffeine 中存在,则直接返回数据,无需进一步访问
本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值