站点图标 书樱寄语

【面试经过】26校招 字节跳动 后端研发工程师 国际化广告CRM与交易平台

1. Spring 启动流程(含 Spring Framework 与 Spring Boot 视角)

(A)Spring 容器 AbstractApplicationContext.refresh() 经典 12 步

  1. prepareRefresh():准备环境(启动时间戳、占位属性、校验必需属性)。
  2. obtainFreshBeanFactory():创建/刷新 BeanFactory,载入 BeanDefinition(XML/注解/JavaConfig)。
  3. prepareBeanFactory():设置类加载器、EL 解析器、ApplicationContextAware 等常用能力。
  4. postProcessBeanFactory():给子类扩展点(如 GenericApplicationContext)。
  5. invokeBeanFactoryPostProcessors():执行 BeanFactoryPostProcessor关键:ConfigurationClassPostProcessor 解析 @Configuration@ComponentScan@Import、注册更多 BeanDefinition)。
  6. registerBeanPostProcessors():注册 BeanPostProcessor(AOP 代理、@Autowired 注入、@PostConstruct 等都依赖它们)。
  7. initMessageSource():国际化消息源。
  8. initApplicationEventMulticaster():事件广播器。
  9. onRefresh():子类扩展点(Web 环境在此创建内置 Web 服务器)。
  10. registerListeners():注册并触发早期事件。
  11. finishBeanFactoryInitialization():实例化非懒加载单例 Bean,完成依赖注入与 AOP 代理创建。
  12. finishRefresh():清理、发布 ContextRefreshedEvent,容器就绪。

(B)Spring Boot 启动要点(SpringApplication.run


2. Elasticsearch 与 SQL(关系型数据库)的区别

维度 Elasticsearch 关系型数据库(SQL/RDBMS)
数据模型 文档型(JSON),Schema 灵活;倒排索引 行/列+严格 Schema;B-Tree/索引
查询语言 Query DSL(布尔、全文检索、评分);也有 ES-SQL(语法糖) 标准 SQL(选择、连接、聚合、窗口函数)
一致性 近实时(默认 \~1s refresh),最终一致;写入刷盘 translog + 段合并 事务 ACID、强一致
事务 文档级原子;无跨多文档/多索引强事务 多语句事务,隔离级别
连接/关联 建议反范式,支持 limited 的 nested/parent-child,成本高 JOIN 一等公民
排序/相关性 TF-IDF/BM25 等相关性打分、模糊搜索 精确匹配,排序多基于字段
聚合分析 分布式聚合、桶/度量聚合、近实时 GROUP BY、窗口函数,强一致
扩展性 天生分片+副本,水平扩展容易 纵向为主;水平分片需中间件/应用层
适用场景 全文检索、日志/可观测性、近实时检索与聚合 OLTP 事务、强一致业务、复杂关联
成本与维护 集群调参(分片、副本、刷新、合并)、写放大 事务调优、索引设计、主从/分库分表

选型建议:检索与日志分析优先 ES;核心交易、强一致与复杂 JOIN 走 RDBMS。它们常互补:数据落地 RDBMS,旁路同步至 ES 做搜索。


3. git merge vs git rebase

概念

对比

团队建议


4. 子线程如何继承父线程里的数据(Java / Python)

Java

  1. ThreadLocal:线程本地变量,不会自动继承。
  2. InheritableThreadLocal(新建线程时复制)

    • 适合直接 new Thread 的场景;线程池复用线程时不生效。
    • 示例:
    static final InheritableThreadLocal CTX = new InheritableThreadLocal<>();
    public static void main(String[] args) {
       CTX.set("trace-123");
       new Thread(() -> System.out.println(CTX.get())).start(); // 输出 trace-123
    }
  3. 线程池中的传递:使用 TransmittableThreadLocal (TTL)(阿里开源)包装 Executor,解决线程复用导致的上下文不传与“脏数据”问题。

    TransmittableThreadLocal ttl = new TransmittableThreadLocal<>();
    ExecutorService raw = Executors.newFixedThreadPool(4);
    ExecutorService exec = TtlExecutors.getTtlExecutorService(raw);
    
    ttl.set("trace-xyz");
    exec.submit(() -> System.out.println(ttl.get())); // 正确输出
  4. 实践要点

    • 上下文建议封装为不可变对象;用完记得 remove() 防泄漏。
    • 记录链路用日志 MDC(org.slf4j.MDC),搭配 TTL 更稳妥。
    • 显式传参就显式传参,降低隐式上下文耦合。

Python

  1. threading.local():线程本地存储,不会自动继承。
  2. contextvars(推荐传递上下文)

    • ContextVar 对线程与协程隔离;默认不继承到新线程,但可用 copy_context() 拷贝父上下文并在子线程中运行。
    import threading, contextvars
    trace_id = contextvars.ContextVar("trace_id")
    
    def worker():
       print("child:", trace_id.get(None))
    
    def main():
       trace_id.set("t-123")
       ctx = contextvars.copy_context()
       threading.Thread(target=ctx.run, args=(worker,)).start()
    
    if __name__ == "__main__":
       main()  # child: t-123
    • 在线程池中:为每个 submit 捕获上下文

      from concurrent.futures import ThreadPoolExecutor
      from contextvars import copy_context
      with ThreadPoolExecutor() as ex:
       ctx = copy_context()
       ex.submit(ctx.run, worker)  # 传播父上下文快照
  3. 其他:显式参数传递最清晰;全局变量可见但不建议承载“上下文”。

5. 从“浏览器输入 URL”到“页面呈现”的全过程(高频面试版)

0)历史/本地“秒开”快路径(跳过网络)

  1. 前进/后退缓存(bfcache)
  1. Service Worker 拦截
  1. HTTP 浏览器缓存(memory/disk)

1)导航与 URL 解析

2)DNS 解析(无本地命中时)

3)定位链路层目标(ARP / MAC)

4)建连与加密:TCP/QUIC & TLS

5)发起 HTTP 请求(可能经代理/CDN/WAF)

6)服务器响应与缓存语义

7)连接复用与版本差异(性能面)

8)Renderer 渲染流水线(以 Chromium 为例)

  1. 解析构建

    • HTML → DOM(脚本标签默认阻塞解析;defer/async/module 可缓解);
    • CSS → CSSOM
    • 合并为 Render Tree
  2. 布局(Reflow):计算几何、盒模型。
  3. 绘制与合成:分层、栅格化、GPU 合成输出到屏幕。
  4. 子资源获取:解析器预扫描发现外链资源,对每个资源同样走 SW/HTTP 缓存/网络 的“三段式”流程。
  5. 页面生命周期:事件循环、微/宏任务,后续交互驱动增量布局与绘制。
    (Chrome 官方系列对浏览器多进程架构与渲染管线有系统性讲解。)

9)连接关闭(或复用)

10)一张“面试可画”的决策树(简化)

  1. 历史导航? → bfcache 命中 ⇒ 直接呈现
  2. Service Worker 控制? → 走 SW 策略,可能离线直出
  3. HTTP 缓存命中?

    • 强缓存有效 ⇒ 本地直接用
    • 需验证 ⇒ 发条件请求,304 用本地体;
    • 否则走网络。
  4. 走网络:DNS → ARP/MAC → TCP/QUIC + TLS(ALPN)→ 请求/响应(含 CDN)→ 回写缓存。
  5. 渲染流水线:DOM/CSSOM/Render Tree → 布局 → 绘制/合成。

5.1 HTTP/1.1 vs “HTTP/2”

对比项 HTTP/1.1 HTTP/2
编码形态 文本协议 二进制分帧(帧/流/消息)
连接利用 慢启动;管线化几乎停用;常并发 6 条连接/域名 单连接多路复用(并行请求,无应用层 HOL 阻塞)
队头阻塞 应用层易 HOL;需多连接来缓解 TCP 层仍可能 HOL,但应用层已缓解
头部 重复大、无压缩(或弱压缩) HPACK 头压缩(动态/静态字典)
优先级 请求优先级/依赖(实现差异)
服务器推送 Server Push(浏览器已普遍弃用)
安全/TLS 可明文/HTTPS 明文 h2c 存在但少见,实际多为 TLS+h2
性能策略 雪碧图、域名分片、内联资源 多路复用下不再需要这些反模式

补充:HTTP/3 基于 QUIC(UDP)解决 TCP HOL 与建连时延,更进一步提升时延与丢包恢复。

5.2 为什么不同地方 ping 同一个域名会解析到不同的 IP?

一句话总结:多数网站把“域名 → IP”的映射交给DNS 调度CDN 来做“就近/最优”分配;权威 DNS 会基于解析器所在位置(或 EDNS Client Subnet 提供的客户端前缀)、实时健康检查与时延权重轮询等策略返回不同的 A/AAAA 记录,于是你在不同地区/不同网络 ping 同一域名时,往往拿到不同 IP。请求真正到达时,还可能走到 Anycast 最近节点或对应的边缘机房。

典型原因拆解

  1. DNS/GeoDNS 就近解析(基于解析器位置 + 可选 ECS)
  1. CDN 全局流量调度(健康、时延、容量)
  1. 轮询/加权轮询(多 A/AAAA)
  1. Anycast(任一播)
  1. 解析与缓存差异

小提示:ping 用的是系统的 DNS 解析结果;在双栈网络中,ping(IPv4)与 ping -6(IPv6)对应查询 AAAAA,拿到的 IP 自然不同,这与地理调度并不冲突。

如何自行验证(命令行思路)

一图心智模型

你(客户端)
   │  DNS 查询
   ▼
本地/公共递归解析器  ──(可带 ECS 客户端前缀)──▶  域名权威 DNS/CDN GSLB
   │                                          │
   │◀──────── 返回“就近/最优”的 A/AAAA ────────┘
   │
   ▼
得到 IP → 发起连接(可能是 Anycast IP,路由到最近节点)

结论:不同地点 ping 同一域名拿到不同 IP 是正常现象,是 GeoDNS + CDN +(可选)Anycast 等机制协同实现的“就近与高可用”。当你换了网络、换了解析器、或过了 TTL,解析答案都可能变化。


6. 「拼手气红包」如何均匀/公平分配(算法与实现要点)

目标:总金额 S(以分为单位)拆成 n 份,每份 ≥ min(通常 1 分),期望值相等、结果看起来“有惊喜”。

常见算法

  1. 二倍均值法(Double-Mean,O(n))

    • 思想:剩余金额 R、剩余人数 m 时,当前可领范围为 (min, 2 * (R/m) - min)均匀分布(再与上界 R - (m-1)*min 取最小以安全)。
    • 期望:E[x_i] = R/m,总体期望均等;极端值概率可控,看起来“有人多有人少”。
    • 伪代码(以分为单位,整型随机):

      remain = S
      for i in 1..n-1:
      m = n - i + 1
      max_allow = remain - (m-1)*min
      max_dm   = floor(2 * remain / m)   # 二倍均值
      hi = min(max_allow, max_dm)
      amt = rand_int(min, hi)            # 均匀取整
      give(i, amt)
      remain -= amt
      give(n, remain)
    • 优化:可设置 max_cap(单个上限)→ hi = min(hi, max_cap);最后用“最大余数法”修正四舍五入误差(若使用浮点/元)。
  2. 线段切割法(随机切点/Dirichlet)

    • [0, S] 上取 n-1 个随机点,排序后求相邻差即每份金额,天然总和 S
    • 若需每份 ≥ min:先预留 n*min,对 S - n*min 切割,再加回 min
    • 连续模型近似 Dirichlet(1,…,1),从统计意义上最“公平随机”。
  3. 正态/指数偏好法

    • 以均值 S/n、方差参数控制离散度,从分布采样再做非负与求和约束修正(体验更“刺激”)。
    • 需注意最终离散到分后的校正。

正确性与工程细节

7. LeetCode 85. 最大矩形

https://leetcode.cn/problems/maximal-rectangle/description/

个人感受

  1. 面试官很注重学历(问我为什么没保研/学校为什么这么差)
  2. 很注重八股(没听到八股答案就上压力)
  3. 确实是我太菜了
退出移动版