面试的本质是一场开卷考试,永远不知道下一场会考什么,但通过持续、高质量的复盘,可以让自己的“知识库”越来越厚,直到应对任何问题都游刃有余。
本文将对京东美团的面试考点进行一次复盘,只有面对自己的不足才能更好的突破自己,祝我复盘有得,早日拿到心仪的Offer!

| 类型 | 京东 | 美团 |
|---|---|---|
| 自我介绍 | - | - |
| 项目介绍 | - | - |
| 分库分表 | 根据什么算法进行的分库分表 | 分库分表都需要注意哪些 |
| 根据业务id与日期如何分片的 | ||
| 线程池 | 为什么自研动态线程池组件 | |
| 为什么要使用动态线程池 | ||
| 自研线程池中为什么采用区间线程数 相比于传统动态线程池,它的优势是什么 | ||
| JVM | 如何进行调优的 | |
| 描述一下调优的场景与效果 | ||
| GC 指标是怎样的,调优后的指标是怎样的 | ||
| QPS 是否有提升,对接口的响应有没有提升 | ||
| SQL | 慢SQL是如何处理的 | SQL 是如何进行优化 |
| SQL 是如何优化的 | SQL 中索引覆盖的效果 | |
| 具体的调优案例 | ||
| 场景设计 | 系统的从0到1,是如何设计的,需要注意哪些方面 | |
| 软技能 | 如何提效代码 Review | |
| 源码 | Mybatis 中的 Mapper 接口执行 SQL 的流程 | |
| Spring 中的事务的实现原理 | ||
| 算法 | 二分查找 |
从整体来看,我在面试前没有做好充足的准备,在此反思一下自己,应该从把握每一次机会
对于jb的岗位匹配度,应该提前进行关注,根据业务方向预测可能被问到的技术点,提前准备 场景案例
在自我介绍中,往往考察的是个人的陈述能力,对自我是否有一个清晰的认知,自信大方的去介绍自己
我需要突出自身的亮点,突出自己积极向上的一面
项目介绍中,需要考察自己对项目业务理解程度,以及在项目中的技术贡献,价值几何
我的回答:
我们公司主体是做光伏的建站发电的,主要是对建站的一站式管理,像“录单 → 踏勘 → 设计 → 签约 → 仓配 → 施工 → 结算 → 运维 → 清分”,前期主要负责“施工、结算、运维”模块,后期职责划分后主要负责“结算、清分”模块,在开发过程中也是遇到一些技术难点,像开源组件的动态线程池内存泄漏,然后自研动态线程池...巴拉巴拉;对结算模型的重构,结合“工厂+抽象模板”设计模式搭建结算三层领域架构模型,主要业务分为三层维度..... 巴拉巴拉;对运维的逆变器发电数据,使用分库分表... 巴拉巴拉;然后面试官开始了询问
考察分析:
对项目的准备是否充分,是否真正的了解自己所做的项目
不足:
对项目的内容没有做到清晰的描述,,过多赘述技术方案上的实现,没有做好 埋点,导致面试官无从问起
改进:
项目部分,这是展示核心技术能力的舞台,要清晰的表达出你的 技术贡献 和 架构思维。
我们公司主体是做光伏的建站发电的,每一电站都创建一个订单,主要是对建站订单的一站式管理,订单的主要流程从 “录单(业务合伙人与电站用户信息确认进行信息录入) → 踏勘(有踏勘合伙人对屋顶进行勘探,测量长度宽度,障碍物等) → 设计(主要是建站的CAD制图与预估物料配置) → 签约(与电站用户签合同的环节) → 仓配(根据设计所需的物料,从仓库进行配货出库) → 施工(当施工队接货后进行施工环节) → 并网(与国家电网进行合闸) → 结算(施工、并网完成后,会根据不同阶段进行结算) → 运维(则是对电站逆变器发电后进行运维) → 清分(根据发电量收益进行清分) ”,以上是业务的主体流程。
在业务快速发展的过程中,也是遇到了一些挑战,像结算单生成的扩展性不足,结合设计模式进行的重构;使用开源动态线程池组件导致内存泄漏的问题,排查问题与自研轻量级的动态线程池组件;电站逆变器发电量数据过大,进行分库分表的扩展等。
在2分钟内过了业务的大流程,让面试官有个大概得了解后,开始进行埋点,描述在业务发展的过程中所遇到的问题与挑战。
这是重中之重,我结合面试的问题分模块详细梳理。
我的回答:
我是通过哈希的方式,对电站id进行取模的方式,为了防止数据的倾斜,采用时间+逆变器id进行复合分片,吧啦吧啦
考察分析:
为什么使用分库分表?
如何选择分片键(如你提到的电站id、时间、逆变器id)? 对分库分表常见算法的理解(如:哈希取模、范围分片、一致性哈希、复合分片等) 是否了解这些算法的优缺点和适用场景 分库分表会产生的问题以及如何解决的?
不足:
没有做好项目案例的准备,不能系统性的回答,没有先构建一个清晰的框架,对于分片算法掌握的广度不足
改进:
准备项目的案例,需要重新组织一下回答,使其更加结构化,用 STAR原则 陈述
我们每个电站下会有一到多个逆变器,而每个逆变器在光照后会进行发电的工作,每隔5分钟会传输一条发电量数据,预估一个逆变器一天产生120条数据,一年预计4w多条数据,而我们电站的数量是一个持续增长的过程,整体拆分成8个数据库,128个表,总计1024张表,截止到目前支撑存储83多亿的数据量;
分库分表中最核心的就是路由算法:
- 哈希取模:均匀分布,但扩容困难
- 一致性哈希:扩容友好,需配合虚拟节点
- 范围分片:适合范围查询,但要防止数据倾斜
- 配置表路由:灵活,但有单点瓶颈
对于分片键选择原则上,需要结合实际的业务场景:
- 数据分布要尽可能均匀
- 查询频次最高的字段
- 业务上根据电站查询多个逆变器
- 考虑业务增长模式
最终我们选择哈希取模的路由算法,采用电站ID作为数据库的分片键,保障了同一个电站下的逆变器在同一个库中。 对于数据表的分片,我们设计的分片方案结合了 哈希取模 和 动态再平衡,本质上是一种负载感知的动态再平衡策略。选择逆变器ID作为数据库表的分片键,确保同一个逆变器数据在同一张表中。核心路由使用哈希取模,确保同一个逆变器的数据在同一张表。同时,我们每天定时监控各分片的数据量,如果发现数据倾斜(20%的差异),则进行动态调整:将原本哈希到数据量多的分片的某些逆变器重新映射到数据量少的分片,并通过Redis记录这些异常映射(即非哈希取模的映射)。查询时,先查Redis是否有自定义映射,如果没有则使用哈希取模。
为了控制再平衡频率,避免频繁路由变更,我们也会计算出下次平衡时间,也就是当平衡完成+倾斜率需达到10%的天数日期作为判断。
STAR原则
我的回答:
原本我们是采用开源的动态线程池组件 DynamicTp v1.1.9 版本,后来产生了内存泄漏,所以开始的自研
考察分析:
项目中为什么采用动态线程池?对使用上的场景是否真正的理解,同时考察自研的线程池有什么优势?
不足:
没有体现出掌握的源码深度,有一定的功力才能自研,没有做好埋点
改进:
原本我们是采用开源的动态线程池组件 DynamicTp v1.1.9 版本中,后来发现其存在 ScheduledDtpExecutor 内存泄漏问题,通过增强机制会将 DtpRunnable 对象存储在功能性的 Map 结构中。
然而,ScheduledThreadPoolExecutor 在内部会将提交的任务包装成 ScheduledFutureTask 对象。这种包装导致了框架在执行 beforeExecute 和 afterExecute 钩子方法时,无法从 Map 中正确移除对应的任务对象,从而导致了内存泄漏的问题,因此我们决定自研动态线程池的组件,同时做了很多的扩展。
埋点
我的回答:
巴拉巴拉
考察分析:
为什么耗时自研线程池组件?比传统的线程池组件的优势是什么?
改进:
传统的动态线程池:需要人工观察监控指标 -> 判断需要调整 -> 修改配置 -> 推送生效。响应延迟高,依赖人工经验。无法根据系统真实负载自动弹性伸缩。
在自研的组件中,我们实现了 “线程数范围区间+自动扩容” 机制。
- 根据自动感知负载,当任务队列的阈值达到80%以后,计算最大线程数、任务执行速率是否足以支撑任务增长率,如果不满足则开始扩容,每次扩容区间差的一半,直到扩容到满足支撑任务增长率
- 线程数范围区间内智能调整,在预设的
[minThreads, maxThreads]范围内动态寻找最优值- 设置一个冷却期,在每次调整后的一段时间内不再调整,避免频繁调整造成系统不稳定,防止震荡
自研轻量级架构的优势:
- 避免复杂依赖,稳定性更高
- 基于工厂类获取线程池,无需 Bean 的注入,扩展性更强
- 无需人工干预,做到了响应更快
- 可根据不同业务线程池可以设置不同的弹性策略,自动选择最合适的线程数
我的回答:
我们使用的是jdk11,从jdk9开始默认的垃圾回收器是G1,其内存结构与jdk8已不一致,像jdk8的内存结构从物理上划分为年轻代,老年代,而G1则是划分为一块块的region区域,在逻辑上的承担年轻代,老年代的角色。而调优的思路主要分为低延迟与吞吐量。
考察分析:
查看你是否会真正的调优,根据不同的场景如何调优
不足:
不能一上来就根据调优的方向,设置JVM参数
改进:
我们通常不会进行过早的优化,但是当系统出现性能问题(如GC频繁、CPU负载高、响应慢)时,我们需要进行JVM调优。调优的目标通常是:降低GC的频率和停顿时间,提高吞吐量,避免内存泄漏和内存溢出。
我得调优思路是:
- 监控发现性能问题: 通过查看GC统计
jstat -gc <pid> 1000 10,查看堆内存信息jstat -gccapacity <pid>, 生成堆转储文件查看内存对象信息jcmd <pid> GC.heap_dump ./dump.hprof- 分析定位根本原因:开启GC日志进行分析
- 优化应用代码和架构:找到业务的大对象场景,进行优化
- 最后才是JVM参数调优
提示
先不要调优! 这是最重要的原则。调优应该是数据驱动、问题导向的,而不是盲目地进行。
我的回答:
在我看来慢sql优化主要🈶2个方向,一个是sql本身的优化,要符合小表驱动大表的方式,将筛选量多的条件优先处理,一种是索引上的优化,根据 explain 排查索引命中情况,
考察分析:
对 SQL 的优化掌握的是否全面
不足:
没有回答完整,且没有系统性的思路
改进:
SQL 的优化其实可以分为:SQL语句优化、索引优化、数据库结构优化和系统配置优化。
SQL 语句上的优化:
- 避免使用SELECT *
- 小表驱动大表
- 避免在WHERE子句中使用函数或表达式
- 注意数据类型转换
索引优化
- 注意索引顺序,最左原则
- 选择性更高的字段放左侧
- 使用覆盖索引
- 避免过度索引,索引会占用空间,并且降低写操作
- 定期检查索引使用情况,使用EXPLAIN分析查询,删除未使用的索引。
数据库结构优化
- 选择合适的数据类型,越小越好
- 使用NOT NULL,允许NULL的列需要额外的存储空间,并且查询时需要进行判断。
- 适当冗余,减少表关联,提高查询效率
系统配置优化
- 调整缓冲区大小,InnoDB的缓冲池(innodb_buffer_pool_size),通常设置为系统内存的70%-80%。
- 调整连接数,根据并发请求调整最大连接数(max_connections)。
除了以上的优化项外,发现慢SQL与索引命中分析也是重中之重
- 开启慢查询日志
- 使用EXPLAIN分析查询
案例:
关联查询订单的最后一次关单时间,从数百万的操作记录中筛选,响应时间越来越慢,影响系统吞吐量,sql如下
select ...,(select close_time from operate_log where order_id = order_id and work_type = xxx)as close_time from order_main where ....;
方式,从sql上优化:查询表数据量与查询条件,按照小表驱动大表的形式调整, 查看索引:查看创建的索引结构,根据数据分析通过 explain 查看索引命中情况,适当调整索引顺序或新建索引,从索引底层的B+树结构上来看,应该以做前缀作为查询的顺序; 索引失效:表字符集不一致,导致索引失效
我的回答:
考察分析:
是否真正的理解代码 Review,给团队带来提升
不足:
改进:
代码Review是保证代码质量的关键步骤,它可以帮助在代码集成之前发现问题,减少后续测试阶段和生产环境中的缺陷。同时,它也是团队技术交流和知识传递的好机会。
- 质量保证:发现bug、安全漏洞、性能问题
- 知识共享:团队成员互相学习,了解系统不同部分
- 规范统一:确保代码风格和架构一致性
- mentorship:帮助团队成员成长
我们在需求开发中采用 "双阶段Review"
- 设计Review:编码前的技术方案评审,确保技术方案合理,避免错误的设计决策
- 代码Review:编码完成后的实现细节评审
应该做的 ✅
- 对事不对人:评论代码,不评论人
- 提供具体建议:不只说"不好",要说"怎么改"
- 及时响应:在约定时间内完成Review
- 保持学习心态:把每次Review当作学习机会
写个二分查找,一个升序的数据,去查找是否存在某个值,如果存着,则返回下标,否则返回-1
我的回答:
考察分析:
算法的掌握程度
不足:
整体思路清晰,但是编码上的边界值控制没有把控好
改进:
脱离idea,手写算法
回答问题的思路:
如果是项目上的询问,在上面回答问题思路的基础上,继续描述:
本次面试暴露了我在项目案例准备上与手写算法上的不足,接下来本周,重点攻克分库分表、JVM调优实战的案例和脱离 idea 的手写算法。
本文作者:柳始恭
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!