目录

领星-牛客网面经

首先先说明一下我的招聘性质是社招(1.5年工作经验),本人目前是在深圳,面试是在线下面试的,面试主要是两轮,一轮技术面,一轮HR面;

公司环境非常不错,感觉员工都很年轻,领导年纪稍微大一些(估摸着应该30+),整个面试流程大改30min+,难度也不是很大,主要是针对简历上写的来问一些场景题, eg:

领星-Java开发岗技术面

1.阐述下项目架构,用到哪些技术,有哪些技术挑战和难点等等。


2.根据你的回答,问一些基础的东西,如MQ是用在哪些地方,怎么保证消息不丢失等等

(我回答:Mq在项目中主要是用在异步解耦第三方请求上….;保证消息一致是使用消息表【数据库】解释一下流程,但不是很对,可参考以下回答)


RocketMQ消息丢失防治 RocketMQ的消息想要确保不丢失,需要生产者、消费者以及Broker的共同努力,缺一不可。

首先在生产者端,消息的发送分为同步和异步两种,在同步发送消息的情况下,消息的发送会同步阻塞等待Broker返回结果,在Broker确认收到消息之后,生产者才会拿到SendResult。如果这个过程中发生异常,那么就说明消息发送可能失败了,就需要生产者进行重新发送消息。

但是Broker其实并不会立即把消息存储到磁盘上,而是先存储到内存中,内存存储成功之后,就返回给确认结果给生产者了。然后再通过异步刷盘的方式将内存中的数据存储到磁盘上。但是这个过程中,如果机器挂了,那么就可能会导致数据丢失。

如果想要保证消息不丢失,可以将消息保存机制修改为同步刷盘,这样,Broker会在同步请求中把数据保存在磁盘上,确保保存成功后再返回确认结果给生产者。

使用消息表 在使用消息队列(Message Queue,简称MQ)进行系统间解耦和异步通信时,消息的可靠性是一个非常重要的考虑因素。消息表是一种常见的策略,用于确保消息不会在传输过程中丢失。以下是使用消息表来保障MQ消息不丢失的步骤:

  1. 消息表设计:在数据库中创建一个消息表,用于记录即将发送到MQ的消息。消息表通常包含以下字段:id(主键)、message(消息内容)、status(消息状态,如待发送、已发送、发送失败等)、send_time(发送时间)、retry_count(重试次数)等。

  2. 消息写入:当业务逻辑需要发送消息到MQ时,首先将消息写入消息表,并标记状态为“待发送”。为了保证数据库事务的一致性,写入消息表的操作应该与业务逻辑操作在同一个事务中。

  3. 消息发送:使用一个单独的服务或线程定期扫描消息表,查找状态为“待发送”的消息。将这些消息发送到MQ,并在发送后更新消息表中的状态为“已发送”。如果发送失败,更新状态为“发送失败”,并增加retry_count。

  4. 消息确认:在消息发送到MQ后,等待MQ的发送确认(如果MQ支持)。如果收到确认,更新消息表中的状态为“已确认”。如果没有收到确认或确认失败,根据策略决定是否重试发送。

  5. 失败处理:对于“发送失败”的消息,可以设置一个重试机制,例如指数退避策略。如果达到最大重试次数仍然失败,可以记录错误日志,并通过告警通知运维人员。

  6. 消息清理:对于已经“已确认”的消息,可以设置一个清理策略,定期清理旧消息,以避免消息表无限增长。

  7. 高可用和容错:确保消息表所在的数据库是高可用的,可以通过主从复制、数据库集群等方式。对于消息发送服务,也应该考虑其高可用性,避免单点故障。

通过以上步骤,可以有效地保障MQ消息的可靠性。以下是一些额外的最佳实践:

  • 幂等性:确保消息处理是幂等的,即重复处理同一消息不会产生副作用。

  • 顺序性:如果消息处理需要保持顺序,消息表应该支持顺序性,例如通过分区键或时间戳。

  • 监控和告警:实施监控,确保能够及时发现问题并进行处理。

使用消息表虽然可以保障消息不丢失,但也会引入额外的复杂性和可能的性能瓶颈。因此,在设计系统时,应根据业务需求和系统特性综合考虑。


3.根据我的简历,问了一些关于Redis的使用场景,在项目里怎么用的

(我的回答:在项目中,一些登录信息和一些配置、业务信息会用到Redis,举例使用配置信息怎么放到Redis的:首先配置信息属于是热点数据,一般查询的时候会从redis中读取,redis中没有再去数据库中查询,减少数据库的压力,在更新或者插入时先删除redis中的数据再去修改数据库,保证缓存数据的一致性[其实这里还可以拓展缓存穿透问题:比如使用布隆过滤器之类的进行判空操作],可以参考以下回答)


保证缓存一致性 为了保证Redis和数据库的数据一致性,肯定是要缓存和数据库双写了。

一般来说,在业内有3种比较常见的具体方案:

1、先更新数据库, 再删除缓存。

2、延迟双删:先删除缓存,再更新数据库,再删除一次缓存

3、cache-aside:更新数据库,基于 binlog 监听进行缓存删除

缓存击穿、缓存穿透、缓存雪崩

缓存击穿:是指当某一key的缓存过期时大并发量的请求同时访问此key,瞬间击穿缓存服务器直接访问数据库,让数据库处于负载的情况。

缓存穿透:是指缓存服务器中没有缓存数据,数据库中也没有符合条件的数据,导致业务系统每次都绕过缓存服务器查询下游的数据库,缓存服务器完全失去了其应用的作用。

缓存雪崩:是指当大量缓存同时过期或缓存服务宕机,所有请求的都直接访问数据库,造成数据库高负载,影响性能,甚至数据库宕机。

解决方案: 一、缓存击穿: 1.异步定时更新:在缓存处理上,同理,比如某一个热点数据的过期时间是1小时,那么每59分钟,通过定时任务去更新这个热点key,并重新设置其过期时间。

2.互斥锁:在缓存处理上,通常使用一个互斥锁来解决缓存击穿的问题。简单来说就是当Redis中根据key获得的value值为空时,先锁上,然后从数据库加载,加载完毕,释放锁。若其他线程也在请求该key时,发现获取锁失败,则先阻塞。

二、缓存穿透:

1.缓存空值:在缓存中,之所以会发生穿透,就是因为缓存没有对那些不存在的值得Key缓存下来,从而导致每次查询都要请求到数据库。那么我们就可以为这些key对应的值设置为null并放到缓存中,这样再出现查询这个key 的请求的时候,直接返回null即可 。

2.BloomFilter: 很多时候,缓存穿透是因为有很多恶意流量的请求,这些请求可能随机生成很多Key来请求查询,这些肯定在缓存和数据库中都没有,那就很容易导致缓存穿透。在缓存穿透防治上常用的技术是布隆过滤器(Bloom Filter)。布隆过滤器是一种比较巧妙的概率性数据结构,它可以告诉你数据一定不存在或可能存在,相比Map、Set、List等传统数据结构它占用内存少、结构更高效。对于缓存穿透,我们可以将查询的数据条件都哈希到一个足够大的布隆过滤器中,用户发送的请求会先被布隆过滤器拦截,一定不存在的数据就直接拦截返回了,从而避免下一步对数据库的压力。

三、缓存雪崩:

1.不同的过期时间:为了避免大量的缓存在同一时间过期,可以把不同的key过期时间设置成不同的, 并且通过定时刷新的方式更新过期时间。

2.集群:在缓存雪崩问题防治上面,一个比较典型的技术就是采用集群方式部署,使用集群可以避免服务单点故障。


4.问在生产环境中CPU飙高一般是什么情况,如何排查

(我的回答结合实际生产环境的场景列举:一般出现CPU飙高,说明服务器中肯定存在CPU的大量运算,可能出现:1.代码中存在死循环;2.代码结构不好,导致CPU一直处于高度占用状态;3.在使用多线程或者并发高时,出现等待或阻塞死锁的情况【这里反问了一下,死锁为什么会出现CPU飙高呢?我回回答了:因为Java的线程在尝试获取锁或者在进行子内存和主内存间的交换数据时导致CPU不停的在计算,可能会导致CPU飙高;排查呢可以使用排查呢可以使用JvisualVm(一些性能检测工具),查看内存使用情况以及CPU使用情况,看代码运行时间,大改就可以判断出哪块代码出现问题,我感觉不是很对也不完整,可以参考以下回答)


在Java生产环境中遇到CPU使用率飙升的情况,通常是由多种原因导致的。下面是一些常见的原因以及排查方法:

常见原因 线程问题:死锁、大量线程竞争、线程长时间运行。 内存问题:频繁的垃圾回收(GC),内存泄露。 代码效率问题:存在效率低下的算法或资源使用不当。 外部资源等待:数据库、网络或其他系统响应缓慢。 系统问题:操作系统或硬件问题。 排查步骤 使用TOP或任务管理器:查看CPU使用率高的进程,确认是Java进程导致的。 线程 Dump:使用jstack命令获取Java进程的线程堆栈信息。分析线程状态,查看是否有大量线程处于运行状态,是否有死锁。 分析GC日志:启用GC日志,分析日志文件,看是否频繁进行Full GC。使用工具如VisualVM, GCViewer等来分析GC日志。 CPU Profile:使用VisualVM, JProfiler等工具进行CPU抽样或跟踪,查看哪些方法占用CPU时间最长。 查看系统资源:检查磁盘I/O、网络使用情况,看是否有异常。检查操作系统日志,看是否有硬件错误或资源瓶颈。 代码审查:分析可能存在性能问题的代码段,如循环、递归调用、同步块等。使用代码分析工具,如FindBugs, Checkstyle等,帮助发现潜在问题。 外部系统检查:如果应用依赖外部系统(如数据库),检查这些系统的性能和日志。 复现问题:如果可能,尝试在测试环境中复现问题,进行更深入的分析。 实用命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//查看Java进程:
ps -ef | grep java
//获取线程堆栈:
jstack -l <pid> > thread_dump.txt
//查看GC情况:
jstat -gcutil <pid> 1000
//分析堆信息:
jmap -heap <pid>
jmap -histo <pid>

通过上述步骤,通常能够定位到导致CPU使用率飙升的原因。在处理问题时,建议记录每一步的操作和观察到的现象,这有助于问题的定位和解决。


5.根据简历上写的SQL优化提问,如何优化SQL,你在项目中是怎么做的

(回答:一般SQL优化主要是看索引是否失效,减少SQL的复杂度等等,列举一些实际场景如:SQL中联表查出现大表套小表,超过3张表一起查询等等,优化方案如:如何拆分SQL,优化索引,使用java代码对查询做封装和优化等等,可以参考以下回答)


SQL优化策略:

使用索引:为经常用于查询条件的列创建索引。避免在索引列上使用函数或计算,这会导致索引失效。

选择合适的索引类型:根据查询需求选择合适的索引类型,如B-Tree、HASH、FULLTEXT等。

优化查询语句:避免使用SELECT *,只查询需要的列。使用JOIN代替子查询,特别是在WHERE子句中。使用LIMIT限制返回结果的数量,尤其是在分页查询中。

减少数据量:使用WHERE子句过滤不必要的行。使用GROUP BY和HAVING子句进行数据聚合。

优化数据模型:正规化数据模型以减少数据冗余。在某些情况下,适当的反规范化可以提高查询性能。

使用查询缓存:如果数据库支持查询缓存,确保它被合理配置和使用。

分析执行计划:使用EXPLAIN或类似的命令分析查询的执行计划,查找潜在的性能瓶颈。

项目中实施SQL优化的步骤:

性能监控:使用数据库的性能监控工具(如MySQL的Performance Schema、PostgreSQL的pg_stat_statements等)来监控慢查询。

慢查询日志分析:启用慢查询日志,定期分析慢查询,找出性能瓶颈。

建立SQL审核流程:在代码提交前,对SQL查询进行代码审查,确保遵循最佳实践。

定期优化:定期对数据库进行维护,如更新统计信息、重建索引等。

测试和基准测试:在部署前对SQL查询进行测试,使用基准测试工具(如sysbench、Apache JMeter等)来评估性能。

持续改进:根据监控结果和业务需求,持续优化SQL查询和数据模型。

培训和教育:对开发人员进行数据库性能优化方面的培训,提高他们的意识和技能。


6.根据简历上使用的多线程,线程池进行提问,如怎么设置的核心线程数,线程存活时间等等,你是怎么设置这些参数的,有用到线程池的拦截策略吗?是什么?

(我的回答:在项目中用到的线程数量大约在十多个左右,由于大多线程主要是在做数据库的操作,少部分数数据组织操作,主要是IO密集型和CPU密集型,但是线程数量也不是很多,加上服务器的CPU配置也还能接受,所以主要考虑的是有多少线程就设置多大的线程池; 拦截策略主要说了在项目中对一些参数的校验,如果不通过则不允许执行等等,可以参考以下回答)


线程池的核心参数:

acc : 获取调用上下文

corePoolSize: 核心线程数量

aximumPoolSize: 最大的线程数量

workQueue:多余任务等待队列

keepAliveTime:非核心线程空闲时间

threadFactory: 创建线程的工厂

handler:线程池拒绝策略

影响线程数的因素:

CPU 核数

多核处理器:理想的线程数肯定取决于处理器的核心数。在理想情况下,每个核心运行一个线程是最高效的。

超线程技术(如Intel的Hyper-Threading):目前,很多CPU都采用了超线程技术,也就是利用特殊的硬件指令,把两个逻辑内核模拟成两个物理芯片,让单个处理器都能使用线程级并行计算。所以我们经常可以看到"4核8线程的CPU",也就是物理内核有4个,逻辑内核有8个。如果CPU支持超线程技术,可以为每个核心分配更多的线程,因为超线程可以提高CPU资源的利用率。

应用类型

CPU密集型:对于CPU密集型的任务(如计算密集型任务),线程数最好设置为核心数的1到1.5倍,因为这些任务主要消耗CPU资源。

I/O密集型:如果任务涉及大量的等待或阻塞(如数据库操作、文件操作、网络操作等),则可以配置更多的线程,比如2倍,因为线程在等待时CPU可以切换去处理其他任务。

JVM和系统资源

内存限制:每个线程都会占用一定的内存(如栈空间)。如果创建过多线程,可能会消耗大量内存,甚至导致内存溢出。

操作系统限制:操作系统对进程可创建的线程数通常有限制,过多的线程可能导致系统性能下降。

其他考虑

RT要求:如果系统对响应时间有严格要求,可能需要更多线程来减少处理延迟。

任务特性:不同的任务可能对线程数的需求不同。长时间运行的任务与短时任务,同步任务与异步任务,都需要考虑不同的线程配置。

公式 根据我们前面提到的这些影响因素来看,主要和CPU核心数、以及应用类型等有关。简单一点的话,可以根据应用类型来套用以下公式(但是,实际应用起来,也不要死守着公式不放,公式只是可以当作参考):

如果是CPU密集型应用,则线程池大小设置为N+1 如果是IO密集型应用,则线程池大小设置为2N+1 上面的N为CPU总核数

但是,上面的公式中,前提要求是知道你的应用是IO密集型还是CPU密集型,那么,到底怎么样算IO密集,怎么样又算CPU密集呢?一个应用就真的能明确的定位出来是CPU密集还是IO密集吗?

所以,还有另外一个公式:

/posts/%E9%A2%86%E6%98%9F-%E7%89%9B%E5%AE%A2%E7%BD%91%E9%9D%A2%E7%BB%8F/img.png

等待时间是指线程在执行过程中花费在等待外部操作完成的时间。这些外部操作通常包括I/O操作(如读写文件、数据库操作、网络请求等)和其他资源的同步等待(如等待锁的释放)。在等待时间内,线程通常不占用CPU资源,因为它在等待某个事件或资源可用。

计算时间是指线程实际进行计算处理的时间,即线程在CPU上执行操作的时间。计算时间通常指的是CPU密集型操作,如数学计算、数据处理等。

在这个公式中,“等待时间 / 计算时间"的比例是一个关键因素,它帮助决定合适的线程数量以平衡CPU利用和等待效率:

I/O密集型任务:对于I/O密集型任务,等待时间通常远大于计算时间,这意味着可以分配更多的线程。当一个线程在等待时(如等待网络响应),CPU可以切换到另一个线程进行计算,从而提高CPU利用率。

CPU密集型任务:对于CPU密集型任务,计算时间通常占主导地位。在这种情况下,增加线程数可能不会提高性能,因为大部分时间都在进行CPU计算,线程之间的上下文切换可能导致性能下降。


7.最后反问面试官一些问题

(问了一些面试官公司项目架构以及使用到的技术栈这块的问题;问了需求开发流程这一块的问题;问了整个项目管理上是否为客户提供多个分支的问题,以及Git代码管理方式的问题)

总结:

面试的整体感受:

整个面试流程感觉面试官也比较亲和,主要就是根据简历上写的和项目中的东西来提问,我觉得在面试过程中可添加一些自己的想法,对一些问题的探讨,比如举例在项目中实际遇到的一些场景,怎么解决的,思路是什么,怎么做的等等;

面试之前也在牛客上看了几篇面试的帖子,不知道是不是各位同志是校招的原因,还是项目不够多,面试官没得问,直接上来就是八股拷打(本人八股也不是很好,只能记个大概Q_Q),导致我在去面试的时候慌得一批,但是提前去到公司之后,在那呆了差不多半个钟把状态调整了一下,面试的时候稍微好一些;

我觉得八股背肯定是要背的,但是你得理解着背,套到实际当中去,这样即使想不起来,也能通过某个案例说出个大概出来;

最后分享一下我的一些想法:

1.遇到不会的提问时不会不要硬答,说错了反而印象不好;

2.有一点把握的问题,能答多少答多少,说一说自己的看法;

3.保持良好的心态,面试的时候不要紧张,当和朋友聊天一样就行;

作者:Oamgnoix 链接:https://www.nowcoder.com/discuss/664192060047568896 来源:牛客网

AI搜索的

有没有领星Java开发工程师的面试经验分享?

领星Java开发工程师的面试经验主要围绕‌技术深度‌、‌项目实战‌和‌系统设计能力‌展开,尤其注重候选人对Spring Boot、分布式架构及企业级应用开发的理解。以下是结合真实面试反馈的详细经验分享:

一、面试流程与整体感受

领星的Java开发岗位面试通常为‌社招性质‌,流程简洁高效,整体时长约30分钟以上,包含两轮:

第一轮:技术面‌(重点考察技术栈匹配度与项目细节)

第二轮:HR面‌(了解职业规划与团队适配性)

面试官普遍亲和,提问以简历内容为核心,偏向于从实际项目出发探讨技术选型、难点解决和优化思路,而非单纯“八股文”拷问。因此,准备时应更注重‌结合业务场景讲清楚“为什么这么做”‌。

二、技术面高频考察方向

  1. ‌基础但不脱离实际‌

虽然会问Java基础,但往往结合项目场景。例如:

“你在项目中用到了MQ,具体在哪些环节?如何保证消息不丢失?”

回答需体现全流程理解:生产者确认机制、Broker持久化配置、消费者ACK机制等,并可提及RocketMQ的具体实践。

在领星Java开发工程师面试中,如果被问到“你在项目中用到了MQ,具体在哪些环节?如何保证消息不丢失?”,可以这样回答:

  1. MQ在项目中的具体使用环节:‌

在实际项目中,MQ(消息队列)主要被用于以下几个关键环节来提升系统性能和解耦能力:

系统解耦与异步处理‌:当一个核心业务操作(如订单创建)需要触发多个下游系统的处理(如库存扣减、积分增加、发送通知等),通过MQ可以将这些操作异步化。订单服务在创建订单后,将“订单已创建”消息发送到MQ,而库存、积分等服务作为消费者从MQ中消费消息并处理,避免了服务间的直接调用和强耦合。

流量削峰‌:在高并发场景下(如秒杀活动),直接请求涌入后端服务可能导致系统过载甚至崩溃。MQ可以作为缓冲池,将瞬时的高并发请求放入队列中,由消费者以系统可承受的速率处理,从而平滑流量峰值。

数据同步‌:在微服务架构中,不同服务可能拥有独立的数据库。为了保证数据一致性,可以通过MQ实现数据变更的异步同步。例如,用户服务更新了用户信息,会发送一条“用户信息已变更”的消息到MQ,订单服务作为消费者监听该消息并更新本地缓存或相关数据。

日志收集与处理‌:系统产生的日志(如操作日志、错误日志)可以通过MQ进行集中收集和处理,便于后续分析和监控。

  1. 如何保证消息不丢失:‌

消息丢失是使用MQ时需要重点保障的问题。通常需要从生产、存储、消费三个环节共同保障:

生产者端(发送)‌:

开启发送确认机制‌:如使用RocketMQ时,可以启用sendMessageWithVIPChannel的同步发送模式或使用TransactionListener实现事务消息,确保消息被Broker成功接收。

使用消息表‌:在发送消息前,将消息记录插入数据库(消息表),并确保该操作与业务逻辑在同一个事务中。如果发送失败,则通过定时任务或补偿机制重新发送消息。

设置合理的重试策略‌:生产者在发送失败时,应有合理的重试机制,并设置最大重试次数,避免无限重试导致资源浪费。

MQ Broker端(存储)‌:

启用持久化存储‌:确保消息在Broker上是持久化存储的,即使Broker宕机,消息也不会丢失。

配置副本机制‌:如Kafka的副本机制,确保消息在多个节点上都有备份,即使单个节点故障也能保证消息可用。

刷盘策略‌:选择合适的刷盘策略(如同步刷盘),确保消息写入磁盘后才返回成功确认,以防止在Broker宕机时丢失数据。

消费者端(消费)‌:

手动ACK确认‌:消费者在接收到消息后,先处理业务逻辑,处理成功后再手动发送ACK确认给Broker。如果处理失败,可以选择拒绝ACK或进行重试,避免消息被错误地标记为已消费。

实现幂等性‌:消费者在处理消息时,需要保证即使消息被重复消费,也不会对业务造成影响。可以通过消息ID、业务唯一标识等方式实现幂等性校验。

设置死信队列‌:对于消费失败的消息,可以将其发送到死信队列(DLQ),由专门的消费者处理或人工介入排查,防止消息无限重试或丢失。

通过以上措施的组合应用,可以最大程度地保证消息在生产、传输和消费过程中的可靠性,确保业务的稳定运行。


  1. ‌框架原理深入追问‌

Spring Boot是核心技术栈,常见问题包括:

自动配置原理是什么?@SpringBootApplication 包含哪些注解?

在领星Java开发工程师面试中,关于Spring Boot自动配置原理和@SpringBootApplication注解的考察,是评估候选人对Spring Boot核心机制理解的重要环节。以下是关键知识点:

Spring Boot自动配置原理‌

Spring Boot的自动配置机制旨在根据项目的依赖和配置,自动设置Spring应用程序所需的组件,从而减少开发者手动配置的工作量。其核心原理如下:

核心注解@EnableAutoConfiguration‌:

该注解是实现自动配置的关键。它通过@Import注解引入AutoConfigurationImportSelector类。

AutoConfigurationImportSelector会扫描classpath下的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件(或旧版本的spring.factories文件)。

该文件中列出了大量的自动配置类(如RedisAutoConfiguration、DataSourceAutoConfiguration等)。

通过SpringFactoriesLoader加载这些配置类的全限定名,并根据条件注解(如@ConditionalOnClass、@ConditionalOnMissingBean等)进行过滤,决定哪些配置类需要被加载到Spring容器中。

条件注解机制‌:

自动配置类中通常包含多个@Conditional系列注解,用于判断是否应该创建某个Bean。

例如,@ConditionalOnClass(RedisOperations.class)表示只有当类路径下存在RedisOperations类时,才会加载RedisAutoConfiguration配置类。

这种机制确保了只有在特定条件下(如引入了某个starter依赖),才会进行相应的自动配置。

配置属性绑定‌:

自动配置类通常会使用@ConfigurationProperties注解,将配置文件(如application.properties或application.yml)中的属性绑定到配置类的属性上。

例如,DataSourceProperties类会绑定数据库连接相关的配置,如spring.datasource.url等。

自动配置类的加载‌:

通过@AutoConfigurationPackage注解,会将启动类所在的包及其子包下的所有组件纳入自动配置的扫描范围。

最终,符合条件的自动配置类会通过@Bean方法在Spring容器中创建相应的Bean实例。

@SpringBootApplication 包含哪些注解?‌

@SpringBootApplication是一个复合注解,它将多个核心注解组合在一起,简化了Spring Boot应用的配置。它主要包含以下三个注解:

@SpringBootConfiguration‌:

这个注解本质上是一个@Configuration注解。

它标识当前类是一个配置类,允许使用@Bean注解定义Bean。

@EnableAutoConfiguration‌:

启用Spring Boot的自动配置功能。

这是实现自动配置的核心注解,它会根据类路径下的依赖和配置,自动配置Spring应用所需的组件。

@ComponentScan‌:

启用组件扫描。 默认扫描启动类所在的包及其子包下的所有带有@Component、@Service、@Repository、@Controller等注解的类,并将它们注册到Spring容器中。 该注解的excludeFilters属性可以用来排除特定的组件扫描。

通过这三个注解的组合,@SpringBootApplication为Spring Boot应用提供了启动、自动配置和组件扫描的完整功能,使得开发者可以快速搭建和运行一个Spring Boot应用。


Bean的生命周期?循环依赖如何解决?三级缓存的作用?

Bean 的生命周期

Spring 中的 Bean 生命周期可以分为以下几个阶段:

实例化(Instantiation)‌ Spring 根据 BeanDefinition 创建 Bean 实例。这一步只是创建了对象,还没有进行属性赋值和初始化。

属性赋值(Population)‌ Spring 根据 BeanDefinition 中的信息对 Bean 的属性进行赋值,比如通过 @Autowired 注解注入依赖。

初始化(Initialization)‌ 在属性赋值之后,Spring 会调用初始化方法。包括:

调用 InitializingBean 接口的 afterPropertiesSet() 方法; 执行自定义的初始化方法(如通过 @PostConstruct 标注的方法); 调用 BeanPostProcessor 的 postProcessBeforeInitialization() 和 postProcessAfterInitialization() 方法。

使用(Usage)‌ 初始化完成后,Bean 就可以被应用程序使用了。

销毁(Destruction)‌ 当容器关闭时,Spring 会调用 DisposableBean 接口的 destroy() 方法或自定义的销毁方法(如通过 @PreDestroy 标注的方法)来清理资源。

循环依赖如何解决?

当两个或多个 Bean 相互依赖时,就会出现循环依赖的问题。Spring 通过三级缓存机制来解决单例 Bean 的循环依赖问题。

具体流程如下:

当 Spring 创建 Bean A 时,发现它依赖于 Bean B,于是暂停 A 的创建,转而去创建 B。

在创建 B 的过程中,发现它依赖于 A,此时 Spring 会尝试从缓存中获取 A。

如果 A 还未完全创建,Spring 会将 A 提前暴露出来(放入三级缓存),并将其放入二级缓存中,供 B 使用。

B 创建完成后,A 可以继续完成属性赋值和初始化。

最终,A 和 B 都被放入一级缓存中,供后续使用。

三级缓存的作用

Spring 的三级缓存机制用于解决单例 Bean 的循环依赖问题,其作用如下:

一级缓存(singletonObjects)‌

存放已经完全初始化好的单例 Bean 实例。这是最终的缓存,存放的是完整的 Bean。

二级缓存(earlySingletonObjects)‌

存放早期暴露的 Bean 实例,即已经实例化但尚未完成属性注入和初始化的 Bean。主要用于解决循环依赖问题。

三级缓存(singletonFactories)‌

存放 Bean 的工厂对象,用于延迟创建 Bean 实例。当需要提前暴露 Bean 时,通过工厂对象生成 Bean 实例,并将其放入二级缓存中。

这种分层缓存机制使得 Spring 能够在不破坏 Bean 生命周期的前提下,优雅地处理循环依赖问题。特别是对于使用了 AOP 动态代理的 Bean,三级缓存还能确保代理对象的正确生成。

通过三级缓存机制,Spring 在保证 Bean 生命周期完整性的基础上,有效解决了循环依赖问题,提升了容器的健壮性和灵活性。


@Transactional 失效的常见场景有哪些?

@Transactional 注解在 Spring 中用于声明式事务管理,但在实际使用中,它可能会因为多种原因失效。以下是其常见的失效场景:

方法访问修饰符不是 public

@Transactional 注解只能应用于 public 方法上。如果方法是 private、protected 或包级可见(package-private),事务将不会生效。这是因为 Spring AOP 代理机制只对 public 方法生成代理对象。

方法内部直接调用‌

当一个带有 @Transactional 注解的方法被同一个类中的另一个方法直接调用时,事务不会生效。这是由于 Spring 使用 AOP 代理实现事务管理,而类内调用绕过了代理对象。

异常被捕获且未重新抛出‌

如果在方法中使用了 try-catch 块捕获了异常,并且没有将异常重新抛出,事务将不会回滚。因为 Spring 默认只对未被捕获的运行时异常(如 RuntimeException)进行回滚。

使用了错误的事务传播机制‌

若配置了不支持事务传播的行为,例如 PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER 等,事务将不会生效。

类未被 Spring 容器托管

如果被注解的方法所在的类没有被 Spring 管理(即未加 @Service、@Component 等注解),那么 @Transactional 注解也不会起作用。

数据库引擎不支持事务‌

比如 MySQL 的 MyISAM 存储引擎不支持事务,即使使用了 @Transactional 也不会生效。

事务方法被 private 或 final 修饰‌

被 private 或 final 修饰的方法无法被 Spring AOP 代理,因此事务不会生效。

异步多线程调用‌

在使用 @Async 注解时,如果方法同时被 @Transactional 标记,事务可能不会生效,因为它们运行在不同的线程中,各自拥有独立的事务上下文。

事务回滚异常类型设置不当‌

默认情况下,Spring 只对 RuntimeException 和 Error 进行事务回滚。如果抛出的是受检异常(checked exception),需要显式指定 rollbackFor 属性以触发回滚。

以上这些场景都可能导致 @Transactional 注解失效,开发者在开发过程中应特别注意避免这些情况。


这些问题要求不仅能用,还要“知其所以然”。

  1. ‌分布式与高可用设计‌

由于领星ERP涉及多平台数据同步、高并发订单处理,面试中常出现系统设计类题目:

如何设计一个支持千万级并发的优惠券系统?

设计一个支持千万级并发的优惠券系统需要从架构设计、数据存储、缓存策略、限流降级等多个维度综合考虑。以下是详细的设计思路和技术选型建议:

整体架构设计

  1. 架构分层

    接入层 -> 网关层 -> 应用服务层 -> 缓存层 -> 数据存储层 -> 异步处理层

  2. 核心设计要点

    读写分离‌: 查询走缓存,发放和核销走数据库

    异步处理‌: 发放、核销记录异步落库

    预热机制‌: 热点优惠券提前加载到缓存

    限流熔断‌: 防止突发流量击垮系统

    水平扩展‌: 无状态服务便于集群部署

    技术实现方案

  3. 数据模型设计

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
-- 优惠券模板表(活动配置)
coupon_template: id, name, total_count, issued_count,
start_time, end_time, rules(json), status

-- 用户优惠券表(已领取)
user_coupon: id, template_id, user_id, status(可用/已用/过期),
receive_time, used_time

-- 优惠券库存表(分段库存)
coupon_stock: template_id, segment_key, available_count
  1. 核心技术选型

    缓存‌: Redis Cluster + Lua脚本保证原子性

    数据库‌: MySQL主从 + 分库分表(user_coupon按用户ID分片)

    消息队列‌: Kafka/RocketMQ处理异步任务

    服务治理‌: Nacos + Sentinel实现注册发现和限流

    网关‌: Spring Cloud Gateway统一入口

  2. 并发控制策略

    Redis分布式锁‌: 防止超发

    Lua原子操作‌: 判断库存并扣减一体化

    令牌桶算法‌: 控制接口QPS

    数据库唯一索引‌: 防止重复领取

这个设计方案能支撑千万级并发的关键在于合理的缓存策略、异步处理机制以及多层次的限流保护措施。


分库分表后如何处理跨表查询?分布式事务如何保障一致性?

分库分表后处理跨表查询和保障分布式事务一致性是架构设计中的关键挑战。以下是具体的解决方案:

跨表查询处理方案

  1. 全局表(广播表)

适用于基础数据表,如地区表、配置表等,数据量小且更新频率低。在所有分库中冗余存储全量数据,避免跨库JOIN。

  1. 中间件处理(推荐)

使用ShardingSphere或MyCAT等中间件,支持SQL分片执行和结果合并。优点是业务代码无需修改,中间件完成分库分表逻辑。

  1. 手动分片查询

按分片逐一查询数据,在业务层合并结果。例如聚合查询需要跨多个分片表执行,通过程序实现数据汇总。

分布式事务一致性保障

  1. 分布式事务协议(2PC/3PC)

协调者统一管理各节点事务提交/回滚,保证强一致性,但存在同步阻塞和性能问题。

  1. 消息队列异步处理

业务操作与消息发送绑定,通过MQ异步通知其他服务,实现最终一致性。需处理消息可靠性和幂等性。

  1. TCC补偿模式

业务层定义Try、Confirm、Cancel接口,通过预留资源+提交/回滚实现柔性事务。灵活性强但代码复杂度高。

  1. 可靠事件队列

本地事务完成后发布事件,消费者处理后更新状态,依赖事件持久化和重试保证最终一致。

  1. 对账修复机制

允许短期不一致,通过定期核对数据并修正差异,适合对实时性要求低的场景。

在实际应用中,需要根据业务场景选择合适的方案,权衡强一致性和最终一致性之间的取舍。


Redis如何实现分布式锁?有哪些坑?

Redis 实现分布式锁的核心原理基于其单线程特性和原子性命令,主要通过 SET key value NX EX timeout 命令实现。具体来说,加锁时使用 SET 命令的 NX(Not Exists)参数确保只有当键不存在时才能设置成功,从而保证互斥性;同时使用 EX 参数设置过期时间,防止因客户端崩溃导致锁无法释放。

核心实现方式

  1. 基础实现(SETNX + EXPIRE)

    加锁‌:使用 SETNX key value 设置锁,再用 EXPIRE key timeout 设置过期时间

    解锁‌:使用 DEL key 删除锁

  2. Lua脚本原子化操作

为避免加锁和设置过期时间的非原子操作问题,推荐使用 Lua 脚本。通过 SET key value NX PX timeout 命令,将加锁和设置过期时间合并为一个原子操作。

  1. Redisson实现

Redisson 提供了更完善的分布式锁实现,包含自动续期、可重入性等功能。其核心是通过 Lua 脚本保证加锁和解锁的原子性,并提供看门狗机制自动续期。

常见问题与解决方案

  1. 非原子操作问题

基础实现中,SETNX 和 EXPIRE 是两个独立操作,可能导致锁无法自动释放。解决方案是使用 SET 命令的原子操作参数。

  1. 锁超时与死锁

如果持有锁的客户端崩溃,锁可能无法释放。通过设置合理的过期时间来避免死锁。

  1. 主从切换丢锁

在 Redis 主从架构中,主节点故障时可能丢失锁。解决方案包括使用 Redlock 算法或 Redisson 的高可用方案。

  1. 锁误释放

不同客户端可能误删其他客户端持有的锁。通过为锁设置唯一标识(如 UUID)并在解锁时验证标识。

  1. 可重入性问题

同一线程多次获取同一把锁时,基本实现不支持重入。Redisson 提供了可重入锁支持。

  1. 网络分区风险

网络分区可能导致锁状态不一致。建议结合业务校验机制和缩短锁过期时间。

最佳实践建议

优先使用成熟框架‌:如 Redisson,它封装了解决上述问题的完整方案。

合理设置过期时间‌:略大于业务最长执行时间,避免频繁续期。

使用 Lua 脚本‌:保证操作的原子性。

实现续期机制‌:防止业务执行时间超过锁过期时间。

考虑高可用性‌:在重要业务场景使用 Redlock 算法或 ZooKeeper。

通过以上方式,可以构建一个稳定可靠的 Redis 分布式锁系统。


这类问题重在考察‌分析思路‌,即使答案不完整,只要逻辑清晰、有分层意识(如限流→削峰→异步→容错),也能获得认可。

  1. ‌项目经历深挖‌

面试官会针对简历中的项目逐一提问,重点包括:

项目架构说明:使用了哪些技术?为什么选择这些技术?

技术挑战与解决方案:遇到过什么性能瓶颈?如何优化?

团队协作与流程管理:Git分支策略?是否为客户维护多个版本?

建议使用‌STAR法则‌(Situation-Task-Action-Result)结构化表达,突出个人贡献与结果量化。

三、面试建议与应对策略

不要硬答不会的问题‌

遇到不了解的技术点,坦诚说明“目前没有实践经验,但我的理解是……”,展现学习意愿和基础认知,比强行回答更稳妥。

突出工程落地能力‌

面试官更看重“能不能解决问题”,而非“能不能背概念”。例如谈到线程池调优,可以说:“我在项目中通过监控CPU和GC情况,调整核心线程数和队列大小,将任务处理效率提升了30%”。

提前熟悉公司技术栈‌

领星使用Spring Boot + MyBatis Plus + 分布式架构,建议重点复习相关框架原理、微服务治理、API对接设计等内容。

保持良好心态,适当表达思考‌

即使紧张,也要尽量条理清晰地表达思路。面试不仅是考核,也是双向交流的机会。

结合领星的网络面试经验分享,帮我总结一些面试题目

领星Java开发工程师的面试题目通常围绕‌技术深度‌、‌项目实战能力‌与‌系统设计思维‌展开,结合网络上的真实面试经验(如牛客网、CSDN、知乎等平台分享),以下是根据社招场景(1.5年+经验)总结出的高频面试题目分类与典型问题,帮助你精准准备。

一、Java 基础(重理解,非死记硬背)

String、StringBuilder、StringBuffer 三者区别?底层实现有何不同?

HashMap 的数据结构?JDK 8 中如何解决哈希冲突?为什么链表长度超过8会转红黑树?

ConcurrentHashMap 如何保证线程安全?JDK 7 与 JDK 8 实现有何本质区别?

synchronized 的锁升级过程是怎样的?偏向锁、轻量级锁、重量级锁分别在什么场景下触发?

volatile 关键字的作用是什么?它能保证原子性吗?如何结合 CAS 实现线程安全?

ThreadLocal 的原理和使用场景?有哪些内存泄漏风险?如何避免?

final、finally、finalize 的区别?

接口和抽象类的区别?Java 8 接口中可以有默认方法吗?

二、多线程与并发编程(突出实战应用)

创建线程的几种方式?Runnable 与 Callable 有何区别?

线程池的核心参数有哪些?如何合理设置核心线程数与最大线程数?

ThreadPoolExecutor 的工作流程是怎样的?任务队列满了之后会发生什么?

常见的线程池类型有哪些?Executors.newFixedThreadPool() 为什么不推荐在生产环境使用?

CountDownLatch 和 CyclicBarrier 的使用场景有何不同?

ReentrantLock 是公平锁还是非公平锁?为什么默认是非公平锁?

如何排查死锁?程序中出现死锁后有哪些表现?

生产者消费者模型如何用代码实现?(可用 BlockingQueue 或 wait/notify)

三、JVM 与性能调优(侧重原理与调优实践)

JVM 内存模型包含哪些区域?各自的作用是什么?

对象一定分配在堆上吗?栈上分配和逃逸分析是什么?

垃圾回收算法有哪些?G1 收集器的特点是什么?相比 CMS 有哪些优势?

什么是类加载机制?双亲委派模型是什么?能否打破?什么时候需要打破?

如何判断一个对象是否可以被回收?四种引用类型分别是什么?

频繁 Full GC 如何排查?常用的 JVM 调优参数有哪些?(如 -Xms, -Xmx, -XX:NewRatio)

四、Spring 与 Spring Boot(聚焦原理与工程落地)

Spring 中的 IOC 和 AOP 是什么?底层是如何实现的?

Bean 的生命周期是怎样的?从创建到销毁经历了哪些步骤?

Spring 是如何解决循环依赖的?三级缓存分别存的是什么?

@Autowired 和 @Resource 有什么区别?

Spring Boot 的自动配置原理是什么?@SpringBootApplication 包含哪些注解?

@Transactional 在什么情况下会失效?事务传播机制有哪些?

如何实现多数据源配置?动态数据源切换如何实现?

Spring Cloud 中常用的组件有哪些?Nacos 的作用是什么?

五、数据库与 MyBatis(强调索引优化与事务控制)

MySQL 的索引类型有哪些?B+树索引为什么适合做数据库索引?

什么是事务的四大特性(ACID)?MySQL 如何实现隔离性?

什么是脏读、不可重复读、幻读?MySQL 默认的隔离级别是什么?

如何优化慢查询?执行计划中 type 字段有哪些值?哪个最好?

MyBatis 中 #{} 和 ${} 的区别?如何防止 SQL 注入?

MyBatis 一级缓存和二级缓存的区别?使用时需要注意什么?

分库分表的常见方案有哪些?如何处理跨表查询和分布式事务?

六、分布式与中间件(突出高并发与系统可靠性)

什么是 CAP 理论?分布式系统中如何权衡?

Redis 常见的数据类型有哪些?各自的应用场景?

如何用 Redis 实现分布式锁?需要注意哪些问题?(如锁过期、误删)

RocketMQ 如何保证消息不丢失?生产者、Broker、消费者各需做什么?

什么是服务雪崩?熔断和降级的区别是什么?

Zookeeper 的典型应用场景有哪些?如何实现分布式锁?

什么是分布式事务?Seata 的工作原理是什么?

七、项目与场景题(重点考察表达与思维)

请介绍你参与度最高的项目,画出架构图并说明技术选型原因。

项目中遇到过哪些性能瓶颈?你是如何定位和优化的?

如何设计一个支持高并发的订单系统?从接口防重、库存扣减、消息异步等方面说明。

如果系统出现 Full GC 频繁,你会如何排查和解决?

如何保证多个电商平台的数据一致性?遇到同步失败如何处理?

你在项目中是如何进行代码管理的?Git 分支策略是怎样的?是否支持多版本维护?