苏三的免费八股文网站:
大家好,我是苏三。
最近是春招投递的高峰期时间,很多同学遇到一个问题,大厂部门那么多,如何选择部门来投递呢?
我的想法是这样,如果在还没拿到某个大厂 offer 之前,先不着急选部门,哪个部门约你面试,你就面哪个再说,先拿第一个大厂 offer 先拿下来会更好,然后后面再考虑找其他大厂业务比较核心的部门,不然一开始就挑来挑去,最终可能啥都没有。
当然,还有一个方式也可以作为参考,就是看大厂各个事业群年终奖的情况,年终奖发的越多,大概率这个业务是比较核心的。
这次,我们来看看快手今年年终奖的情况,我根据脉脉网的爆料,整理了与后端岗位有关的年终奖信息,年终奖也与个人的绩效挂钩,所以仅供参考:
- 部门主站,年终奖 28w ,后端开发,职级 e7(老职级对应 k3a)
- 部门主站,年终奖 11.4w,后端开发,职级 e7(老职级对应 k3a)
- 部门主站,年终奖 15w,后端开发,职级 e8(老职级对应 k3b)
- 部门风控,年终奖 15w,后端开发,职级 e8(老职级对应 k3b)
- 部门基础平台,年终 12w,后端开发,职级 e9(老职级对应 k3c)
- 部门社科,年终奖 13w,后端开发,职级 e9(老职级对应 k3c)
- 部门社科,年终奖 25w,后端开发,职级 e10(老职级对应 k4a)
这里也贴一个快手职级体系映射薪酬级别的表格,这个是老的职级:
现在快手是 e 系列为职级了,新老职级的对比如下:
既然聊到快手,那么快手的面经也是逃不了的
来看看最近 快手的Java后端开发的面经 ,属于一面,重点考虑了Redis、消息队列、MySQL、网络、SSM、项目场景、算法这些内容。
快手一面
Redis有哪些客户端,有什么区别?
在 Java 编程中,我了解到的有 Jedis 和 Redisson 这两种客户端
- Jedis
:一款老牌的 Java Redis 客户端,提供了全面且简洁的 API,能直接对应 Redis 的各种命令,使用方式简单直观,适合对 Redis 命令操作有直接需求的场景,例如,执行
SET
命令可以直接调用jedis.set(key, value)
方法,但它没有提供分布式锁的实现,在分布式场景下功能有所欠缺 - Redisson :基于 Redis 实现的分布式和可扩展的 Java 数据结构集合,它不仅提供了与 Redis 交互的基本功能,还封装了丰富的分布式对象和服务,如分布式锁、分布式集合等,让开发者可以方便地在分布式系统中使用这些功能,而不需要自己去实现复杂的分布式逻辑。
我的项目中,因为有用到 redis 分布式锁,所以选择了 Redisson。
为什么选择RabbitMQ,kafka了解过吗,使用场景?
Kafka、ActiveMQ、RabbitMQ、RocketMQ来进行不同维度对比。
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
单机吞吐量 | ||||
万级 | ||||
万级 | ||||
10 万级 | ||||
10 万级 | ||||
时效性 | ||||
毫秒级 | ||||
微秒级 | ||||
毫秒级 | ||||
毫秒级 | ||||
可用性 | ||||
高(主从) | ||||
高(主从) | ||||
非常高(分布式) | ||||
非常高(分布式) | ||||
消息重复 | ||||
至少一次 | ||||
至少一次 | ||||
至少一次 最多一次 | ||||
至少一次最多一次 | ||||
消息顺序性 | ||||
有序 | ||||
有序 | ||||
有序 | ||||
分区有序 | ||||
支持主题数 | ||||
千级 | ||||
百万级 | ||||
千级 | ||||
百级,多了性能严重下滑 | ||||
消息回溯 | ||||
不支持 | ||||
不支持 | ||||
支持(按时间回溯) | ||||
支持(按offset回溯) | ||||
管理界面 | ||||
普通 | ||||
普通 | ||||
完善 | ||||
普通 | ||||
选型的时候,我们需要根据业务场景,结合上述特性来进行选型。
比如你要支持天猫双十一类超大型的秒杀活动,这种一锤子买卖,那管理界面、消息回溯啥的不重要。
我们需要看什么?看吞吐量!所以优先选Kafka和RocketMQ这种更高吞吐的。
比如做一个公司的中台,对外提供能力,那可能会有很多主题接入,这时候主题个数又是很重要的考量,像Kafka这样百级的,就不太符合要求,可以根据情况考虑千级的RocketMQ,甚至百万级的RabbitMQ。
又比如是一个金融类业务,那么重点考虑的就是稳定性、安全性,分布式部署的Kafka和Rocket就更有优势。
特别说一下时效性,RabbitMQ以微秒的时效作为招牌,但实际上毫秒和微秒,在绝大多数情况下,都没有感知的区别,加上网络带来的波动,这一点在生产过程中,反而不会作为重要的考量。
其它的特性,如消息确认、消息回溯,也经常作为考量的场景,管理界面的话试公司而定了,反正我呆过的地方,都不看重这个,毕竟都有自己的运维体系。
如何避免消费端重复消费?
避免消费端重复消费的方法有:
- 给每个消息添加唯一的标识,例如一个全局唯一的 ID。消费端在处理消息时,先检查该消息的 ID 是否已经被处理过。可以使用数据库表、缓存等存储已经处理过的消息 ID,当收到新消息时,查询存储中是否存在该 ID,若存在则直接丢弃,若不存在则处理消息并将 ID 存入存储。
- 让消费端的业务处理具有幂等性,即多次执行相同的操作结果与执行一次相同。例如,对于数据库插入操作,可以使用唯一索引来避免重复插入相同的数据;对于更新操作,确保更新的条件是基于唯一标识而不是其他易变的条件,这样即使多次执行更新,也不会出现数据不一致的情况。
什么情况下会出现重复收到消息?
当消费端与消息队列之间的网络出现故障,如网络中断、延迟过高或数据包丢失等情况,可能导致消息的确认信息未能及时发送到消息队列。消息队列在未收到确认信息时,会认为消息没有被成功消费,从而重新发送消息,导致消费端重复收到消息。
TCP 粘包半包是什么,怎么解决的?
粘包 指发送方在多次发送数据的过程中,数据包在同一个数据流中传输给了接收端,导致接收端无法正确分割数据包。例如,客户端连续发送两个数据包 “ABC” 和 “DEF”,服务端可能一次性收到 “ABCDEF”,这就像是两个包粘在了一起。产生原因主要有以下两点:
- 发送方原因 :发送方每次写入数据小于套接字(Socket)缓冲区大小,TCP 会将多次写入缓冲区的数据一次发送出去。例如,发送方先写入 “ABC”,此时缓冲区未满,接着又写入 “DEF”,然后 TCP 将 “ABCDEF” 一起发送到接收端。
- 接收方原因 :接收方读取套接字(Socket)缓冲区数据不够及时。当接收方的应用层没有及时读取接收缓冲区中的数据,新的数据又不断到来,就可能导致多个数据包被缓存,接收方一次读取时就会得到多个粘在一起的包。
半包 指发送方发送的数据大于发送缓冲区,接收端一次接收的数据不是完整的数据。比如,客户端发送一个较大的数据包 “ABCDEFG”,由于数据包大小超过了 TCP 缓存容量,它会被分成多个包发送,服务端第一次可能只收到 “ABC”,这就是半包现象。半包产生的原因主要有以下方面:
- 发送方原因 :发送方每次写入数据大于套接字(Socket)缓冲区大小,数据包不得不被分割成多个小包进行发送。
针对 TCP 粘包和沾包一般有三种解决的方式。
- 固定长度的消息;
- 特殊字符作为边界;
- 自定义消息结构。
固定长度的消息
这种是最简单方法,即每个用户消息都是固定长度的,比如规定一个消息的长度是 64 个字节,当接收方接满 64 个字节,就认为这个内容是一个完整且有效的消息。
但是这种方式灵活性不高,实际中很少用。
特殊字符作为边界
我们可以在两个用户消息之间插入一个特殊的字符串,这样接收方在接收数据时,读到了这个特殊字符,就把认为已经读完一个完整的消息。
HTTP 是一个非常好的例子。
HTTP 通过设置回车符、换行符作为 HTTP 报文协议的边界。
有一点要注意,这个作为边界点的特殊字符,如果刚好消息内容里有这个特殊字符,我们要对这个字符转义,避免被接收方当作消息的边界点而解析到无效的数据。
自定义消息结构
我们可以自定义一个消息结构,由包头和数据组成,其中包头包是固定大小的,而且包头里有一个字段来说明紧随其后的数据有多大。
比如这个消息结构体,首先 4 个字节大小的变量来表示数据长度,真正的数据则在后面。
struct {
u\_int32\_t message\_length;
char message\_data[];
} message;
当接收方接收到包头的大小(比如 4 个字节)后,就解析包头的内容,于是就可以知道数据的长度,然后接下来就继续读取数据,直到读满数据的长度,就可以组装成一个完整到用户消息来处理了。
限流算法中漏桶和令牌桶使用场景是什么?
漏桶限流算法
漏桶限流算法是模拟水流过一个有漏洞的桶进而限流的思路,如图。
水龙头的水先流入漏桶,再通过漏桶底部的孔流出。如果流入的水量太大,底部的孔来不及流出,就会导致水桶太满溢出去。
从系统的角度来看,我们不知道什么时候会有请求来,也不知道请求会以多大的速率来,这就给系统的安全性埋下了隐患。但是如果加了一层漏斗算法限流之后,就能够保证请求以恒定的速率流出。在系统看来,请求永远是以平滑的传输速率过来,从而起到了保护系统的作用。
使用漏桶限流算法,缺点有两个:
- 即使系统资源很空闲,多个请求同时到达时,漏桶也是慢慢地一个接一个地去处理请求,这其实并不符合人们的期望,因为这样就是在浪费计算资源。
- 不能解决流量突发的问题,假设漏斗速率是2个/秒,然后突然来了10个请求,受限于漏斗的容量,只有5个请求被接受,另外5个被拒绝。你可能会说,漏斗速率是2个/秒,然后瞬间接受了5个请求,这不就解决了流量突发的问题吗?不,这5个请求只是被接受了,但是没有马上被处理,处理的速度仍然是我们设定的2个/秒,所以没有解决流量突发的问题
令牌桶限流算法
令牌桶是另一种桶限流算法,模拟一个特定大小的桶,然后向桶中以特定的速度放入令牌(token),请求到达后,必须从桶中取出一个令牌才能继续处理。如果桶中已经没有令牌了,那么当前请求就被限流。如果桶中的令牌放满了,令牌桶也会溢出。
放令牌的动作是持续不断进行的,如果桶中令牌数达到上限,则丢弃令牌,因此桶中可能一直持有大量的可用令牌。此时请求进来可以直接拿到令牌执行。比如设置 qps 为 100,那么限流器初始化完成 1 秒后,桶中就已经有 100 个令牌了,如果此前还没有请求过来,这时突然来了 100 个请求,该限流器可以抵挡瞬时的 100 个请求。由此可见,只有桶中没有令牌时,请求才会进行等待,最终表现的效果即为以一定的速率执行。令牌桶的示意图如下:
令牌桶限流算法综合效果比较好,能在最大程度利用系统资源处理请求的基础上,实现限流的目标,建议通常场景中优先使用该算法。
Springboot怎么做到导入就可以直接使用的?
这个主要依赖于自动配置、起步依赖和条件注解等特性。
起步依赖
起步依赖是一种特殊的 Maven 或 Gradle 依赖,它将项目所需的一系列依赖打包在一起。例如,
spring-boot-starter-web
这个起步依赖就包含了 Spring Web MVC、Tomcat 等构建 Web 应用所需的核心依赖。
开发者只需在项目中添加一个起步依赖,Maven 或 Gradle 就会自动下载并管理与之关联的所有依赖,避免了手动添加大量依赖的繁琐过程。
比如,在
pom.xml
中添加
spring-boot-starter-web
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
自动配置
Spring Boot 的自动配置机制会根据类路径下的依赖和开发者的配置,自动创建和配置应用所需的 Bean。它通过
@EnableAutoConfiguration
注解启用,该注解会触发 Spring Boot 去查找
META - INF/spring.factories
文件。
spring.factories
文件中定义了一系列自动配置类,Spring Boot 会根据当前项目的依赖情况,选择合适的自动配置类进行加载。例如,如果项目中包含
spring-boot-starter-web
依赖,Spring Boot 会加载
WebMvcAutoConfiguration
类,该类会自动配置 Spring MVC 的相关组件,如 DispatcherServlet、视图解析器等。
开发者可以通过自定义配置来覆盖自动配置的默认行为。如果开发者在
application.properties
或
application.yml
中定义了特定的配置,或者在代码中定义了同名的 Bean,Spring Boot 会优先使用开发者的配置。
条件注解
条件注解用于控制 Bean 的创建和加载,只有在满足特定条件时,才会创建相应的 Bean。Spring Boot 的自动配置类中广泛使用了条件注解,如
@ConditionalOnClass
、
@ConditionalOnMissingBean
等。
比如,
@ConditionalOnClass
表示只有当类路径中存在指定的类时,才会创建该 Bean。例如,在
WebMvcAutoConfiguration
类中,可能会有如下代码:
@Configuration
@ConditionalOnClass
({ Servlet
.
class
,
DispatcherServlet
.
class
,
WebMvcConfigurer
.
class
})
public
class
WebMvcAutoConfiguration
{
// 配置相关的 Bean
}
这段代码表示只有当类路径中存在
Servlet
、
DispatcherServlet
和
WebMvcConfigurer
类时,才会加载
WebMvcAutoConfiguration
类中的配置。
mysql默认隔离级别是什么?
可重复读隔离级别
可重复读能够解决幻读问题吗?
可重复读隔离级别下虽然很大程度上避免了幻读,但是还是没有能完全解决幻读。
- InnoDB 使用 MVCC 来实现在同一时间点不同事务的数据并发访问。在可重复读隔离级别下,MVCC 通过为每个事务分配唯一的事务 ID 和时间戳来跟踪每个数据行的版本。对于快照读(普通的查询语句),只会读取已提交的数据版本,并忽略未提交的或已回滚的事务的数据版本。这样,即使其他事务对数据行进行了插入操作,当前事务仍然能读取到事务开始时的数据,避免了幻读。例如,事务 A 在开始时查询了一批数据,在事务执行过程中,事务 B 插入了新的数据,但事务 A 再次查询时,由于 MVCC 的作用,仍然会读到事务开始时的数据,不会看到事务 B 插入的新数据。
- 在可重复读隔离级别下,当执行锁定读语句的时候,它会在读取数据行的同时,也会锁定数据行之前和之后的间隙,防止其他事务在该间隙中插入新的数据行。例如,事务 A 执行
select * from table where a = 40 for update
,此时 InnoDB 不仅会锁住值为 40 的这条记录,还会锁(20, 40)
、(40, 60)
这两个间隙。如果事务 B 尝试插入21
或41
,就会因为获取不到锁而失败,从而避免了幻读。
举一个可重复读下出现幻读的例子?
我举例一个可重复读隔离级别发生幻读现象的场景。以这张表作为例子:
事务 A 执行查询 id = 5 的记录,此时表中是没有该记录的,所以查询不出来。
# 事务 A
mysql>
begin
;
Query OK, 0 rows affected (0.00 sec)
mysql>
select
*
from
t\_stu
where
id
=
5
;
Empty
set
(
0.01
sec)
然后事务 B 插入一条 id = 5 的记录,并且提交了事务。
# 事务 B
mysql>
begin
;
Query OK, 0 rows affected (0.00 sec)
mysql>
insert
into
t\_stu
values
(
5
,
'小美'
,
18
);
Query OK, 1 row affected (0.00 sec)
mysql>
commit
;
Query OK, 0 rows affected (0.00 sec)
此时, 事务 A 更新 id = 5 这条记录,对没错,事务 A 看不到 id = 5 这条记录,但是他去更新了这条记录,这场景确实很违和,然后再次查询 id = 5 的记录,事务 A 就能看到事务 B 插入的纪录了,幻读就是发生在这种违和的场景 。
# 事务 A
mysql>
update
t\_stu
set
name
=
'小林coding'
where
id
=
5
;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql>
select
*
from
t\_stu
where
id
=
5
;
+
----+--------------+------+
| id | name | age |
+
----+--------------+------+
| 5 | 小林coding | 18 |
+
----+--------------+------+
1 row in
set
(
0.00
sec)
整个发生幻读的时序图如下:
在可重复读隔离级别下,事务 A 第一次执行普通的 select 语句时生成了一个 ReadView,之后事务 B 向表中新插入了一条 id = 5 的记录并提交。接着,事务 A 对 id = 5 这条记录进行了更新操作,在这个时刻,这条新记录的 trx_id 隐藏列的值就变成了事务 A 的事务 id,之后事务 A 再使用普通 select 语句去查询这条记录时就可以看到这条记录了,于是就发生了幻读。
因为这种特殊现象的存在,所以我们认为 MySQL Innodb 中的 MVCC 并不能完全避免幻读现象 。
mysql有哪些日志?都有什么作用?
- redo log 重做日志,是 Innodb 存储引擎层生成的日志,实现了事务中的 持久性 ,主要 用于掉电等故障恢复 ;
- undo log 回滚日志,是 Innodb 存储引擎层生成的日志,实现了事务中的 原子性 ,主要 用于事务回滚和 MVCC 。
- bin log 二进制日志,是 Server 层生成的日志,主要 用于数据备份和主从复制 ;
- relay log 中继日志,用于主从复制场景下,slave通过io线程拷贝master的bin log后本地生成的日志
- 慢查询日志,用于记录执行时间过长的sql,需要设置阈值后手动开启
插入一行数据,分别什么时候写入这三个日志?
INSERT 事务
│
│
1
. 生成 Undo Log(记录反向操作)
▼
更新 Buffer Pool 数据页
│
│
2
. 写入 Redo Log Buffer(物理变更)
▼
「事务提交 Prepare 阶段」
│
│
3
. Redo Log 刷盘(根据参数配置)
▼
「事务提交 Commit 阶段」
│
│
4
. 写入 Binlog 并刷盘(根据参数配置)
▼
完成
Redo Log
- 事务执行中 :当执行插入操作时,相关的修改记录会先写入 Redo Log Buffer。
- 事务提交时
:InnoDB 存储引擎会先将 Redo Log 标记为 “准备提交”(prepare)状态,然后将 Redo Log Buffer 中的内容写入到文件系统的 Page Cache 中,在适当的时候,系统会调用 fsync 将 Page Cache 中的 Redo Log 数据持久化到磁盘中的redolog文件;也有可能日志直接从 Buffer 持久化到了磁盘。可以通过参数
innodb\_flush\_log\_at\_trx\_commit
配置 Redo Log 的写入策略,设置为 1(默认)表示每次事务提交时都将 Redo Log 持久化到磁盘,以保证数据的安全性。
Undo Log
- 插入数据时 :执行插入操作时,InnoDB 会将插入记录的反向操作(逻辑上可以理解为 “删除” 这个插入记录的操作)记录在 Undo Log 中,用于事务回滚。如果是 InnoDB 引擎,还会根据 MVCC(多版本并发控制)机制,可能会记录一些与版本控制相关的信息。
- 事务提交时 :在事务提交时,Undo Log 不会被立即删除,而是标记为可清理,后续由 Purge 线程根据情况判断是否可以真正释放 Undo Log 占用的空间。
Binlog
- 事务执行过程中 :在事务执行插入操作时,相关的 SQL 语句会先被记录到 Binlog Cache 中,Binlog Cache 是每个线程内独立的空间。
- 事务提交时
:可以通过参数
sync\_binlog
控制 Binlog 的持久化时机,sync\_binlog = 1
表示每次提交事务都要发生 fsync,将 Binlog 从 Page Cache 中真正持久化到磁盘;sync\_binlog = n
表示每次事务都会 write,但是 n 次事务提交会执行 fsync 进行持久化。
线上项目,没有日志,没后台数据的情况下怎么找出问题?
- 根据用户反馈的信息,尝试在测试环境或本地环境中重现问题。通过模拟用户的操作步骤,可能会发现一些在生产环境中难以察觉的问题。如果能够重现问题,就可以更方便地进行调试和分析。
- 如果问题是在某个版本发布后出现的,对比当前线上版本与之前正常工作的版本之间的代码差异。查看是否有引入新的功能、修改了配置或依赖关系等,这些变化可能导致了问题的出现。
- 可以通过生成Heap Dump或者线程转储来分析。例如,使用jstack、jmap这些JDK工具。或者使用Arthas的监控命令,实时查看方法执行情况。还要考虑到网络问题,比如是否有外部服务调用失败,或者资源耗尽的情况,比如数据库连接池满了。这时候可能需要检查系统资源,如CPU、内存、磁盘IO、网络流量等,使用top、netstat、ss等命令。
算法
-
算法:lc415字符串相加
最后欢迎
,你将获得:AI开发项目课程、苏三AI项目、
商城微服务实战、秒杀系统实战
、
商城系统实战、秒杀系统实战、代码生成工具、系统设计、性能优化、技术选型、底层原理、Spring源码解读、工作经验分享、痛点问题
、面试八股文
等多个优质专栏。
还有1V1答疑、修改简历、职业规划、送书活动、技术交流。
扫描下方二维码,即可加入星球:
目前星球已经更新了 5200+ 篇优质内容,还在持续爆肝中.....
星球已经被 官方推荐了3次 ,收到了小伙伴们的一致好评。戳我加入学习,已有 1600+ 小伙伴加入学习。
苏三的免费八股文网站: