企业总结
使命:技术为本,致力于成为全球最值得信赖的企业。
愿景:
近期:以供应链效率优化为核心,巩固自营电商和物流双轮驱动的竞争壁垒,推进AI客服、智能仓储的规模化落地;
中期:以京东云和工业品供应链服务为突破口,面向企业客户提供一站式数字化采购与供应链管理服务;
长期:成为全球最领先的以供应链为基础的技术与服务企业,让全球消费与产业供应链更高效、更可信赖。
面试题解析
1. 介绍你做过的一个最有挑战性的项目
【回答思路】
京东技术面非常注重工程能力的深度,要展示你对系统设计的全局思考,而非只会写业务代码。
1. 项目背景:规模、业务场景、你的角色。
2. 挑战定义:是技术挑战(高并发/数据一致性),还是工程挑战(跨团队协作/需求多变)?
3. 你的解法:核心思路和具体技术方案。
4. 结果与反思:量化数据 + 如果重来会有什么改变。
【回答示例】
我做过最有挑战性的项目是一个大学图书馆座位预约系统我做过最有挑战性的项目是一个**大学图书馆座位预约系统**(面向全校3万名学生)。
挑战点:开放预约时(每天早上8点整),3万学生同时涌入,系统出现严重的接口超时和座位超订问题——同一个座位被多人预约成功。
我的解决方案**我的解决方案**:
第一,前端限流:预约按钮点击后立即置灰(防重复提交),同时加入前端队列(最多500人同时进入预约流程);
第二,Redis原子扣减:将每个座位的可预约状态存入Redis(seat:available:A101 = 1),用Lua脚本原子执行"查询+扣减",保证同一座位只有一次成功:
if redis.call('GET', KEYS[1]) == '1' then
redis.call('SET', KEYS[1], '0')
return 1
end
return 0
第三,异步落库:Redis扣减成功后,将预约消息写入RabbitMQ,消费者异步写入MySQL,避免大量同步DB写入压垮数据库;
第四,补偿机制:MQ消费失败时(死信队列)触发自动重试,重试3次后人工告警。
结果:8点开放时并发峰值约3000 QPS,系统稳定运行,零超订,数据库QPS下降75%。
2. MySQL优化:一条慢SQL如何定位和优化?
【回答思路】
京东仓储/物流数据量巨大,MySQL优化是必考题,要给出完整的排查 → 优化流程。
1. 发现问题**发现问题**:慢查询日志、监控告警。
2. 定位问题**定位问题**:EXPLAIN分析执行计划,重点看type/key/rows字段。
3. 优化手段**优化手段**:索引优化、SQL改写、表结构调整、读写分离/分库分表。
【回答示例】
第一步:发现慢SQL
通过慢查询日志(long_query_time = 1通过慢查询日志(`long_query_time = 1`)或数据库监控工具(如Prometheus + Grafana)发现执行时间超过阈值的SQL。
第二步:EXPLAIN分析**第二步:EXPLAIN分析**
执行EXPLAIN SELECT 重点关注:
• typ:ALL(全表扫描,最差)→ index → range → ref → const(最优),目标是达到range及以上;
• key:是否用到了预期的索引;
• rows:预估扫描行数,越小越好;
• Extra:是否出现Using filesort(文件排序,意味着无法利用索引排序)或Using temporary(使用临时表)。
第三步:常见优化手段
1. 建/修正索引:根据WHERE/ORDER BY/JOIN条件设计联合索引,遵循最左前缀原则;
2. SQL改写:避免SELECT(减少数据传输)、用LIMIT限制返回行数、将子查询改为JOIN;
3. 覆盖索引:在索引上直接包含查询所需字段,避免回表;
4. 分页优化:深度分页LIMIT 10000, 10性能差,改为游标分页(WHERE id > last_id LIMIT 10);
5. 读写分离:将读请求路由到从库,降低主库压力。
3. 请设计一个"京东购物车"的后端数据结构
【回答思路】
系统设计题,考察对实际业务的工程化思维,要考虑登录/未登录状态、数据存储选型、高并发写入。
1. 需求分析:未登录用户(临时购物车)vs 登录用户(持久化购物车)。
2. 数据结构设计:用户ID + 商品SKU + 数量 + 价格快照(下单时价格可能变动)。
3. 存储选型:Redis(高频读写)+ MySQL(持久化)。
4. 合并逻辑:未登录购物车在登录后如何合并。
【回答示例】
需求拆分:
• 未登录用户:购物车存在浏览器本地(localStorage)或后端临时存储(以device_id为key);
• 登录用户:购物车持久化存储,多端同步。
数据结构设计:
Redis Hash结构:
key: cart:{user_id}
field: sku_id
value: {quantity, price_snapshot, add_time}
用Hash的原因:HSET/HGET/HDEL操作单个商品O(1),HGETALL获取全量购物车O(n),性能高。
MySQL作为持久化备份:
CREATE TABLE cart (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
sku_id BIGINT NOT NULL,
quantity INT NOT NULL,
price_snapshot DECIMAL(10,2),
created_at DATETIME,
updated_at DATETIME,
INDEX idx_user_id (user_id)
);
合并逻辑:用户登录后,将本地临时购物车中的商品逐一与服务端购物车合并(同一SKU数量相加),合并后删除临时购物车。
价格快照的意义:记录用户加入购物车时的价格,结算时与实时价格对比,展示"比加入时降价XX元"是促进转化的重要产品细节。
4. 算法题:合并K个升序链表
【回答思路】
京东笔试高频题,要掌握堆(优先队列)方案,时间复杂度O(nk log k)。
【回答示例】
public ListNode mergeKLists(ListNode[] lists) {
PriorityQueue<ListNode> heap = new PriorityQueue<>(
(a, b) -> a.val - b.val
);
for (ListNode node : lists) {
if (node != null) heap.offer(node);
}
ListNode dummy = new ListNode(-1);
ListNode cur = dummy;
while (!heap.isEmpty()) {
ListNode minNode = heap.poll();
cur.next = minNode;
cur = cur.next;
if (minNode.next != null) {
heap.offer(minNode.next);
}
}
return dummy.next;
}
// 时间复杂度 O(nk log k)
5. Redis的主从复制原理是什么?如何解决主从脑裂问题?
【回答思路】
京东Redis高可用必备知识,主从脑裂(split-brain)是生产环境的真实风险。
1. 主从复制原理:全量同步(RDB)+ 增量同步(命令传播),断点续传(backlog)。
2. 主从脑裂问题:网络分区导致从库被选为新主,多主同时写入造成数据分裂。
3. 解决方案:合理配置min-slave-to-write/min-slave-max-lag,强制主库写入前确认从库数量。
【回答示例】
Redis主从复制原理:
分为三个阶段:
1. 全量同步:
• 从库向主库发送PSYNC ? -1(请求全量同步);
• 主库执行BGSAVE生成RDB快照,同时记录后续命令到repl_backlog缓冲区;
• 将RDB文件发送给从库,从库接收后清空本地数据,加载RDB;
• 将缓冲区中的增量命令发送给从库执行。
2. 增量同步(命令传播):
• 主库将每个写命令同时发送给所有从库(主库推送模式)。
3. 断点续传(Redis 2.8+):
• 从库重连时发送PSYNC {runid} {offset},主库判断偏移量是否在repl_backlog中(默认1MB),如果在则只发送缺失的命令,避免全量同步。
主从脑裂问题:
网络分区时,主库和从库断开连接,如果从库被哨兵/Cluster自动选为新主,而原主库未感知(新主写入),网络恢复后会产生两个主库,导致数据分裂(旧主的数据覆盖新主,或两主数据不一致)。
解决方案:
min-slave-to-write 2 # 主库写入前至少等待2个从库确认
min-slave-max-lag 10 # 从库延迟超过10秒,主库拒绝写入
这样当网络分区时,如果只有1个从库存活,主库自动降级为只读,强制等待更多从库恢复,避免两个主库同时写入。
6. 算法题:求两个字符串的最长公共子序列(LCS)
【回答思路】
动态规划经典题,京东笔试高频,dp数组的维度设计是核心难点。
1. dp[i][j]含义:s1前i个字符和s2前j个字符的LCS长度。
2. 状态转移:如果s1[i-1]==s2[j-1],则dp[i][j]=dp[i-1][j-1]+1;否则dp[i][j]=max(dp[i-1][j], dp[i][j-1])。
3. 复杂度:O(mn)时间,O(min(m,n))空间优化。
【回答示例】
public int longestCommonSubsequence(String s1, String s2) {
int m = s1.length(), n = s2.length();
// dp[i][j]: s1[0..i-1] 和 s2[0..j-1] 的LCS长度
int[][] dp = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
// 空间优化版 O(min(m,n)):滚动数组,只需保留上一行
public int longestCommonSubsequenceOptimized(String s1, String s2) {
if (s1.length() < s2.length()) {
String tmp = s1; s1 = s2; s2 = tmp; // s2取较短
}
int m = s1.length(), n = s2.length();
int[] prev = new int[n + 1];
int[] cur = new int[n + 1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
cur[j] = prev[j - 1] + 1;
} else {
cur[j] = Math.max(prev[j], cur[j - 1]);
}
}
int[] tmp = prev; prev = cur; cur = tmp; // 交换并清空cur
Arrays.fill(cur, 0);
}
return prev[n];
}
7. 什么是依赖注入?Spring中@Autowired和@Resource的区别?
【回答思路】
Spring框架基础题,京东技术面常见,要说清楚两者的实现原理和区别。
1. 依赖注入(DI):Spring自动为Bean注入依赖,而非Bean自己new依赖。
2. @Autowired:byType注入(按类型),配合@Qualifier可指定bean名称。
3. @Resource:byName注入(默认按名称),找不到再按类型。
4. 实际选择:如果同一个接口有多个实现,@Autowired+@Qualifier更灵活。
【回答示例】
依赖注入(DI)是IOC的实现方式。传统写法中,ServiceA需要ServiceB,ServiceA自己new ServiceB(),造成强耦合;DI后,Spring容器在创建ServiceA时,自动把ServiceB的实例注入进去,ServiceA不需要关心ServiceB是如何创建的。
@Autowired(Spring原生):
• 默认按类型注入,从容器中找类型匹配的Bean;
• 如果容器中有多个相同类型的Bean,会抛出NoUniqueBeanDefinitionException,需要配合@Qualifier("beanName")指定具体Bean;
• 可以标注在字段、构造函数、setter方法上。
@Resource(JDK标准):
• 默认按名称(@Resource(name="xxx"),找不到再按类型;
• 如果不指定name,先按字段名去容器中找,找不到再按类型;
• 不支持按类型多个候选。
实战选择:同一接口有多个实现时,用@Autowired + @Qualifier("实现Bean名");同一接口只有一个实现时,两者差别不大,用哪个都行,但@Autowired*更符合Spring生态的惯例。
8. 京东的库存系统如何设计?如何防止超卖?
【回答思路】
电商核心系统设计题,京东的库存管理是业内标杆,要展示完整的库存架构和防超卖机制。
1. 库存分层设计:活动库存(秒杀)/ 可售库存 / 预占库存 / 实物库存。
2. 防超卖核心:乐观锁(版本号/状态机)+ Redis原子扣减。
3. 下单链路:锁库存(Redis预占)→ 支付(实际扣减)→ 取消/超时回滚。
【回答示例】
库存分层架构:
活动库存(Redis,精准备)→ 预占库存(Redis,有限时间内有效)→ 实物库存(MySQL,持久化)
活动库存(如秒杀):在活动开始前,将秒杀商品的库存量(可能只有100件)预加载到Redis,所有扣减在Redis完成。
防超卖机制——Redis Lua原子扣减**防超卖机制——Redis Lua原子扣减**:
local stock = redis.call('GET', 'sku:stock:' .. KEYS[1])
if tonumber(stock) <= 0 then return 0, 'STOCK_EMPTY' end
redis.call('DECR', 'sku:stock:' .. KEYS[1])
return 1, 'SUCCESS'
Lua脚本保证判断和扣减的原子性,不会出现两个请求同时读到库存=1、同时扣减为0的超卖情况。
下单链路中的库存流转:
1. 用户点击下单 → Redis原子扣减活动库存,返回成功 → 订单创建(状态=待支付);
2. 用户完成支付 → MySQL实物库存扣减(乐观锁)→ 订单状态变更为已支付;
3. 用户取消订单或支付超时(如30分钟)→ Redis活动库存回滚(INCR)+ 预占库存释放 → MySQL库存不变(实物还未真正扣减)。
这种设计保证了超高并发的秒杀场景下Redis承担所有压力,MySQL不会被直接击穿。
9. 什么是Spring事务的传播行为?@Transactional有哪些失效场景?
【回答思路】
Spring事务是京东Java岗必考题,要覆盖7种传播行为 + 常见失效场景。
1. 7种传播行为:REQUIRED(默认)/ REQUIRES_NEW / NESTED 等。
2. 失效场景:private方法(非代理)、同类内部调用、本地数据库无事务、外部事务未覆盖。
3. 解决方案:编码式事务(TransactionTemplate)、AOP切面捕获异常。
【回答示例】
Spring事务传播行为(7种):
|
传播行为 |
说明 |
|
REQUIRED(默认) |
有事务则加入,无则新建 |
|
REQUIRES_NEW |
总是新建事务,挂起外部事务 |
|
NESTED |
有事务则在嵌套点创建Savepoint,无则新建 |
|
SUPPORTS |
有则加入,无则不以事务运行 |
|
NOT_SUPPORTED |
以非事务运行,挂起外部事务 |
|
MANDATORY |
必须在事务中运行,否则抛异常 |
|
NEVER |
必须在非事务中运行,否则抛异常 |
@Transactional失效的常见场景:
1. private方法:Spring AOP基于代理(Proxy),代理只能拦截public方法,private方法不受事务控制;
2. 同类内部调用:在ServiceA的methodA()中直接调用this.methodB(),不走代理,走的是目标对象的直接调用,@Transactional不生效;
3. 异常被catch吞掉:如果业务方法内部catch了异常但没有重新抛出,Spring事务管理器感知不到异常,不会回滚;
4. 异常类型不匹配:默认只对RuntimeException和Error回滚,Checked Exception不会触发回滚(除非rollbackFor=Exception.class);
5. 事务传播行为不当:PROPAGATION_REQUIRES_NEW会挂起外部事务,如果外部需要看到内部事务的提交结果就会出问题。
10. HR面:你对京东的"客户为先"价值观有什么理解?
【回答思路】
京东文化题,结合自己的实际经历来回答,展示真诚的思考而非背诵。
1. 对"客户为先"的理解:不是"服务客户",而是"以客户视角做决策"。
2. 结合经历:举一个你主动以用户视角改进工作的例子。
3. 表达期待:说明你在京东能如何实践这个价值观。
【回答示例】
我对"客户为先"的理解是:不是口号,而是每次做决策时的第一性问题。
在我做项目时,有一次我完成了商品详情页的改版,从技术角度看代码质量很高,页面加载速度也优化了40%,我很满意。但上线前让用户测试时,发现新的页面布局把用户最关心的"价格"和"评价"藏到了页面下方,需要滚动才能看到——用户在旧版上是直接能看到这些信息的。
这件事让我意识到,技术做得好,不等于用户用得好。从那之后,我在每个功能开发前,都会问自己:"如果我是用户,我会不会用?会不会觉得麻烦?"这个习惯后来帮我避免了好几次"技术自我感动"的设计。
京东"客户为先"对我来说,就是这种思维——在每一个细节上,都站在用户的角度去判断什么更重要,而不是以开发者的便利为优先。
🚀 准备好了吗?用AI模拟京东面试 | 求职精灵收录45万道国企&互联网真题,支持AI模拟面试、简历测评、自动网申填写。立即免费体验:https://finsight.work