开搜 K
主题
主题
收集了栢码群里面用栢码项目相关的面经。
jwt 的优点:
jwt 的缺点:
安全性。由于 jwt 的 payload 是使用 base64 编码的,并没有加密,因此 jwt 中不能存储敏感数据。而 session 的信息是存在服务端的,相对来说更安全。
性能。由于是无状态使用 JWT,所有的数据都被放到 JWT 里,如果还要进行一些数据交换,那载荷会更大,经过编码之后导致 jwt 非常长,cookie 的限制大小一般是 4k,cookie 很可能放不下,所以 jwt 一般放在 local storage 里面。并且用户在系统中的每一次 http 请求都会把 jwt 携带在 Header 里面,http 请求的 Header 可能比 Body 还要大。而 sessionId 只是很短的一个字符串,因此使用 jwt 的 http 请求比使用 session 的开销大得多。
一次性。无状态是 jwt 的特点,但也导致了这个问题,jwt 是一次性的。想修改里面的内容,就必须签发一个新的 jwt。
无法废弃。一旦签发一个 jwt,在到期之前就会始终有效,无法中途废弃。例如你在 payload 中存储了一些信息,当信息需要更新时,则重新签发一个 jwt,但是由于旧的 jwt 还没过期,拿着这个旧的 jwt 依旧可以登录,那登录后服务端从 jwt 中拿到的信息就是过时的。为了解决这个问题,我们就需要在服务端部署额外的逻辑,例如设置一个黑名单,一旦签发了新的 jwt,那么旧的就加入黑名单(比如存到 redis 里面),避免被再次使用。
续签。传统的 cookie 续签方案一般都是框架自带的,session 有效期 30 分钟,30 分钟内如果有访问,有效期被刷新至 30 分钟。一样的道理,要改变 jwt 的有效时间,就要签发新的 jwt。最简单的一种方式是每次请求刷新 jwt,即每个 http 请求都返回一个新的 jwt。这个方法不仅暴力不优雅,而且每次请求都要做 jwt 的加密解密,会带来性能问题。另一种方法是在 redis 中单独为每个 jwt 设置过期时间,每次访问时刷新 jwt 的过期时间。
spring boot 3 和 spring boot 2 的部分区别:
最低环境。SpringBoot2 的最低版本要求为 Java8,支持 Java9;而 SpringBoot3 决定使用 Java17 作为最低版本,并支持 Java19。Spring Boot2 基于 Spring Framework5 开发;而 SpringBoot3 构建基于 Spring Framework6 之上,需要使用 Spring Framework6。
GraalVM 支持。相比 SpringBoot2,SpringBoot3 的 Spring Native 也是升级的一个重大特性,支持使用 GraalVM 将 Spring 的应用程序编译成本地可执行的镜像文件,可以显著提升启动速度、峰值性能以及减少内存使用。
GraalVm 配置参考:https://www.itbaima.cn/space/project/deploy/8
图片 Banner。在 SpringBoot2 中,自定义 Banner 支持图片类型;而现在 Spring Boot3 自定义 Banner 只支持文本类型(banner.txt),不再支持图片类型。
依赖项。删除了对一些附加依赖项的支持,包括 Apache ActiveMQ、Atomikos、EhCache2 和 HazelCast3。
Java EE 已经变更为 Jakarta EE(javax.servlet.Filter 要改为 jakarta.servlet.Filter)。
swagger 问题:
新版 security 配置全面采用了 lambda 表达式来配置,例子:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable);
http.formLogin(formLogin -> formLogin
.loginProcessingUrl("/auth/login")
.successHandler((request, response, authentication) -> {})
.failureHandler((request, response, authentication) -> {}));
http.logout(logout -> logout
.logoutUrl("/auth/logout")
.logoutSuccessHandler((request, response, authentication) -> {}));
http.authorizeHttpRequests(request -> request
.requestMatchers("/auth/**")
.permitAll()
.anyRequest()
.authenticated()
);
return http.build();
}
任何 MQ 相关的问题,优先想到“异步”、“解耦”。
(表达意思:我会玩反射)
BeanUtils.copyProperties 底层也是反射,但 spring 做了大量逻辑优化和缓存优化,性能不会很低。
限流工具类的功能:限流(难道还有其他功能?)
限流工具类 的 redis 的键值分别是什么:key 是 ip 地址,value 是访问次数
ip 黑名单存储结构:字符串(简单高效!)
几十万个 ip 请求:这个量 redis 完全存的下,如果占用太高可以考虑转为无符号整型
Minio 的主要特点:
简单易用:Minio 的安装和配置非常简单,只需要下载并运行相应的二进制文件即可。它提供- 了一个 Web UI,可以通过界面管理存储桶和对象。
可扩展性:Minio 可以轻松地扩展到多个节点,以提供高可用性和容错能力。它支持多种部署- 模式,包括单节点、主从复制和集群等。
高可用性:Minio 提供了多种机制来保证数据的可靠性和可用性,包括冗余备份、数据复制和- 故障转移等。
安全性:Minio 提供了多种安全机制来保护数据的机密性和完整性,包括 SSL/TLS 加密、- 访问控制和数据加密等。
多语言支持:Minio 支持多种编程语言,包括 Java、Python、Ruby 和 Go 等。
社区支持:Minio 是一个开源项目,拥有庞大的社区支持和贡献者。它的源代码可以在 - GitHub 上获得,并且有一个活跃的邮件列表和论坛。
对象存储:Minio 的核心功能是对象存储。它允许用户上传和下载任意数量和大小的对象,并- 提供了多种 API 和 SDK 来访问这些对象。
块存储:Minio 还支持块存储,允许用户上传和下载大型文件(例如图像或视频)。块存储是一- 种快速、高效的方式来处理大型文件。
文件存储:Minio 还支持文件存储,允许用户上传和下载单个文件。文件存储是一种简单、快速的方式来处理小型文件。
使用 minio 官方提供的 Java SDK 进行操作。
Minio 官方文档:https://min.io/docs/minio/linux/developers/java/minio-java.html
雪花算法是一种分布式 ID 生成方案,它可以生成一个长度为 64 位的唯一 ID,其中包含了时间戳、数据中心 ID 和机器 ID 等信息。
雪花算法的核心思想是利用时间戳和机器 ID 生成一个唯一的序列号,从而保证生成的 ID 的唯一性。
雪花算法的优点包括唯一性、时间戳有序和高性能,缺点包括依赖时钟和数据中心 ID 和机器 ID 需要手动分配。
全球唯一 ID 目前有两种方案:
基于时间的:能基本保证全球唯一性,但是使用了 Mac 地址,会暴露 Mac 地址和生成时间。
分布式的:能保证全球唯一性,但是常用库基本没有实现。
具体可参考 https://www.developers.pub/article/606
每条评论加一个类似parentId
和replyId
字段。
parentId
和replyId
都为空parentId
和replyId
都为一级评论的id
parentId
为一级评论的id
,replyId
为回复的评论的id
两种基于 Redis 的方案:
用户登录时,使用setbit
命令记录用户已登录。
例子:
setbit login:<日期> <用户id> 1
然后使用bitcount
统计今日的数量。
例子:
bitcount login:<日期>
注意点: 统计的时间复杂度为 O(N) ,当进行大数据量的统计时,最好将任务指派到附属节点(slave)进行,避免阻塞主节点。
优点: 精准统计,基本上是秒出结果,能方便地获取统计对象的状态。
缺点: 数量十分巨大时,空间占用会比较,可以通过分片,或者压缩等手段去解决。
用户登录时,使用pfadd
命令记录用户已登录。
例子:
pffadd login_<日期> <用户id>
然后使用pfcount
统计今日的数量。
例子:
pfcount login_<日期>
注意点: key 不能用:分隔,可使用_代替
优点: 可以统计海量数量,并且占用内存很小。
缺点: 牺牲了准确率,而且无法得到每个统计对象的状态。
Bitmap 存储一亿数据需要 12M,而 HyperLogLog 只需要 14K。
每个线程都有自己的一个虚拟机栈,虚拟机栈保存着方法的局部变量、部分结果,并参与方法的调用和返回,生命周期和所属的线程一致。每个虚拟机栈中都有一个个的栈帧(Stack Frame),每个栈帧对应一次方法调用。
栢码视频:https://www.bilibili.com/video/BV1Er4y1r7as?p=8
如果线程请求分配的栈容量超过允许的最大容量,将会抛出StackOverflowError
异常。
如果 Java 虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,将会抛出一个OutOfMemoryError
异常。
以下内容施工中
写在消息队列里面异步更新redis
开启MySQL慢查询日志、explain关键字