写点什么

北京京东,看看难度

作者:王中阳Go
  • 2025-06-20
    北京
  • 本文字数:4972 字

    阅读完需:约 16 分钟

北京京东,看看难度

最近由于三大外卖平台“打仗”,优惠券多到数不过来,一日三餐每个平台各点一单哈哈哈,正好最近组织内部还有朋友在北京的京东面试过,分享一下她的面经(Java 岗):

1. Kafka 消息不丢失问题,Kafka 本身会去保证消息的不丢失,为什么还需要存一个本地消息表来保证消息的不丢失呢?

Kafka 本身通过副本机制、生产者确认(acks)、消费者手动提交等设计理论上可以实现消息不丢失,但在实际分布式系统中,由于业务逻辑复杂性、中间件与业务操作的原子性难以保障,仍需要引入“本地消息表”等额外机制。以下是具体原因及解决方案的对比分析:


Kafka 消息不丢失的机制及其局限性


  1. 生产者端

  2. acks=-1:要求所有 ISR(同步副本)确认写入成功,否则重试。

  3. 重试机制:配置 retriesretry.backoff.ms 应对网络抖动。

  4. Broker 端

  5. 副本冗余:通过 replication.factor≥3 + min.insync.replicas≥2,避免单点故障丢失数据。

  6. 持久化:消息先写入 PageCache 再异步刷盘(依赖服务器可靠性)。

  7. 消费者端

  8. 手动提交 offset:关闭 enable.auto.commit,业务处理成功后再提交 offset,避免消息未处理就被标记消费。


Kafka 机制的局限性


  1. 生产者与 Broker 的协同问题

  2. 若生产者发送成功但 Broker 未返回 ACK(如网络中断),重试可能导致消息重复,但无法避免中间状态丢失。

  3. 业务操作与消息消费的原子性

  4. 消费者处理业务逻辑(如更新数据库)与提交 offset 不是原子操作。若业务成功但 offset 未提交,系统重启后消息会重复消费;若业务失败但 offset 已提交,则消息永久丢失。

  5. 极端故障场景

  6. Broker 集群同时宕机且未持久化的 PageCache 丢失。

  7. ISR 副本全部失效时,unclean.leader.election 配置可能导致数据丢失。


为什么需要本地消息表?解决哪些 Kafka 无法覆盖的问题


本地消息表的核心是将业务操作与消息发送/消费绑定为原子操作,通过业务数据库事务保证一致性:


  1. 生产者端:解决“发送后丢失”问题

  2. 场景:消息发送到 Kafka 成功,但业务操作(如订单创建)失败,需回滚消息。

  3. 方案

  4. 业务数据与消息记录同库事务写入本地表。

  5. 异步线程轮询本地表,将未发送的消息投递到 Kafka。

  6. 投递成功后删除本地记录。

  7. 消费者端:解决“消费后丢失”问题

  8. 场景:业务逻辑成功执行(如扣款),但提交 offset 前消费者崩溃,导致消息重复消费。

  9. 方案

  10. 消费消息时,先查询本地消息表是否已处理(通过消息唯一 ID)。

  11. 若未处理,执行业务操作并记录结果到本地表,同事务提交。

  12. 成功后提交 offset。

  13. 关键价值:弥补分布式事务缺口

  14. 业务与消息状态的强一致:本地消息表通过数据库事务,确保业务操作和消息状态变更的原子性,避免 Kafka 各环节可能出现的状态分裂。

  15. 幂等性保障:通过本地表去重,解决生产者重试或消费者重复消费导致的数据错乱。


生产建议

  • 若使用本地消息表,建议配合异步核对机制(如比对 Kafka 与业务库消息状态),防止极端情况下的表状态异常。

  • 优先通过 idempotent producer(幂等生产者)减少重复发送,而非完全依赖本地表。

总结:Kafka 与本地消息表的关系

Kafka 的可靠性机制解决消息在管道内的传输问题,而本地消息表解决业务操作与消息状态的全局一致性问题。二者是互补而非替代关系:

  • 在需要 100% 业务一致性的场景(如资金变动),本地消息表是必要的最终防线。

  • 在可容忍 At-Least-Once 语义的场景(如日志收集),仅 Kafka 原生机制即可满足。



2. 用守护线程来保证任务执行状态和数据库状态的一致性,这里有个问题,如果 subjob 执行完了在状态翻转之前宕机了,怎么保证他的一致性呢?宕机导致数据丢失的问题?

在分布式系统中,通过守护线程监控任务状态并同步到数据库时,若任务执行完成但状态更新前发生宕机,需通过以下机制保证一致性。具体解决方案如下:


  1. 核心问题:宕机导致状态丢失的风险

  2. 场景复现:SubJob 执行完成 → 守护线程未完成状态更新(如数据库写入)→ 系统宕机 → 重启后状态未翻转,任务被视为“未完成”,可能触发重复执行或数据不一致。

  3. 根本原因:任务执行结果与状态更新操作非原子性,二者分离导致中间状态丢失。

  4. 解决方案:原子性保障 + 故障恢复机制

  5. 本地事务绑定状态更新

  6. 设计要点:将任务执行结果与状态更新置于同一数据库事务中。

  7. 优势

  8. updateStatus 失败,事务回滚,任务结果不会被确认。

  9. 宕机时事务未提交,数据库自动恢复至初始状态。

  10. 局限:需任务执行本身支持事务(如数据库操作)。

  11. 预写日志(WAL) + 检查点(Checkpoint)

  12. 流程

  13. SubJob 完成时,先持久化结果到预写日志(如 Kafka 或持久化队列)。

  14. 守护线程消费日志,更新状态。

  15. 周期性设置 Checkpoint,记录日志消费位点。

  16. 故障恢复

  17. 宕机重启后,从最近 Checkpoint 恢复日志消费位点,重放未确认的状态更新。

  18. 通过日志唯一 ID 实现操作幂等性,避免重复更新。

  19. 异步核对 + 补偿机制

  20. 设计要点

  21. 守护线程更新状态后,异步记录操作流水(如操作 ID + 时间戳)。

  22. 定时扫描任务表与状态表的差异,对“执行成功但状态未更新”的任务触发补偿更新。

  23. 关键点

  24. 核对需覆盖极端场景(如守护线程更新状态后宕机)。

  25. 补偿操作需幂等(例如通过 UPDATE status SET state='done' WHERE id=task_id AND state!='done')。

  26. 总结:关键设计原则

  27. 原子操作优先:通过事务或预写日志绑定任务执行与状态更新,减少中间态窗口。

  28. 幂等性必备:状态更新操作需支持重复执行(如基于唯一任务 ID 的幂等更新)。

  29. 最终一致性兜底:通过核对与补偿覆盖极端故障,实现数据闭环。

  30. 生产建议

    • 若采用预写日志,建议搭配 Kafka 事务消息(idempotent producer)避免消息重复。

    • 核对频率需权衡时效性与系统负载(如每 5 分钟扫描一次)。



3. Dubbo 执行的原理

Dubbo 是一个高性能 Java RPC 框架,其核心执行流程如下:


  1. 服务暴露与注册

  2. 服务提供者启动时,将服务接口、实现类及主机信息注册到注册中心(如 ZooKeeper)。

  3. 注册中心通知消费者服务列表变更。

  4. 服务调用流程

  5. 代理层:消费者通过动态代理生成远程接口的代理对象,调用时转为 RPC 请求。

  6. 集群容错:根据配置(如 Failover、Failfast)选择可用提供者,支持负载均衡(如随机、轮询)。

  7. 网络传输:通过 Netty 或 Mina 进行网络通信,默认使用 Hessian2 序列化协议。

  8. 核心分层设计

  9. Service 层:业务逻辑接口与实现。

  10. Config 层:配置管理(如 @Reference 注解注入服务)。

  11. Proxy 层:生成服务代理。

  12. Registry 层:服务注册与发现。

  13. Monitor 层:调用统计与监控。



4. 什么情况下会导致索引失效

MySQL 索引失效的常见场景包括:


  1. 违反最左前缀原则

  2. 复合索引 (a,b,c) 下,查询条件缺失 a 或未按顺序使用索引列(如 WHERE b=1)。

  3. 对索引列运算或函数操作

  4. 例如 WHERE YEAR(create_time)=2023WHERE amount*2>100

  5. 隐式类型转换

  6. 如字符串字段使用数字查询(WHERE code=100,实际 code 为 VARCHAR)。

  7. 使用 OR 连接非索引列

  8. WHERE a=1 OR b=2,若 b 无索引则全表扫描。

  9. LIKE 以通配符开头

  10. WHERE name LIKE '%abc' 无法利用索引(LIKE 'abc%' 有效)。

  11. 数据分布不均

  12. 优化器判断全表扫描更快(如表中 90% 数据满足条件)。



5. MySQL 的 MVCC 机制

MVCC(多版本并发控制)是 InnoDB 实现高并发的核心机制:


  1. 核心组件

  2. 隐藏字段:每行数据包含 DB_TRX_ID(最近事务 ID)和 DB_ROLL_PTR(回滚指针)。

  3. Undo Log:存储数据的历史版本,用于回滚和一致性读。

  4. Read View:事务开启时生成,记录当前活跃事务 ID 列表,用于判断数据可见性。

  5. 可见性规则

  6. 数据行的 DB_TRX_ID 小于 Read View 中最小事务 ID → 可见(已提交)。

  7. DB_TRX_ID 大于 Read View 中最大事务 ID → 不可见(未提交)。

  8. DB_TRX_ID 在活跃事务列表中 → 不可见(未提交);否则可见。

  9. 解决并发问题

  10. 读已提交(RC):每次查询生成新 Read View,避免脏读。

  11. 可重复读(RR):事务内首次查询生成 Read View 并复用,避免不可重复读。



6. 缓存穿透和缓存雪崩

  1. 缓存穿透

  2. 问题:大量请求查询不存在的数据(如无效 ID),绕过缓存直击数据库。

  3. 解决方案

  4. 布隆过滤器(Bloom Filter)拦截非法 Key。

  5. 缓存空值(key:null),并设置短过期时间。

  6. 缓存雪崩

  7. 问题:大量缓存同时失效,请求集中访问数据库导致宕机。

  8. 解决方案

  9. 过期时间添加随机值(如 30min + rand(10min))。

  10. 热点数据永不过期,后台异步更新。

  11. 熔断降级:数据库压力过大时拒绝部分请求。



7. Redis 如何保证高性能?Redis 数据结构

Redis 高性能的核心设计:


  1. 内存操作:数据全内存存储,读写速度比磁盘数据库快 10⁵ 倍。

  2. 高效数据结构优化

  3. SDS(简单动态字符串):预分配空间减少扩容开销。

  4. 跳表(ZSET):O(logN) 复杂度实现范围查询。

  5. 渐进式 Rehash(Hash):避免一次性迁移大哈希表导致服务阻塞。

  6. 单线程模型(6.0 前核心命令处理):

  7. 避免多线程锁竞争和上下文切换开销。

  8. I/O 多路复用

  9. 基于 epoll/kqueue 监听大量连接,单线程处理网络 I/O。

  10. 协议简单:RESP 协议解析高效,减少 CPU 消耗。


Redis 6.0+ 优化:网络 I/O 多线程化提升吞吐量,但命令执行仍保持单线程以保证原子性。



8. Spring 和 Spring Boot 的区别



9. Spring Boot 自动装配的原理

自动装配通过以下流程实现:


  1. 启动注解 @SpringBootApplication

  2. 组合了 @EnableAutoConfiguration,触发自动配置加载。

  3. 加载 spring.factories

  4. 扫描 META-INF/spring.factories 文件,读取 AutoConfiguration 类列表。

  5. 条件化装配

  6. 通过 @ConditionalOnClass@ConditionalOnProperty 等注解,按需实例化 Bean(如仅当存在 DataSource.class 时配置数据库连接)。

  7. Bean 注册

  8. 符合条件的配置类中,@Bean 方法将对象注册到 IoC 容器。



10. @Autowired 是如何把 Bean 注入进去的

@Autowired 注入流程分为四个阶段:


  1. 注入触发阶段

  2. AutowiredAnnotationBeanPostProcessor 扫描被 @Component 标记的类,识别带 @Autowired 的字段/方法。

  3. 收集依赖信息并封装为 InjectionMetadata 对象。

  4. 依赖解析阶段

  5. 按类型匹配:查找容器中与目标类型匹配的 Bean(如 UserService)。

  6. 按名称兜底:若同类型多个 Bean 存在,尝试匹配字段/参数名称(如 userService)。

  7. @Qualifier 指定:强制按名称注入(如 @Qualifier("masterDB"))。

  8. 注入执行阶段

  9. 字段注入:反射直接修改字段值(无需 Setter)。

  10. 方法注入:调用 Setter 方法传入依赖对象。

  11. 特殊场景处理

  12. 集合注入List<Interface> 注入所有实现类;Map<String, Interface> 的 Key 为 Bean 名称。

  13. 静态字段限制:无法直接注入静态变量(需通过 @PostConstruct 中转)。


底层原理:依赖解析由 DefaultListableBeanFactory.doResolveDependency() 完成,最终通过 Field.set() 或方法反射注入。



11. ES 与 MySQL 的区别,几个数据节点,几个副本,副本数可以为 0?

一、核心差异



二、节点与副本配置


  1. 节点数量

  2. ES:至少 3 节点(防脑裂),主分片与副本分片跨节点分布。

  3. MySQL:主从架构至少需 2 节点(1 主 + 1 从)。

  4. 副本数规则

  5. ES

  6. 可配置为 0(number_of_replicas: 0),但宕机时数据可能丢失。

  7. 生产建议 ≥1(副本=1 容忍单节点故障)。

  8. MySQL:副本数不可为 0(单点部署即无副本),高可用方案需 ≥1 从节点。



12. 设计模式,使用过哪些设计模式,详细介绍模板方法

模板方法模式


  1. 核心思想

  2. 定义算法骨架(抽象类),子类重写特定步骤而不改变结构。

  3. 实现示例


   public abstract class DataProcessor {       // 模板方法(final 防止篡改)       public final void process() {           connect();      // 固定步骤           transform();    // 抽象方法(子类实现)           disconnect();   // 固定步骤       }       private void connect() { /* 数据库连接逻辑 */ }       protected abstract void transform(); // 由子类自定义       private void disconnect() { /* 断开连接 */ }   }
public class CSVProcessor extends DataProcessor { @Override protected void transform() { System.out.println("解析 CSV 数据..."); } }
复制代码


  1. 应用场景

  2. 框架扩展点:如 Spring 的 JdbcTemplate,用户实现 RowMapper 处理结果集。

  3. 业务流程标准化:如订单处理流程(校验 → 计算 → 持久化),子类定制计算逻辑。

  4. 优势

  5. 避免代码重复,确保核心流程稳定。

  6. 开放扩展点,提升灵活性。

欢迎关注 ❤

我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。


没准能让你能刷到自己意向公司的最新面试题呢。


感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:面试群。

发布于: 刚刚阅读数: 3
用户头像

王中阳Go

关注

靠敲代码在北京买房的程序员 2022-10-09 加入

【微信】wangzhongyang1993【公众号】程序员升职加薪之旅【成就】InfoQ专家博主👍掘金签约作者👍B站&掘金&CSDN&思否等全平台账号:王中阳Go

评论

发布
暂无评论
北京京东,看看难度_Java_王中阳Go_InfoQ写作社区