字节跳动 Linux C/C++ 后端面试真题解析
字节跳动后端面试的核心知识点,涵盖 HTTP/HTTPS/TCP 网络协议、C++ 指针与内存管理、进程通信与多线程同步、数据库范式、Spring AOP 原理及缓存穿透击穿雪崩等问题。内容包含技术原理详解、代码示例及实战排查经验,旨在帮助候选人深入理解后端开发基础与高并发场景下的解决方案。

字节跳动后端面试的核心知识点,涵盖 HTTP/HTTPS/TCP 网络协议、C++ 指针与内存管理、进程通信与多线程同步、数据库范式、Spring AOP 原理及缓存穿透击穿雪崩等问题。内容包含技术原理详解、代码示例及实战排查经验,旨在帮助候选人深入理解后端开发基础与高并发场景下的解决方案。

考察重点:HTTP 协议的请求方法语义、适用场景区分,避免仅罗列不说明用途。
回答思路:
按'常用 + 少见'分类,结合后端业务场景说明:
常用方法:
少见方法:
考察重点:HTTPS 与 HTTP 的核心差异、安全机制的底层逻辑(身份认证、加密、完整性)。
回答要点:
HTTPS = HTTP + TLS/SSL 协议,通过三层机制保障安全:
身份认证(防伪装):
服务端需配置 CA 机构颁发的证书(含公钥、服务端域名、有效期),客户端请求时会验证证书合法性(如是否过期、域名是否匹配、CA 签名是否有效),避免连接'假服务端';
数据加密(防窃听):
握手阶段用非对称加密协商会话密钥(服务端公钥加密、私钥解密),传输阶段用对称加密(如 AES)加密数据(对称加密效率高,适合大体积数据);
完整性校验(防篡改):
用 MAC(消息认证码)或 HMAC 对数据生成'指纹',客户端接收后验证指纹,若数据被篡改(如中间件拦截修改),指纹不匹配则拒绝接收。
考察重点:HTTPS 加密体系的'混合模式'逻辑,避免混淆对称 / 非对称加密的作用。
回答要点:
HTTPS同时使用对称加密与非对称加密,分工不同:
非对称加密(仅用于握手阶段):
对称加密(用于数据传输阶段):
补充:TLS1.3 优化了握手流程(仅 1 次往返),但核心加密逻辑仍为'非对称协商密钥 + 对称传输数据'。
考察重点:状态码的分类逻辑(1xx-5xx)、常见码的实际业务场景(如 304、403、502)。
回答要点:
按 HTTP 标准分类,结合后端场景举例:
1xx(信息性):临时响应,如 100 Continue(客户端需继续发送请求体);
2xx(成功):
3xx(重定向):
4xx(客户端错误):
5xx(服务端错误):
考察重点:TCP 可靠传输的核心机制(避免丢失、乱序、重复),底层原理与业务场景结合。
回答要点:
TCP 通过六大机制保障可靠传输,对应不同问题:
考察重点:完整网络请求链路(从 URL 解析到页面渲染),各层协议协同逻辑。
回答要点:
分 8 个核心步骤,覆盖'网络 + 浏览器'全流程:
考察重点:C++ 内存模型理解、指针与引用的语法 / 语义差异,实际编码选择逻辑。
回答要点:
从 6 个核心维度对比,结合场景举例:
| 维度 | 指针 | 引用 |
|---|---|---|
| 定义与初始化 | 变量(存地址),可空 / 未初始化 | 变量别名,必须初始化且绑定对象 |
| 内存占用 | 占 4/8 字节(依平台) | 不占独立内存(编译器优化为指针) |
| 可修改性 | 可重指向其他对象(如 int* p=&a; p=&b;) | 绑定后不可更改(如 int& r=a; r=b 是赋值,非重绑定) |
| 操作方式 | 需解引用(*p)/ 取地址(&p) | 直接使用(如 r=1 等价于 a=1) |
| 多态支持 | 可指向基类 / 派生类对象(如 Base* p=new Derived;) | 可绑定基类 / 派生类对象(如 Base& r=Derived ();) |
| 风险点 | 野指针(未初始化)、空指针(nullptr) | 无空引用,但需避免绑定局部变量(生命周期问题) |
| 场景举例 | 函数参数传递时,需修改实参用指针 / 引用;返回对象时,用引用避免拷贝(如 vector& getVec ()),但不可返回局部变量引用。 |
考察重点:程序编译链接机制,静态 / 动态链接的底层差异与场景适配。
回答要点:
先定义核心概念,再对比优缺点:
静态链接:
动态链接:
场景选择:工具类程序(如命令行工具)用静态链接(部署方便);后端服务(如 API 服务)用动态链接(便于更新库、节省内存)。
考察重点:C++ 对象拷贝的内存管理,浅拷贝的风险与深拷贝的实现逻辑。
回答要点:
核心差异在'动态内存成员的处理',结合例子说明:
浅拷贝:
深拷贝:
实现举例:
// 浅拷贝(默认拷贝构造)
class String {
public:
char* data;
String(const char* s) {
data = new char[strlen(s)+1];
strcpy(data, s);
}
~String() {
delete[] data;
}
}; // 浅拷贝时会双重释放
// 深拷贝(重写拷贝构造与赋值运算符)
String::String(const String& other) {
data = new char[strlen(other.data)+1];
strcpy(data, other.data); // 拷贝指针指向的内容
}
String& String::operator=(const String& other) {
if (this != &other) {
delete[] data; // 先释放自身内存
data = new char[strlen(other.data)+1];
strcpy(data, other.data);
}
return *this;
}
考察重点:进程通信的架构设计思维,如何降低进程间依赖(避免紧耦合)。
回答要点:
解耦核心是'减少直接交互,通过中间层 / 协议隔离',5 类常见机制:
1)、消息队列(如 Linux msgqueue、RabbitMQ):
2)、发布 - 订阅模式(如 Redis Pub/Sub、Kafka):
3)、共享内存 + 信号量:
4)、中间件代理(如 RPC 服务发现):
5)、管道 / 命名管道(半解耦):
考察重点:Linux IPC 机制的分类与场景适配,避免仅罗列不说明用途。
回答要点:
7 类核心 IPC 方式,按'数据传递 / 同步'分类:
| 方式 | 核心逻辑 | 适用场景 |
|---|---|---|
| 匿名管道 | 父子 / 兄弟进程,半双工,基于文件描述符 | shell 命令管道(如 ls) |
| 命名管道(FIFO) | 无亲缘进程,半双工,文件系统可见 | 不同服务间简单通信(如服务 A→服务 B 传状态) |
| 消息队列 | 无亲缘进程,异步,按类型接收消息 | 高并发异步通信(如日志收集、订单通知) |
| 共享内存 | 进程直接访问同一块内存,最快 IPC | 大数据量传输(如视频帧、传感器数据) |
| 信号量 | 同步互斥,不传递数据,控制并发数 | 保护共享资源(如限制 3 个进程读写共享内存) |
| 信号 | 异步通知,传递简单信号(如 SIGKILL) | 异常处理(如 kill -9 终止进程)、事件触发(子进程退出通知父进程) |
| Socket(套接字) | 跨主机 / 本地进程,基于 TCP/UDP | 网络服务(如客户端 - 服务端通信,Nginx 接收请求)、本地进程通信(如 Unix 域套接字) |
考察重点:数据库设计规范,范式的作用(减少冗余、避免异常)与实际权衡。
回答要点:
按'1NF→3NF→BCNF'讲解(常用范式),结合反例说明:
1NF(第一范式):列不可再分(原子性);
2NF(第二范式):满足 1NF,且非主键列'完全依赖'主键(消除部分依赖);
3NF(第三范式):满足 2NF,且非主键列'不传递依赖'主键(消除传递依赖);
BCNF(巴斯 - 科德范式):满足 3NF,且主键列'不传递依赖'非主键列(解决主属性传递依赖);
关键提醒:范式不是越高越好,过度范式会增加表连接(如 5 张表 join,查询效率低),实际设计需'反范式'权衡(如热点数据冗余存储,减少 join)。
考察重点:死锁的底层逻辑(必要条件)、实际开发中的资源竞争场景,避免仅罗列理论不结合代码。
回答要点:
多线程死锁是'多个线程互相等待对方持有的资源,无法继续执行'的状态,需同时满足四个必要条件,结合 C++ 场景举例说明:
互斥条件:资源只能被一个线程占用(如 std::mutex 加锁后,其他线程需等待解锁);
持有并等待条件:线程持有已获得的资源,同时等待其他线程的资源;
不可剥夺条件:线程持有的资源不能被强制剥夺(如已加锁的 mutex,只能由持有线程主动解锁);
循环等待条件:多个线程形成'资源等待循环链'(线程 A→线程 B→线程 C→线程 A);
示例(典型死锁代码):
std::mutex mutex1, mutex2;
// 线程 A
void threadA() {
mutex1.lock(); // 持有 mutex1
std::this_thread::sleep_for(100ms); // 让线程 B 先持有 mutex2
mutex2.lock(); // 等待 mutex2,此时线程 B 已持有 mutex2 并等待 mutex1,形成死锁
// 业务逻辑...
mutex2.unlock();
mutex1.unlock();
}
// 线程 B
void threadB() {
mutex2.lock(); // 持有 mutex2
std::this_thread::sleep_for(100ms); // 让线程 A 先持有 mutex1
mutex1.lock(); // 等待 mutex1,死锁发生
// 业务逻辑...
mutex1.unlock();
mutex2.unlock();
}
常见场景:数据库连接池(线程 A 持有连接 1 等待连接 2,线程 B 持有连接 2 等待连接 1)、车载系统资源竞争(线程 A 持有传感器 1 等待传感器 2,线程 B 相反)。
**考察重点:**针对死锁四个必要条件的解决方案,工程化的编码规范与工具应用,需结合 C++ 实践。
回答要点:
核心思路是'破坏死锁的任一必要条件',分 5 类具体措施,附代码示例:
**1.破坏'持有并等待'条件:**一次性申请所有资源,不部分持有;
实现:封装资源申请函数,同时申请所有需要的锁,失败则全部放弃;
示例:
// 一次性申请 mutex1 和 mutex2,用 std::lock 避免部分持有
void safeLock() {
std::lock(mutex1, mutex2); // 原子操作,要么同时获取,要么同时等待
std::lock_guard<std::mutex> lock1(mutex1, std::adopt_lock); // 接管已加锁的 mutex1
std::lock_guard<std::mutex> lock2(mutex2, std::adopt_lock); // 接管已加锁的 mutex2
// 业务逻辑...
}
**2.破坏'循环等待'条件:**按固定顺序申请资源(如按资源 ID 升序);
规则:约定所有线程申请锁时,先申请 ID 小的锁,再申请 ID 大的锁;
示例:
const int MUTEX1_ID = 1, MUTEX2_ID = 2;
// 线程 A、B 都按'小 ID→大 ID'申请
void threadA() {
if (MUTEX1_ID < MUTEX2_ID) { // 先申请小 ID 的 mutex1
mutex1.lock();
mutex2.lock();
} else {
mutex2.lock();
mutex1.lock();
}
// 业务逻辑...
mutex2.unlock();
mutex1.unlock();
}
void threadB() {
// 与线程 A 申请顺序一致,避免循环等待
if (MUTEX1_ID < MUTEX2_ID) {
mutex1.lock();
mutex2.lock();
} else {
mutex2.lock();
mutex1.lock();
}
// 业务逻辑...
mutex2.unlock();
mutex1.unlock();
}
**3.破坏'不可剥夺'条件:**允许超时释放资源(用带超时的锁申请);
实现:用 std::unique_lock 的 try_lock_for,超时未获取资源则释放已持有资源;
示例:
void threadA() {
std::unique_lock<std::mutex> lock1(mutex1);
// 尝试获取 mutex2,超时 100ms 未成功则放弃
if (!lock2.try_lock_for(100ms)) {
lock1.unlock(); // 释放已持有的 mutex1
return; // 退出或重试
}
// 业务逻辑...
lock2.unlock();
lock1.unlock();
}
**4.破坏'互斥'条件:**用无锁数据结构(如 std::atomic)或共享资源(如读写锁的读共享);
场景:读多写少的场景,用 std::shared_mutex 让多线程同时读,避免互斥;
示例:
std::shared_mutex rwMutex;
std::string data;
// 读线程(共享访问,不互斥)
void readThread() {
std::shared_lock<std::shared_mutex> lock(rwMutex);
std::cout << data << std::endl;
}
// 写线程(独占访问,互斥)
void writeThread() {
std::unique_lock<std::shared_mutex> lock(rwMutex);
data = "new data";
}
**5.工程化工具检测:**提前发现死锁风险;
工具:C++ 用 pstack(查看线程调用栈)、valgrind --tool=helgrind(检测数据竞争与死锁);
流程:测试环境运行服务,用工具监控线程状态,若发现'循环等待'调用栈则优化代码。
考察重点:C++ 线程安全的实现手段,不同机制的适用场景(如原子操作 vs 锁)。
回答要点:
分 6 类核心手段,结合后端场景举例:
互斥同步(阻塞式):
原子操作(无锁式):
线程局部存储(TLS):
避免共享状态:
内存屏障:
同步工具:
考察重点:C++ 标准容器的线程安全特性,实际使用中的同步策略(避免踩坑)。
回答要点:
先明确核心前提,再讲保障手段:
前提:C++ 标准库容器(vector、map、queue 等)本身不保证线程安全(标准未强制,避免性能开销),单个操作(如 push_back)也可能不是原子的;
保障线程安全的 3 种方式:
外部全局锁:
std::mutex mtx;
std::vector<int> vec;
// 写操作
void push(int val) {
std::lock_guard<std::mutex> lock(mtx);
vec.push_back(val);
}
// 读操作
int get(int idx) {
std::lock_guard<std::mutex> lock(mtx);
return vec[idx];
}
细粒度锁(分段锁):
使用线程安全容器(第三方):
关键注意事项:
即使单个操作(如 vec.push_back)是原子的,复合操作仍需同步(如'判断非空→取值':if (!vec.empty ()) { val=vec[0]; },empty () 和 [] 之间可能被其他线程打断,导致取到空值)。
考察重点:Spring AOP 的底层原理(动态代理)、核心概念与实际应用。
回答要点:
先讲核心概念,再拆实现流程,最后讲场景:
AOP 核心概念:
底层实现:
动态代理:Spring AOP 基于动态代理,分两种方式,按需选择:
JDK 动态代理:
IUserService proxy = (IUserService) Proxy.newProxyInstance(
classLoader,
new Class[]{IUserService.class},
(proxy, method, args) -> {
// 前置通知:日志记录
System.out.println("method " + method.getName() + " start");
Object result = method.invoke(target, args); // 执行目标方法
// 后置通知
System.out.println("method " + method.getName() + " end");
return result;
}
);
CGLIB 动态代理:
执行流程:
应用场景:日志记录、事务管理(Spring 声明式事务基于 AOP)、权限校验、性能监控。
考察重点:缓存常见问题的根因与解决方案,结合后端高并发场景(如电商)。
回答要点:
分三类问题,每类讲'原因 + 解决方案 + 示例':
| 问题 | 原因 | 解决方案 | 示例(Redis 缓存) |
|---|---|---|---|
| 缓存穿透 | 请求不存在的数据(如恶意查 item:999999),缓存 / 数据库都无,直击数据库 | 1. 缓存空值(设短期过期,如 5 分钟);2. 布隆过滤器(存所有存在的 key);3. 接口限流 | 缓存 item:999999 为 null,expire 300 |
| 缓存击穿 | 热点 key 过期(如 item:1001,百万用户同时查),缓存失效后直击数据库 | 1. 互斥锁(查缓存失效后加锁查库,其他线程等);2. 热点 key 永不过期(后台更新);3. 熔断降级 | 用 SETNX lock:item:1001 加锁,查库后 del 锁 |
| 缓存雪崩 | 大量 key 同时过期 / 缓存服务宕机,所有请求直击数据库 | 1. 过期时间随机化(如 1h+0-30min);2. 多级缓存(本地缓存 + Caffeine+Redis);3. 缓存集群(主从 + 哨兵);4. 服务熔断 | 给 key 加随机过期:expire item:1001 3600+rand()%1800 |
考察重点:项目落地经验、问题排查能力,避免空泛回答(需具体场景 + 数据)。
回答要点:
按'项目背景→问题现象→排查过程'结构回答,举例参考:
'我参与开发的'电商商品详情页'项目(后端用 Java+Redis+MySQL)已上线,峰值 QPS 5000+,日活用户 10 万 +。缓存穿透是上线后第 2 周遇到的:
现象:监控显示 Redis 命中率从 95% 骤降到 12%,MySQL CPU 使用率飙升到 90%,接口响应时间从 50ms 涨到 500ms,部分请求超时;
排查过程:
解决措施:给不存在的商品 key 缓存空值(expire 300 秒),同时用布隆过滤器预加载所有存在的商品 ID,拦截无效请求,1 小时后 Redis 命中率回升到 93%,MySQL CPU 降到 35%。'
考察重点:测试能力(本地模拟、压测、灰度验证),验证解决方案有效性的逻辑。
回答要点:
结合题 19 的缓存穿透场景,分 4 步讲模拟流程:
本地环境模拟:
压测环境验证方案:
单元测试覆盖:
// 预加载存在的商品 ID 到布隆过滤器
bloomFilter.add(1001);
bloomFilter.add(1002);
// 测试无效 ID(100000)被拦截
assertFalse(bloomFilter.contains(100000));
// 测试有效 ID(1001)通过
assertTrue(bloomFilter.contains(1001));
线上灰度验证:
考察重点:Linux 实操能力,与后端开发的结合度(开发、部署、调试)。
回答要点:
分 4 类核心场景,结合后端实际操作举例:
开发环境搭建:
服务部署与运维:
问题排查与监控:
日常操作:

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online