Bootstrap

【面试题】 Java 三年工作经验(2025)

问题列表

  1. 为什么选择 spring boot 框架,它与 Spring 有什么区别?
  2. spring mvc 的执行流程是什么?
  3. 如何实现 spring 的 IOC 过程,会用到什么技术?
  4. spring boot 的自动化配置的原理是什么?
  5. 如何理解 spring boot 中的 star?
  6. spring boot 为什么要用嵌入式的 web 容器?
  7. 利用 REDIS 存储数据的原则是什么?
  8. 如果 dB 的数据发生变更,如何更新缓存内的数据?
  9. 如果流量很大,REDIS 挂了会发生什么,采取的措施是什么,有什么兜底的方案?
  10. 造成 REDIS 挂掉的原因有哪些?
  11. 什么是数据的倾斜,如何预防数据的倾斜?
  12. REDIS6.0 的多线程是怎样的?
  13. 有没有遇到过 REDIS 突然变慢的情况,原因是什么?
  14. REDIS 的内存碎片是怎么造成的?
  15. REDIS 是如何支持原子操作的?
  16. 在订单每天都是百万级别的数据量且超过 30 分钟未支付系统自动取消订单的场景下,如何设计把订单的状态改为取消?
  17. 订单系统中 MQ 的具体使用场景是什么?
  18. 如何保证 MQ 消息的幂等?
  19. rocket mq 是如何保证消息不丢失的?有没有做过相关设置?
  20. 微服务拆分的原则是什么?
  21. 微服务中分布式事务的具体解决的方案是什么?采用的 Seata 是用的哪种模式,其执行过程是什么?这种模式下的隔离级别和 MYSQL 的隔离级别有什么不一样,会不会有什么问题,如何解决?
  22. 公司的 SQL 语句规范是什么?表在建立索引的时候有什么原则?
  23. 为什么不建议用 uuid 作为 MYSQL 的主键索引?
  24. select 语句的查询过程是什么,如何预防回表?
  25. 在线上给一个小表加了个字段结果导致整个库都挂了,原因是什么,如何解决?
  26. MYSQL 中是如何出现死锁的,在设计表的时候如何减少锁的冲突?
  27. 单点登录系统是用什么协议来实现的?如何解决 token 或 session 的跨域问题?
  28. 如何设计和实现微信扫码登录的功能?

面试题答案

Spring相关

  1. 为什么选择Spring Boot框架,它与Spring有什么区别
    • 选择原因:快速构建项目,简化配置,有大量starter依赖,自动配置减少开发工作量,内置服务器便于部署和运行。
    • 区别:Spring是一个框架体系,需大量配置;Spring Boot基于Spring,以约定大于配置为核心,简化配置,更易上手和快速开发。
  2. Spring MVC的执行流程
    • 用户发送请求到DispatcherServlet。
    • DispatcherServlet查询HandlerMapping找到对应的Handler。
    • 调用HandlerAdapter执行Handler。
    • Handler执行完成返回ModelAndView给DispatcherServlet。
    • DispatcherServlet通过ViewResolver解析视图并渲染,将结果返回给用户。
  3. 如何实现Spring的IOC过程,会用到什么技术
    • 实现方式:通过配置文件或注解定义Bean,Spring容器负责创建和管理Bean。
    • 技术:反射用于创建对象,依赖注入通过构造函数、Setter方法等实现。
  4. Spring Boot的自动化配置的原理
    • 基于条件注解,根据类路径下是否存在某些类、Bean等条件,决定是否自动配置某个功能。通过@ConfigurationProperties绑定配置属性,EnableAutoConfiguration开启自动配置,读取META-INF/spring.factories文件中的自动配置类进行加载和配置。
  5. 如何理解Spring Boot中的starter
    • Starter是一组依赖描述符,将相关的依赖和配置整合在一起。用户只需引入starter依赖,Spring Boot就能自动配置相关功能,简化依赖管理和配置,如spring-boot-starter-web引入后可快速搭建Web项目。
  6. Spring Boot为什么要用嵌入式的web容器
    • 便于快速部署和运行,无需单独安装和配置外部Web容器。可将应用打包成可执行的jar或war文件,直接运行,提高开发和部署效率,便于在不同环境中快速迁移和部署。

Redis相关

  1. 利用REDIS存储数据的原则是什么

    • 数据读写频繁、对读写速度要求高的适合存Redis。如缓存热点数据、实时统计数据、分布式锁等。要考虑数据的生命周期,合理设置过期时间,避免内存占用过多。
  2. 如果DB的数据发生变更,如何更新缓存内的数据

    • 采用缓存更新策略,有先更新数据库再更新缓存、先删除缓存再更新数据库、先更新数据库再异步删除缓存等方式。根据业务场景选择合适策略,注意数据一致性问题。
  3. 如果流量很大,REDIS挂了会发生什么,采取的措施是什么,有什么兜底的方案

    • 发生情况:缓存失效,大量请求直接访问数据库,可能导致数据库压力过大甚至崩溃,系统响应变慢,部分业务可能无法正常处理。
    • 措施:采用Redis集群,增加节点提高可靠性和性能;开启持久化,保证数据不丢失。
    • 兜底方案:使用本地缓存做临时过渡,对数据库进行限流、降级等操作,保证核心业务可用。
  4. 造成REDIS挂掉的原因有哪些

    • 内存不足,超出最大内存限制。网络故障,节点之间通信异常。大量请求导致CPU使用率过高。持久化出现问题,如磁盘空间满等。软件或硬件故障等。
  5. 什么是数据的倾斜,如何预防数据的倾斜

    • 定义:数据在Redis集群节点中分布不均匀,部分节点数据量过大,部分节点数据量很少,导致负载不均衡。
    • 预防:合理设计数据分片策略,采用一致性哈希等算法。对数据进行预处理,将数据均匀分布。监控数据分布情况,及时调整。
  6. REDIS6.0的多线程是怎样的

    • Redis 6.0引入多线程主要用于网络I/O读写,提高网络读写性能。多线程负责读取请求、解析命令、发送响应等操作,数据处理仍由单线程执行,避免了多线程数据竞争问题,通过配置参数可调整线程数量等。
  7. 有没有遇到过REDIS突然变慢的情况,原因是什么

    • 内存不足,触发内存淘汰策略,导致数据读写变慢。有大量复杂的慢查询命令,阻塞了Redis线程。网络带宽不足或网络延迟高。持久化操作频繁,影响了读写性能。CPU负载过高,忙于处理其他任务。
  8. REDIS的内存碎片是怎么造成的

    • 内存分配器分配和释放内存时,由于分配的内存块大小和实际使用的内存大小不一致,多次分配和释放后就会产生内存碎片。例如,频繁地创建和删除不同大小的键值对,会导致内存空间不连续,形成碎片。
  9. REDIS是如何支持原子操作的

    • Redis通过单线程和命令队列来实现原子操作。单线程保证了同一时间只有一个命令在执行,不会被其他命令打断。命令队列则确保命令按照顺序依次执行,在执行一个命令的过程中不会插入其他命令,从而保证了原子性。

订单系统相关

  1. 在订单每天都是百万级别的数据量且超过30分钟未支付系统自动取消订单的场景下,如何设计把订单的状态改为取消
    • 可以使用Redis的有序集合,以订单创建时间加上30分钟作为score,订单ID作为member。定时任务定期扫描有序集合,取出当前时间超过score的订单ID,去数据库中更新订单状态为取消。也可以使用消息队列延迟消息功能,在订单创建时发送一个30分钟后的延迟消息,消息到达时更新订单状态。
  2. 订单系统中MQ的具体使用场景是什么
    • 订单创建后,发送消息到MQ通知库存系统扣减库存。通知支付系统进行支付处理。订单状态变更时,通知相关系统更新状态,如通知物流系统订单已支付待发货等。实现异步解耦,提高系统的稳定性和性能。
  3. 如何保证MQ消息的幂等
    • 在消息中添加唯一标识,消费者在处理消息前先检查是否已处理过该标识的消息。利用数据库的唯一索引,对消息中的关键信息建唯一索引,插入时若冲突则表示重复消息,不做处理。使用状态机,记录消息处理的状态,根据状态决定是否处理消息。

RocketMQ相关

  1. rocket mq是如何保证消息不丢失的?有没有做过相关设置
    • 生产者发送消息时采用同步发送方式,并设置合理的重试次数。Broker配置刷盘策略为同步刷盘,保证消息及时持久化到磁盘。消费者采用手动提交偏移量,处理完消息后再提交,防止消费过程中丢失消息。设置相关参数如unflushDelayMills控制刷盘时机等。

微服务相关

  1. 微服务拆分的原则是什么
    • 单一职责原则,每个微服务只负责一项主要功能。高内聚低耦合,微服务内部功能紧密相关,微服务之间耦合度低。业务边界清晰,按业务模块进行拆分。可扩展性原则,便于独立扩展和维护。数据独立,每个微服务有自己独立的数据库或数据存储。
  2. 微服务中分布式事务的具体解决的方案是什么?采用的Seata是用的哪种模式,其执行过程是什么?这种模式下的隔离级别和MYSQL的隔离级别有什么不一样,会不会有什么问题,如何解决
    • 解决方案:有两阶段提交、TCC、本地消息表、Seata等。
    • Seata模式:以AT模式为例,执行过程是开启全局事务,业务操作时Seata拦截SQL,记录数据的前后镜像,提交时协调各分支事务提交,若失败则根据镜像回滚。
    • 隔离级别差异:Seata AT模式默认读未提交,MySQL默认可重复读。Seata可能存在脏读问题。
    • 问题及解决:可通过在业务层控制事务边界,结合MySQL的锁机制等,必要时提高Seata隔离级别来解决。

数据库相关

  1. 公司的SQL语句规范是什么?表在建立索引的时候有什么原则
    • SQL规范:命名规范,表名、字段名等要有意义且遵循统一格式。语句格式化,缩进、换行规范。避免使用SELECT *,明确列出所需字段。合理使用事务等。
    • 索引原则:在经常用于查询条件、连接条件、排序和分组的字段上建立索引。区分度高的字段优先建索引。避免过多索引,考虑索引的维护成本。联合索引遵循最左前缀原则。

23. 为什么不建议用 uuid 作为 MYSQL 的主键索引?

  • 存储开销大:UUID 是 128 位,通常以 36 个字符的字符串形式存储,相比自增整数(如 INT 型 4 字节)占用更多存储空间,增加了索引文件大小。
  • 性能影响
    • 插入性能:UUID 无序,插入时会导致数据页频繁分裂,影响插入效率。而自增主键按顺序插入,数据页分裂频率低。
    • 查询性能:范围查询时,自增主键更具优势,因为其值连续,利于快速定位数据范围;UUID 无序,无法利用范围查询优化。

24. select 语句的查询过程是什么,如何预防回表?

  • 查询过程
    • 解析与预处理:MySQL 解析 SQL 语句,检查语法,生成解析树,并进行预处理,如权限检查、表名和列名解析。
    • 优化器:分析查询语句,考虑多种执行方案,如索引使用、表连接顺序等,基于成本模型选择最优执行计划。
    • 执行器:按照执行计划调用存储引擎 API,获取数据并返回给客户端。
  • 预防回表
    • 覆盖索引:查询列包含在索引中,避免回表操作。例如,查询 SELECT col1, col2 FROM table WHERE col3 = 'value',若建立 (col3, col1, col2) 复合索引,可利用覆盖索引直接获取结果。
    • 减少不必要查询列:仅查询索引列可避免回表。

25. 在线上给一个小表加了个字段结果导致整个库都挂了,原因是什么,如何解决?

  • 原因
    • 锁表问题:在添加字段操作时,MySQL 可能会使用表锁,长时间占用表资源,导致其他读写操作被阻塞,若有大量并发请求,可能拖垮整个数据库。
    • 磁盘空间不足:添加字段可能导致表结构变更,需要额外磁盘空间。若磁盘空间已满,操作失败并可能影响数据库其他功能。
  • 解决方法
    • 在线 DDL 工具:使用工具如 pt-online-schema-changegh-ost,它们通过创建临时表、复制数据等方式,实现无锁或低锁的表结构变更。
    • 提前检查磁盘空间:在操作前确保有足够磁盘空间。

26. MYSQL 中是如何出现死锁的,在设计表的时候如何减少锁的冲突?

  • 死锁产生原因:两个或多个事务在获取资源时,互相等待对方释放已占有的资源,形成循环等待,导致死锁。例如,事务 A 持有锁 L1 并请求锁 L2,事务 B 持有锁 L2 并请求锁 L1。
  • 减少锁冲突的表设计方法
    • 合理设计索引:确保经常用于查询条件的字段有索引,减少全表扫描,降低锁的粒度和时间。
    • 优化事务顺序:按照固定顺序访问资源,避免循环等待。例如,所有事务都先访问表 A 再访问表 B。
    • 降低事务隔离级别:在满足业务需求前提下,适当降低事务隔离级别,减少锁的持有时间。

27. 单点登录系统是用什么协议来实现的?如何解决 token 或 session 的跨域问题?

  • 常用协议
    • CAS(Central Authentication Service):简单的单点登录协议,客户端通过 CAS 服务器认证,服务器返回票据,客户端凭票据访问其他应用。
    • OAuth(开放授权):用于授权第三方应用访问用户资源,用户在授权服务器认证后,第三方应用获取授权令牌访问资源。
    • SAML(Security Assertion Markup Language):基于 XML 的标准,用于在不同安全域间交换认证和授权信息。
  • 解决跨域问题
    • Token 跨域:通过在响应头设置 Access - Control - Allow - Origin 允许跨域访问,前端在请求时携带 Token。也可使用 JSONP 或 CORS 代理方式传递 Token。
    • Session 跨域:使用 JSONP 传递 Session ID,或通过设置代理服务器,在代理服务器上维护统一的 Session 存储,实现跨域共享 Session。

28. 如何设计和实现微信扫码登录的功能?

  • 设计思路
    • 前端:展示扫码登录按钮,调用微信提供的扫码登录接口,显示二维码。
    • 后端:接收微信服务器回调,验证回调信息,生成用户登录状态,返回登录结果给前端。
  • 实现步骤
    • 注册应用:在微信开放平台注册应用,获取 AppID 和 AppSecret。
    • 前端生成二维码:前端调用微信 JS - SDK 生成带有唯一标识(如 state 参数)的二维码,展示给用户。
    • 用户扫码:用户使用微信扫码,微信服务器将扫码信息发送到后端服务器。
    • 后端验证:后端接收微信回调,使用 AppID 和 AppSecret 验证回调信息合法性,获取用户信息。
    • 生成登录状态:后端生成用户登录状态(如 Token),存储用户登录信息,返回登录结果给前端。
    • 前端处理:前端根据后端返回结果,处理登录成功或失败逻辑。

在Redis集群中使用Lua锁

可能会存在以下一些问题

一致性问题
  • 数据同步延迟:Redis集群采用异步复制机制,主节点将数据复制到从节点存在一定延迟。当使用Lua脚本获取锁时,若主节点在未完成数据同步到从节点的情况下出现故障,新的主节点可能没有锁的相关数据,导致锁的一致性被破坏,出现多个客户端同时获取到锁的情况。
  • 网络分区影响:在网络分区场景下,集群可能会被分割成多个子集群。若获取锁的客户端所在子集群与持有锁的主节点所在子集群隔离,可能会导致客户端无法正确判断锁的状态,出现误判获取锁成功的情况,进而引发数据不一致等问题。
性能问题
  • 脚本执行阻塞:Redis是单线程处理命令,Lua脚本在执行时会阻塞其他命令的执行。如果Lua锁脚本执行时间过长,会影响Redis对其他请求的处理能力,降低整个系统的性能和响应速度。
  • 集群资源消耗:在集群环境中,执行Lua脚本需要在多个节点之间协调和同步,这会增加网络开销和节点的计算资源消耗。大量使用Lua锁可能导致集群资源紧张,影响整体性能。
复杂性问题
  • 脚本编写难度:编写正确、高效且能在集群环境中稳定运行的Lua锁脚本具有一定难度。需要开发者对Redis集群的工作原理、数据一致性模型以及Lua脚本的特性有深入理解,否则容易出现逻辑漏洞和错误。
  • 维护和调试困难:由于Lua锁脚本在集群中的执行涉及多个节点和复杂的网络交互,出现问题时定位和解决问题的难度较大。例如,当锁出现异常释放或无法获取的情况时,很难快速确定是脚本逻辑问题、网络问题还是节点故障等原因导致。
兼容性问题
  • 版本差异:不同版本的Redis对Lua脚本的支持可能存在差异,某些特性或行为可能在不同版本中有所变化。在使用Lua锁时,可能会因为Redis版本升级或集群中节点版本不一致等原因,导致锁的行为不符合预期。
  • 与其他模块冲突:如果Redis集群中还使用了其他模块或扩展,可能会与Lua锁产生兼容性问题。例如,某些模块可能会修改Redis的命令执行逻辑或数据结构,影响Lua锁的正常工作。

如何处理Redis中大key

大key的检测
  • 使用Redis命令:可以使用MEMORY USAGE命令来获取键的内存占用情况,从而找出大key。例如MEMORY USAGE key可以返回key的内存占用字节数。还可以结合SCAN命令,遍历所有键,对每个键执行MEMORY USAGE来全面检测大key。
  • 借助Redis工具:利用redis-cli--bigkeys选项,它可以快速扫描并找出内存中占用空间较大的键。也可以使用Redis的可视化工具,如RedisInsight、RedisDesktopManager等,这些工具通常提供了直观的界面来展示键的大小和分布情况,方便查找大key。
优化存储结构
  • 对象编码优化:Redis的不同数据类型有不同的编码方式,例如hash类型如果字段较少且值较小,使用ziplist编码会更节省空间。可以通过OBJECT ENCODING命令查看键的编码方式,并考虑是否可以通过调整数据结构或使用合适的命令来优化编码。比如,对于一个包含大量小字段的hash,可以使用HMSET而不是逐个HSET来插入数据,这样可能会使Redis采用更紧凑的编码。
  • 使用压缩:对于值是字符串类型的大key,如果内容可压缩,可以在客户端对数据进行压缩后再存储到Redis中,获取数据时再进行解压缩。可以使用gzipzlib等压缩库来实现。不过要注意压缩和解压缩会带来一定的CPU开销,需要根据实际情况权衡。
数据拆分
  • 按数据特征拆分:如果大key是一个hashlist等集合类型,可以根据数据的业务特征将其拆分成多个小key。例如,一个包含用户所有订单信息的大hash,可以按照订单时间或订单类型等维度拆分成多个hash,每个hash只包含部分订单信息。
  • 使用Redis集群:将大key的数据分布到多个Redis节点上,通过集群的方式来分散存储和访问压力。可以使用Redis的分片集群,如Redis Cluster,它会根据键的哈希值将数据分布到不同的节点上。在使用Redis Cluster时,需要合理设计键的命名规则,确保相关数据能够均匀分布在各个节点上。
过期策略和淘汰机制
  • 设置合理过期时间:对于一些时效性较强的大key,设置合适的过期时间,让Redis自动删除过期的大key,以释放内存空间。比如缓存类的大key,根据其业务场景设置几分钟到几小时不等的过期时间。
  • 调整淘汰策略:根据业务需求调整Redis的内存淘汰策略。例如,设置为volatile-lru(对设置了过期时间的键采用LRU算法淘汰)或allkeys-lru(对所有键采用LRU算法淘汰),让Redis在内存不足时优先淘汰不常用的大key。也可以结合maxmemory配置项,合理设置Redis的最大内存使用量,触发淘汰机制。
;