将 脚本之家 设为“星标⭐”
第一时间收到文章更新
文 | 小林coding
出品 | 小林coding(ID:CodingLin )
大家好,我是小林。
最近是春招投递的高峰期时间,很多同学遇到一个问题,大厂部门那么多,如何选择部门来投递呢?
我的想法是这样,如果在还没拿到某个大厂 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来进行不同维度对比。
单机吞吐量 |
万级 |
万级 |
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 的记录,此时表中是没有该记录的,所以查询不出来。
# 事务 Amysql> 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 的记录,并且提交了事务。
# 事务 Bmysql> 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 插入的纪录了,幻读就是发生在这种违和的场景。
# 事务 Amysql> update t_stu setname = '小林coding'whereid = 5;Query OK, 1 row affected (0.01 sec)Rows matched: 1 Changed: 1 Warnings: 0mysql> select * from t_stu whereid = 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字符串相加
END
看完这篇文章你有什么想说的?欢迎各位评论区聊一聊
留言抽 3位 小伙伴,赠送8.88元现金红包!
下面顺便给大家推荐一个不错的AI工具聚合平台,已接入自部署满血版 DeepSeek-R1,欢迎来体验。
点击长按二维码,即可体验:
👇👇👇
✅传送门:https://www.aijuli.com