Moka-Java后端开发 二面 面经
1. 介绍一下你做过的最复杂的项目,架构是怎样的?
参考答案:我做过一个电商平台的订单系统,这是整个项目中最复杂的模块。系统采用微服务架构,订单服务是核心服务之一,与用户服务、商品服务、库存服务、支付服务、物流服务等多个服务交互。
架构设计:接入层用Nginx做负载均衡和反向代理,配置了限流规则。应用层用Spring Cloud构建微服务,包括订单服务、库存服务、支付服务等,用Nacos做服务注册发现和配置中心。服务间通信用Feign做HTTP调用,用Sentinel做限流降级熔断。
数据层:订单表按用户ID分库分表,用ShardingSphere做分库分表中间件,分了8个库,每个库16张表。用Redis做缓存,缓存热点订单数据、用户信息等。用RocketMQ做消息队列,处理订单创建、支付成功、发货等异步消息。
核心流程:用户下单时,先调用库存服务锁定库存,再创建订单,然后发送消息到支付服务。支付成功后,通过MQ通知订单服务更新状态,扣减库存,通知物流服务发货。整个流程用Saga模式保证分布式事务的最终一致性,每个步骤都有补偿操作。
技术难点:高并发下的库存超卖问题,用Redis+Lua脚本保证原子性。订单查询性能问题,用分库分表+缓存+ES搜索优化。分布式事务问题,用本地消息表+定时任务保证最终一致性。
2. 你们的系统QPS能达到多少?如何做压测和性能优化的?
参考答案:订单系统经过优化后,单机QPS能达到2000左右,集群部署10个节点,整体QPS能达到15000-20000。
压测方法:用JMeter做压测工具,模拟真实用户行为,包括浏览商品、加购物车、下单、支付等完整流程。设置不同的并发数(100、500、1000、2000),观察系统表现。压测时监控CPU、内存、网络、磁盘IO等系统指标,以及接口响应时间、错误率、数据库连接数等应用指标。
性能瓶颈定位:通过监控发现数据库CPU使用率高,慢查询多。用explain分析SQL,发现有的查询没走索引,有的join太多。Redis缓存命中率低,大量请求打到数据库。接口响应时间长,有的接口串行调用多个服务,耗时累加。
优化措施:数据库层面,给常用查询字段建索引,优化SQL语句,减少join操作。订单表分库分表,单表数据量从千万降到百万。开启慢查询日志,定期分析优化。
缓存层面:提高缓存命中率,预热热点数据。用布隆过滤器防止缓存穿透。用互斥锁防止缓存击穿。缓存过期时间加随机值,防止缓存雪崩。
应用层面:服务间调用改为异步,用CompletableFuture并行调用多个服务。用线程池管理线程,避免频繁创建销毁。用对象池复用对象,减少GC压力。
JVM调优:调整堆内存大小,年轻代老年代比例。选择合适的垃圾收集器,用G1替代CMS。调整GC参数,减少Full GC频率。
3. 如何设计一个分布式ID生成器?
参考答案:分布式ID需要满足:全局唯一、高性能、高可用、趋势递增(对数据库索引友好)。
常见方案:
数据库自增ID:用MySQL的auto_increment,简单但性能差,单点故障。可以用多个数据库,每个数据库设置不同的起始值和步长,如数据库1从1开始步长3,数据库2从2开始步长3。缺点是扩展性差,增加数据库需要调整步长。
UUID:全局唯一,性能好,但无序,不适合做数据库主键。字符串类型,占用空间大。
Redis INCR:用Redis的INCR命令生成ID,性能高,但依赖Redis,需要考虑持久化和高可用。
雪花算法(Snowflake):Twitter开源的算法,生成64位long型ID。结构:1位符号位(固定0)+ 41位时间戳(毫秒级,可用69年)+ 10位机器ID(支持1024台机器)+ 12位序列号(每毫秒可生成4096个ID)。优点是性能高、趋势递增、不依赖外部系统。缺点是依赖系统时钟,时钟回拨会导致ID重复。
我在项目中用的是改进的雪花算法:机器ID从配置中心获取,避免手动配置。时钟回拨时抛异常或等待,保证ID不重复。用位运算生成ID,性能极高,单机QPS可达百万级。
实现要点:用synchronized保证线程安全,或者用ThreadLocal为每个线程分配独立的序列号。序列号用完时等待下一毫秒。记录上次生成ID的时间戳,检测时钟回拨。
4. 讲讲你对MySQL索引的深入理解
参考答案:索引是提升查询性能的关键,但也有成本。
索引类型:B+树索引是最常用的,支持范围查询和排序。哈希索引只支持等值查询,不支持范围查询,InnoDB不支持显式创建哈希索引。全文索引用于文本搜索,性能不如Elasticsearch。
索引分类:主键索引(聚簇索引),叶子节点存完整行数据。辅助索引(二级索引),叶子节点存主键值,查询需要回表。联合索引,多个字段组合的索引,遵循最左前缀原则。覆盖索引,索引包含所有查询字段,不需要回表。
索引优化:选择区分度高的字段建索引,如用户ID比性别更适合。联合索引的字段顺序很重要,区分度高的放前面,常用查询条件放前面。索引不是越多越好,每个索引都会占用空间,增加写入成本。定期分析索引使用情况,删除无用索引。
索引失效场景:在索引列上使用函数或表达式。类型不匹配导致隐式转换。LIKE以%开头。OR连接的条件有字段没索引。不等于、NOT IN通常不走索引。联合索引不满足最左前缀。
索引设计原则:频繁查询的字段建索引。WHERE、ORDER BY、GROUP BY的字段建索引。区分度低的字段不建索引,如性别。字符串字段可以用前缀索引,节省空间。合理使用联合索引,避免索引冗余。
实际案例:订单表有user_id、status、create_time三个常用查询字段,建立联合索引(user_id, status, create_time)。查询WHERE user_id=1 AND status=1 ORDER BY create_time可以完全用到索引,不需要filesort。
5. Redis集群方案有哪些?各有什么优缺点?
参考答案:Redis集群方案主要有主从复制、哨兵模式、Cluster集群三种。
主从复制:一个主节点,多个从节点,主节点负责写,从节点负责读。主节点数据自动同步到从节点。优点是读写分离,提升读性能。缺点是主节点单点故障,需要手动切换。不支持自动故障转移。
哨兵模式(Sentinel):在主从复制基础上增加哨兵节点,监控主从节点健康状态。主节点故障时,哨兵自动选举新的主节点,实现故障转移。优点是高可用,自动故障转移。缺点是不支持水平扩展,所有数据都在主节点。哨兵本身也需要集群部署,增加复杂度。
Cluster集群:官方的分布式方案,数据分片存储在多个节点。用哈希槽(16384个)分配数据,每个节点负责一部分槽。支持主从复制,每个主节点可以有多个从节点。优点是水平扩展,支持海量数据。高可用,自动故障转移。缺点是不支持多键操作(如MGET),除非key在同一个槽。客户端需要支持Cluster协议。运维复杂,需要管理多个节点。
选择建议:数据量小、并发不高,用主从复制。需要高可用但数据量不大,用哨兵模式。数据量大、需要水平扩展,用Cluster集群。
我在项目中用的是哨兵模式,1主2从3哨兵,满足高可用需求。数据量不大,单机能支撑,不需要分片。
6. 如何解决缓存穿透、缓存击穿、缓存雪崩问题?
参考答案:这是缓存使用中的三大经典问题。
缓存穿透:查询不存在的数据,缓存和数据库都没有,每次请求都打到数据库。解决方案:布隆过滤器,在缓存前加一层,判断数据是否存在,不存在直接返回。缓存空值,查询不到数据时,缓存一个空值,过期时间设短一点。参数校验,在接口层校验参数合法性,拦截非法请求。
缓存击穿:热点数据过期,大量请求同时打到数据库。解决方案:互斥锁,第一个请求获取锁,查数据库并更新缓存,其他请求等待。用Redis的SETNX实现分布式锁。热点数据永不过期,在缓存中设置逻辑过期时间,后台线程异步更新。提前预热,系统启动时加载热点数据到缓存。
缓存雪崩:大量缓存同时过期,请求全部打到数据库。解决方案:过期时间加随机值,避免同时过期。如原本过期时间30分钟,加上0-5分钟的随机值。用Redis集群,提高可用性,避免Redis宕机导致所有缓存失效。限流降级,数据库扛不住时,限制请求数量,保护数据库。多级缓存,本地缓存+Redis缓存,Redis挂了还有本地缓存。
实际应用:我在项目中用布隆过滤器防止缓存穿透,用互斥锁防止缓存击穿,过期时间加随机值防止缓存雪崩。同时配置了限流规则,数据库压力过大时触发降级。
7. 讲讲JVM的类加载机制和双亲委派模型
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
Java面试圣经,带你练透java圣经
查看12道真题和解析