问题列表
- 为什么选择 spring boot 框架,它与 Spring 有什么区别?
- spring mvc 的执行流程是什么?
- 如何实现 spring 的 IOC 过程,会用到什么技术?
- spring boot 的自动化配置的原理是什么?
- 如何理解 spring boot 中的 star?
- spring boot 为什么要用嵌入式的 web 容器?
- 利用 REDIS 存储数据的原则是什么?
- 如果 dB 的数据发生变更,如何更新缓存内的数据?
- 如果流量很大,REDIS 挂了会发生什么,采取的措施是什么,有什么兜底的方案?
- 造成 REDIS 挂掉的原因有哪些?
- 什么是数据的倾斜,如何预防数据的倾斜?
- REDIS6.0 的多线程是怎样的?
- 有没有遇到过 REDIS 突然变慢的情况,原因是什么?
- REDIS 的内存碎片是怎么造成的?
- REDIS 是如何支持原子操作的?
- 在订单每天都是百万级别的数据量且超过 30 分钟未支付系统自动取消订单的场景下,如何设计把订单的状态改为取消?
- 订单系统中 MQ 的具体使用场景是什么?
- 如何保证 MQ 消息的幂等?
- rocket mq 是如何保证消息不丢失的?有没有做过相关设置?
- 微服务拆分的原则是什么?
- 微服务中分布式事务的具体解决的方案是什么?采用的 Seata 是用的哪种模式,其执行过程是什么?这种模式下的隔离级别和 MYSQL 的隔离级别有什么不一样,会不会有什么问题,如何解决?
- 公司的 SQL 语句规范是什么?表在建立索引的时候有什么原则?
- 为什么不建议用 uuid 作为 MYSQL 的主键索引?
- select 语句的查询过程是什么,如何预防回表?
- 在线上给一个小表加了个字段结果导致整个库都挂了,原因是什么,如何解决?
- MYSQL 中是如何出现死锁的,在设计表的时候如何减少锁的冲突?
- 单点登录系统是用什么协议来实现的?如何解决 token 或 session 的跨域问题?
- 如何设计和实现微信扫码登录的功能?
面试题答案
Spring相关
- 为什么选择Spring Boot框架,它与Spring有什么区别
- 选择原因:快速构建项目,简化配置,有大量starter依赖,自动配置减少开发工作量,内置服务器便于部署和运行。
- 区别:Spring是一个框架体系,需大量配置;Spring Boot基于Spring,以约定大于配置为核心,简化配置,更易上手和快速开发。
- Spring MVC的执行流程
- 用户发送请求到DispatcherServlet。
- DispatcherServlet查询HandlerMapping找到对应的Handler。
- 调用HandlerAdapter执行Handler。
- Handler执行完成返回ModelAndView给DispatcherServlet。
- DispatcherServlet通过ViewResolver解析视图并渲染,将结果返回给用户。
- 如何实现Spring的IOC过程,会用到什么技术
- 实现方式:通过配置文件或注解定义Bean,Spring容器负责创建和管理Bean。
- 技术:反射用于创建对象,依赖注入通过构造函数、Setter方法等实现。
- Spring Boot的自动化配置的原理
- 基于条件注解,根据类路径下是否存在某些类、Bean等条件,决定是否自动配置某个功能。通过@ConfigurationProperties绑定配置属性,EnableAutoConfiguration开启自动配置,读取META-INF/spring.factories文件中的自动配置类进行加载和配置。
- 如何理解Spring Boot中的starter
- Starter是一组依赖描述符,将相关的依赖和配置整合在一起。用户只需引入starter依赖,Spring Boot就能自动配置相关功能,简化依赖管理和配置,如spring-boot-starter-web引入后可快速搭建Web项目。
- Spring Boot为什么要用嵌入式的web容器
- 便于快速部署和运行,无需单独安装和配置外部Web容器。可将应用打包成可执行的jar或war文件,直接运行,提高开发和部署效率,便于在不同环境中快速迁移和部署。
Redis相关
-
利用REDIS存储数据的原则是什么
- 数据读写频繁、对读写速度要求高的适合存Redis。如缓存热点数据、实时统计数据、分布式锁等。要考虑数据的生命周期,合理设置过期时间,避免内存占用过多。
-
如果DB的数据发生变更,如何更新缓存内的数据
- 采用缓存更新策略,有先更新数据库再更新缓存、先删除缓存再更新数据库、先更新数据库再异步删除缓存等方式。根据业务场景选择合适策略,注意数据一致性问题。
-
如果流量很大,REDIS挂了会发生什么,采取的措施是什么,有什么兜底的方案
- 发生情况:缓存失效,大量请求直接访问数据库,可能导致数据库压力过大甚至崩溃,系统响应变慢,部分业务可能无法正常处理。
- 措施:采用Redis集群,增加节点提高可靠性和性能;开启持久化,保证数据不丢失。
- 兜底方案:使用本地缓存做临时过渡,对数据库进行限流、降级等操作,保证核心业务可用。
-
造成REDIS挂掉的原因有哪些
- 内存不足,超出最大内存限制。网络故障,节点之间通信异常。大量请求导致CPU使用率过高。持久化出现问题,如磁盘空间满等。软件或硬件故障等。
-
什么是数据的倾斜,如何预防数据的倾斜
- 定义:数据在Redis集群节点中分布不均匀,部分节点数据量过大,部分节点数据量很少,导致负载不均衡。
- 预防:合理设计数据分片策略,采用一致性哈希等算法。对数据进行预处理,将数据均匀分布。监控数据分布情况,及时调整。
-
REDIS6.0的多线程是怎样的
- Redis 6.0引入多线程主要用于网络I/O读写,提高网络读写性能。多线程负责读取请求、解析命令、发送响应等操作,数据处理仍由单线程执行,避免了多线程数据竞争问题,通过配置参数可调整线程数量等。
-
有没有遇到过REDIS突然变慢的情况,原因是什么
- 内存不足,触发内存淘汰策略,导致数据读写变慢。有大量复杂的慢查询命令,阻塞了Redis线程。网络带宽不足或网络延迟高。持久化操作频繁,影响了读写性能。CPU负载过高,忙于处理其他任务。
-
REDIS的内存碎片是怎么造成的
- 内存分配器分配和释放内存时,由于分配的内存块大小和实际使用的内存大小不一致,多次分配和释放后就会产生内存碎片。例如,频繁地创建和删除不同大小的键值对,会导致内存空间不连续,形成碎片。
-
REDIS是如何支持原子操作的
- Redis通过单线程和命令队列来实现原子操作。单线程保证了同一时间只有一个命令在执行,不会被其他命令打断。命令队列则确保命令按照顺序依次执行,在执行一个命令的过程中不会插入其他命令,从而保证了原子性。
订单系统相关
- 在订单每天都是百万级别的数据量且超过30分钟未支付系统自动取消订单的场景下,如何设计把订单的状态改为取消
- 可以使用Redis的有序集合,以订单创建时间加上30分钟作为score,订单ID作为member。定时任务定期扫描有序集合,取出当前时间超过score的订单ID,去数据库中更新订单状态为取消。也可以使用消息队列延迟消息功能,在订单创建时发送一个30分钟后的延迟消息,消息到达时更新订单状态。
- 订单系统中MQ的具体使用场景是什么
- 订单创建后,发送消息到MQ通知库存系统扣减库存。通知支付系统进行支付处理。订单状态变更时,通知相关系统更新状态,如通知物流系统订单已支付待发货等。实现异步解耦,提高系统的稳定性和性能。
- 如何保证MQ消息的幂等
- 在消息中添加唯一标识,消费者在处理消息前先检查是否已处理过该标识的消息。利用数据库的唯一索引,对消息中的关键信息建唯一索引,插入时若冲突则表示重复消息,不做处理。使用状态机,记录消息处理的状态,根据状态决定是否处理消息。
RocketMQ相关
- rocket mq是如何保证消息不丢失的?有没有做过相关设置
- 生产者发送消息时采用同步发送方式,并设置合理的重试次数。Broker配置刷盘策略为同步刷盘,保证消息及时持久化到磁盘。消费者采用手动提交偏移量,处理完消息后再提交,防止消费过程中丢失消息。设置相关参数如unflushDelayMills控制刷盘时机等。
微服务相关
- 微服务拆分的原则是什么
- 单一职责原则,每个微服务只负责一项主要功能。高内聚低耦合,微服务内部功能紧密相关,微服务之间耦合度低。业务边界清晰,按业务模块进行拆分。可扩展性原则,便于独立扩展和维护。数据独立,每个微服务有自己独立的数据库或数据存储。
- 微服务中分布式事务的具体解决的方案是什么?采用的Seata是用的哪种模式,其执行过程是什么?这种模式下的隔离级别和MYSQL的隔离级别有什么不一样,会不会有什么问题,如何解决
- 解决方案:有两阶段提交、TCC、本地消息表、Seata等。
- Seata模式:以AT模式为例,执行过程是开启全局事务,业务操作时Seata拦截SQL,记录数据的前后镜像,提交时协调各分支事务提交,若失败则根据镜像回滚。
- 隔离级别差异:Seata AT模式默认读未提交,MySQL默认可重复读。Seata可能存在脏读问题。
- 问题及解决:可通过在业务层控制事务边界,结合MySQL的锁机制等,必要时提高Seata隔离级别来解决。
数据库相关
- 公司的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-change
或gh-ost
,它们通过创建临时表、复制数据等方式,实现无锁或低锁的表结构变更。 - 提前检查磁盘空间:在操作前确保有足够磁盘空间。
- 在线 DDL 工具:使用工具如
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。
- Token 跨域:通过在响应头设置
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中,获取数据时再进行解压缩。可以使用
gzip
、zlib
等压缩库来实现。不过要注意压缩和解压缩会带来一定的CPU开销,需要根据实际情况权衡。
数据拆分
- 按数据特征拆分:如果大key是一个
hash
或list
等集合类型,可以根据数据的业务特征将其拆分成多个小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的最大内存使用量,触发淘汰机制。