一片荒芜的面经
Mysql
Mysql 的事务隔离级别有哪些?
给定 MYSQL 的情景:通过非主键列更新数据时加锁的流程。
一列主键 a,一列 b,一列 c,通过 b 读出五行 c 的数据需要进行几次磁盘 IO?
索引用什么数据结构?
索引设计原则及优化手段有哪些?
B+ 树的优势是什么?为什么不用 B 树?
三大引擎(MyISAM, InnoDB 等)讲一下,各自的优势和区别是什么?
NoSQL 与 SQL 的区别?
数据库优化
对于高并发环境下数据库的优化都有哪些?
缓存、分库分表、悲观锁乐观锁和队列
为什么会引起索引失效?最左前缀法则?
从 b+ 树的角度去讲一讲,如果插入节点的时候达到了页上限,树结构怎么调整的?
乐观锁和悲观锁的区别是什么?
数据库的锁都有什么类型?
介绍一下行级锁和表级锁。
给出一个使用表级锁的场景例子。
在更新没有索引的非主键列时,加锁的流程是什么?
MySQL 聚簇与非聚簇索引的区别是什么?
联合索引的具体结构是什么?给定一个 SQL 语句,能否命中联合索引 (a,b,c)?
1 | select * from order where a=1 and c>2 order by b |
行锁、表锁、间隙锁的区别是什么?给出一个 SQL 语句,问会加上什么锁?
行锁什么时候会升级?
索引结构是什么?
MVCC(多版本并发控制)的工作原理是什么?
explain 语句,Using filesort 怎么优化
Redis
Redis 为什么快?
基于内存、单线程、key-value 而非传统的关系型数据库
还有其他让 Redis 快速的原因么?
基于事件的 reactor 模型加上 I/O 部分的多线程模型
Redis 的 RDB 持久化指令 BGSAVE 操作是 fork 一个子进程进行持久化的,为什么不创建一个子线程完成持久化?
看你用到了 redis,如果是多主节点,分布在不同的机房,同步规则是啥?
写入的时候是只写最近机房的集群吗?其他机房集群有写入功能吗?还是只是读取?
讲解 Redis 的架构(单 Reactor 单进程)。
Redis 为什么快?讲解不同版本的架构。
Redis 集群与哨兵模式的工作原理是什么?
Redis 项目服务器怎么实现的,集群怎么做的
如果新增一个 Redis 服务器节点,什么情况
Redis 的过期删除与内存淘汰
Redis 的 zset 数据结构底层实现
Redis 的 AOF、RDB 和混合备份模式
消息队列
消息队列有什么作用?
如果使用消息队列怎么解决返回消息同步性的问题?
网络编程
TCP 和 UDP 的区别是什么?
TCP 握手和挥手过程中的 TIME_WAIT 和 CLOSE_WAIT 分别在哪个阶段?
epoll 原理是什么?
TCP 四次挥手的状态有哪些?
I/O 多路复用:select、poll、epoll 的区别是什么?
socket 底层的关键字你有使用过吗,协议怎么设计的
TCP 拥塞控制过程,TCP 拥塞控制是降低发包频率还是减小发送窗口
内核网络收包过程,这个过程中中断处理的作用,后续协议栈是怎么处理的
TCP 头部字段有哪些
操作系统
操作系统的用户态和内核态有什么区别?
除了 IO 操作什么时候会访问内核态?
匿名管道和文件管道的区别?
fork 的时候是否会创建 fd?
进程间通信方式有哪些?哪个比较快?共享内存的方式在哪里见到过?不同主机之间一般是怎么通信的?
Linux 内存模型是怎样的?malloc 做了什么?
堆区和栈区的作用是什么?
多线程同步机制有哪些?
进程、线程、协程的区别是什么?
为什么进程之间切换开销大于线程之间切换开销?
I/O 模型有哪些?
提到 Reactor 和 Proactor 模式。
进程的内存布局
Open 和 fopen 区别
linux 上电启动整个系统的过程
软中断和硬中断
进程上下文切换过程
系统调用和进程上下文切换区别
Linux IO 栈了解么
缺页异常
异常,中断区别
进程调度算法有哪些,进程的优先级反转是什么
linux 下 C++ 线程怎么是实现的
场景题:
电商下单策略,在抢购情景下如何实现数据库操作?(针对一件商品和一千件商品的不同策略)
在抢购情景下,你会用什么方法实现数据库操作?
如果有两个不同的商品抢购场景(一件商品和一千件商品),你会怎么设计策略?
设计一个计数系统,比如统计一个文章的点赞量,转发量,收藏数。怎么设计这个系统,怎么能保证高并发的数据查询,怎么能保证系统的健壮性
数据库方面考虑索引设计,分表设计,事务方面考虑下,缓存方面考虑一些数据结构帮助节省空间,比如用 bitmap 之类的,然后缓存和数据库之间的一致性设计,还有缓存上的限流检测要考虑下,高并发下缓存中的分布式锁怎么设计,然后 mq 可以怎么用
场景题:设计一个广告收费系统,考虑到高可用性和流量很大的情况。削峰之后 MySQL 压力依然很大怎么办?
设计一个贴吧系统,在某个贴吧做活动期间发帖量很高时应如何处理?
同步处理请求、异步落地、同步缓存发帖信息、异步 MQ 落地。
回答了 AQS, 先写库->改成缓存告诉前端实现了—>后台异步调用实际的发布情况。
如果某个贴吧发帖量相当高,二级消费者消费不过来导致阻塞其他用户发帖怎么办?
提案包括:topic 分区、物理隔离各个贴吧;单点限流(用户体验差被否定);改二级消费者的事件驱动型消费为批处理消费,一次消费多条数据增加 MySQL 吞吐量,同时动态扩容消费者。
发消息的话,如果某一个贴吧很多,怎么做到该贴吧慢慢发布,但是不影响其他贴吧。
当时回答了很多想法,比如 Topic、分区,但是都被面试官指出不符合不会影响其他贴吧的发布速度。最后回答是快慢队列,首先尝试慢队列发消息,同时维护一个拥挤贴吧的列表。如果慢队列消息超过阈值,不在拥挤贴吧队列的消息开始往快队列发送,拥挤贴吧的消息继续发送在慢队列解决这个问题
如果某一个贴子突然热度很高,怎么提高吞吐量、性能,不至于开销太多
面试官说主要是网络、带宽和其他开销,业务内部开销不会很大,主要是图片、资源的后台开销,要怎么优化?我说的后端给 redis 热点帖子存资源
怎么保证刷库的有序性?
那就只能用消息队列发消息了,以前端点击或者后端时间为主?
一个论坛系统,deepseek 能用在哪些地方?
a、帖子总结
b、个性化搜索
c、最关键,对于帖子的问题或者引导 deepseek 能给出一个回答。
优势在于 参与人去责任化,也定下了贴子互动引导的标杆,保证帖子热度高的同时,不至于引导跑偏,而且开发人员、用户都不会担心需要为此负责(因为是 ai 说的,作为参考就很好,不代表任何人的态度),因为 deepseek 可以说是机器的回答,更可以说代表所有人的大众意志,因为数据都是有用户填入的
设计一个微信朋友圈系统,要求能够查看朋友发的所有朋友圈,并按照时间排序。
每个用户维护一个 zset,保存好友的朋友圈内容和时间戳,同时动态维护这个 zset。当有人发布新朋友圈时,更新其所有好友的 zset。
如果一个人的朋友非常多,一次性更新大量 zset 如何保证全部更新成功或失败?
用户发布朋友圈时不直接更新 Redis,而是先落地数据库,同时发送异步消息通过 MQ 机制更新 Redis,依赖 MQ 的重试机制确保数据一致性。
如何实现幂等性?
利用 zset 的天然幂等特性。
对于类似微博的朋友圈,有很多大 V 用户,他们发布消息时也会异步更新所有关注者吗?
异步更新加主动拉取相结合。对于粉丝量特别大的大 V,不采用异步更新 Redis 的方式,而是在发布朋友圈时主动缓存到 Redis 中。用户浏览朋友圈时,维护一个大 V 的 keyset,用户的 zset 和 keyset 进行归并。
你现在看到的代码考核系统,允许输入任何代码,假设你来维护这套系统,需要考虑什么安全问题;注意它可以提交运行,运行肯定在服务器运行
分布式系统设计
设计一个配置中心的架构,包括哪些模块?如何实现推送和拉取轮询?设计 API 时应考虑哪些方面?
大表治理怎么做?表空洞解决方法是什么?删除大量数据时如何减少 IO 损耗?
Redis 集群如何决定命中哪一个 Redis 节点?集群通信的基本流程是什么?
大 key 问题的解决方案是什么?
服务注册是如何实现的?
微服务的优点和缺点是什么?
使用过哪些分布式组件?
如何缓解写频繁的问题?
回答了构建缓存,进一步提到使用 RocksDB(LSM 树架构的数据库)。
设计 12305(假设为类似铁路售票系统),在分布式高并发场景下,如何处理这么高的并发量?
- 限流:通过限制单位时间内请求的数量来保护系统。
- Hash 切片:利用一致性哈希算法对数据和服务进行分区,以分散负载。
- 分区:数据库分库分表,减少单点压力。
- 缓存:使用 Redis 等缓存技术减轻数据库负担。
Redis 缓存 MySQL,怎么处理写操作?
需要解决缓存与数据库之间的一致性问题。可以采用写操作直接更新数据库后删除缓存中的对应项,或者使用消息队列异步更新缓存等方式确保数据同步。
怎么处理脚本抢票?
可以通过验证码、图形验证、滑块验证等方式增加抢票难度,防止脚本自动执行;同时结合限流策略和排队机制保证公平性。
怎么处理并发购票?
处理并发购票可以通过多种方式:
- 使用乐观锁或悲观锁控制对共享资源(如票务库存)的访问。
- 实现基于版本号或时间戳的 CAS(Compare And Swap)机制避免超卖。
- 结合分布式锁服务如 Zookeeper 来协调不同实例间的操作顺序。
- 利用队列机制对购票请求进行排序,确保同一时间内的请求有序处理,避免冲突。
面对一个场景,我们的服务器宕机情况很多,有已知的问题和未知的问题,你会选择什么样的方案来处理?尽可能多的说出你的解决思路面对已知的问题尽量去解决它未知的问题
服务器做好备份,流量控制,用户的这个服务锁死在一个容器里面,如果挂掉切换容器
目前有一个高性能的计算引擎,内存有限,可以处理 1 亿个数据计算,面对两种情况:小数据计算,大数据(类似 100 亿左右的),该怎么处理这个问题?尽可能多的说出你的解决思路
尽可能多的说出一些提高项目性能的方案,减少代码错误,内存泄漏等情况?
代码规范,单元测试用例,代码评审,测试,项目文档等等
编程题目
代码题 53.最大子数组和
另一棵树的子结构
算法:两两反转链表。
算法:两个 list 求交集,扩展是 list 无限容量的情况怎么办。
手撕 LRU 算法。
实现一个支持 TTL(Time To Live)和支持任意类型值的 K-V 存储数据库。
解决方案包括:map + 自定义 entry + 懒惰删除 + 定期任务 + 线程池。
针对重复字符串实现一种优化长度转换。比如 IIISSTTAAAA 转变为 I3S2T3A4。如果压缩以后反而更长,返回原来的。(还需要考虑 ABABABAB 可以优化成 (AB,4),这就需要说出 LZ77 压缩算法的思想了,但是不会)
Java 并发编程
CompletableFuture 在实习中是如何使用的?
线程池的核心参数和任务执行流程是什么?线程是一开始就有的吗?
垃圾回收器相关知识。
C++
拷贝构造函数的参数可以不加引用吗?为什么?
拷贝构造函数的参数通常需要是引用,否则如果传值的话会导致无限递归。因为如果不使用引用作为参数,传入的实参会调用拷贝构造函数来创建一个临时对象,这个过程本身又会再次调用拷贝构造函数,从而导致无限递归。
定义一个 static 成员变量。该怎么写?
定义一个 static
成员变量需要在类声明之外进行初始化(对于静态成员变量来说),同时在类内部声明其类型和名字。例如:
1 | class MyClass { |
这里的关键点在于,static 成员变量的定义必须只能出现一次(遵循 C++ 的一次定义原则,ODR),以避免重复定义错误。如果尝试在类定义中直接初始化一个非整型的 static 成员变量,或者在多个源文件中对其进行定义,则会导致链接错误。通过在类外部定义并初始化 static 成员变量,可以确保在整个程序中只有一个这样的变量实例存在,并且可以在不同的编译单元之间共享。
对于静态常量成员,特别是整型,C++ 允许在类体内直接初始化,但这是个例外情况:
1 | class MyClass { |
写一个单例模式
单例模式确保一个类只有一个实例,并提供一个全局访问点。这里是一个简单的线程安全的单例实现示例:
1 | class Singleton { |
在 C++ 中实现单例模式时,可以使用局部静态变量的方式(如前面提供的示例),这种方式在 C++11 标准及其后版本中是线程安全的,不需要额外的双重检查锁定模式(Double-Check Locking Pattern)。这是因为 C++11 标准规定了初始化局部静态变量时必须是线程安全的。具体来说,当多个线程试图同时首次访问这个局部静态变量时,标准保证只有一个线程会执行初始化代码,其他线程将被阻塞直到初始化完成,从而避免了竞态条件的发生。
C++11 之前的写法
模板类 A 中有一个 static 变量 x,那么该程序中变量 x 的实例有几份?
模板类中的静态成员变量按实例化类型独立存在。这意味着每一种不同的模板参数类型都会对应一个唯一的静态变量实例。比如 A<int>
和 A<double>
将会有各自的 x
实例。
shared_ptr<T>
怎么实现一个普通指针的 const T*
效果?
要使 shared_ptr<T>
表现得像 const T*
,你可以创建一个指向 const T
的 shared_ptr
,即 shared_ptr<const T>
。这样就保证了通过这个智能指针不能修改所指向的对象的数据(除非通过非 const 的方式访问)。
const T*
与 T* const
与 const T* const
区别不再赘述,看 shared_ptr 就很容易看出来。
C++11 为什么引入 nullptr?
C++11 引入 nullptr
是为了明确区分指针和整数类型的 NULL
,并解决了一些由于 NULL
被定义为 0
导致的重载决策问题。nullptr
是一个专门用于表示空指针的字面量,其类型为 std::nullptr_t
。
C++11 为什么引入 enum class?
enum class
提供了强类型枚举,解决了传统枚举的一些问题,如作用域污染(传统枚举项被提升到包围它的作用域)和隐式转换到整型的问题。enum class
需要显式的访问枚举成员且不允许隐式转换为整型。
C++11 的 thread_local 有没有使用过?
thread_local
关键字用于声明某个对象具有线程存储期,意味着每个线程都有自己的变量副本。虽然这里没有具体使用经验分享,但在多线程编程中有重要用途,特别是在线程本地存储场景下。
std::thread 使用 lambda 做回调,有没有什么注意事项?
使用 std::thread
和 lambda 时需要注意捕获列表的正确性,确保所有被捕获的变量在线程执行期间都是有效的。此外,避免捕获局部变量的引用或指针,除非确定它们在整个线程生命周期内都有效。
你觉得 lambda 是语法糖吗?
Lambda 表达式不仅简化了代码编写,而且允许更灵活地定义短小、内联的函数对象,提高了代码的可读性和效率。因此,它不仅仅是语法糖,而是提供了新的编程能力和优化机会。
STL 中的算法使用 lambda 表达式除了写起来方便以外还有没有其他好处?
正如提供的参考材料所述,lambda 表达式能让编译器对算法函数和 lambda 表达式内的代码逻辑整体做优化,这有助于提高性能,而不仅仅是书写上的便利。
谈一谈你对 zero overhead(零开销原则)的理解
Zero Overhead Principle 指的是 C++ 的设计哲学之一,强调不应为未使用的功能付出代价(无论是时间还是空间上)。任何高级语言特性只有在使用时才会产生成本,未使用时不增加额外开销。
你觉得 STL 的出现是否在一定程度上是“反”面向对象的编程范式?
STL 并不是“反”面向对象编程范式,而是提供了一种通用编程的途径,强调数据结构与算法的分离,以及泛型编程的重要性。STL 与 OOP 可以很好地结合使用。
core dump 的原因有哪些?
Core dump 发生的原因包括但不限于:非法内存访问(如访问空指针)、栈溢出、除以零等运行时错误。这些问题通常是由于程序错误引起的。
GDB 怎么调试查看 core dump?
有使用过 noexcept 关键字吗?它有什么使用功能?
noexcept
标识了一个函数承诺不会抛出异常,这对于优化调用链和资源管理特别有用。编译器可以利用这一信息进行优化,比如选择更适合的移动操作而非拷贝操作。
内存泄露的常见原因有哪些?怎么排查内存泄露?
常见的内存泄漏原因包括忘记释放动态分配的内存、循环引用(特别是在使用智能指针时)等。排查内存泄漏可以通过工具如 Valgrind 或 AddressSanitizer,也可以通过仔细检查代码的内存管理部分。
怎么分析代码,找出性能优化点?
分析代码性能通常涉及使用性能分析工具(profilers)识别瓶颈,检查热点代码段。此外,了解算法的时间复杂度和空间复杂度也很关键。优化可能涉及到改进算法、减少不必要的计算、利用缓存友好型数据结构等方面。
C++ 中怎么对内存泄漏情况检查?怎么避免内存泄漏的情况?
gcc 启用 asan 标志检查;重载 new 和 delete;使用 valgrind 工具尽量使用智能指针代替普通指针;使用 RAII 的思想编写类;预先设定好内存池,使用内存池管理内存(他好像还觉得不满意,让我答出点别的标记清除法(参考其他语言 gc 的方法;公司有代码审查工具,可以自动检查野指针,空指针,内存泄漏等高风险问题,有问题会给出代码高风险预警
C++ 的 mutex 源码有看过么,底层怎么实现的
C++ 的 share_ptr 源码有阅读过么,底层怎么实现,有没有使用锁
不需要锁,记录 _uses 和 _weaks 两个原子变量即可,两个变量可以独立增减
C++ 的编译过程
编译遇到 undefined symbol 情况你怎么解决的
你 makefile 写的多么
遇到段错误你怎么排查问题的
C++ vector 相关,内存释放怎么做
我们常用的操作 clear() 和 erase(),实际上只是减少了 size(),清除了数据,并不会减少 capacity,所以内存空间没有减少。那么如何释放内存空间呢,正确的做法是 swap() 操作。
- 可以利用 swap() 方法去除 vector 多余的容量:vector
(x).swap(x);其中,x 是当前要操作的容器,T 是容器的类型。 - 利用 swap() 方法清空 vector 容器:当 swap() 成员方法用于清空 vector 容器时,可以套用如下的语法格式:vector
().swap(x)。 https://zh.cppreference.com/w/cpp/container/vector/shrink_to_fit
手撕实现 string
手撕实现完美转发
weak_ptr 使用场景(请说出除了解决循环引用的以外的
动态链接库与静态链接库的理解
静态链接库(Static Library) 和 动态链接库(Dynamic Link Library, DLL 在 Windows 上,或 Shared Object, .so 在 Linux 上) 是两种不同的代码复用方式。
- 静态链接库:在编译期间将库的代码复制到最终生成的可执行文件中。使用静态库的优点是部署简单,因为所有需要的代码都在一个文件里;缺点是如果多个程序使用相同的库,每个程序都需要包含一份该库的副本,这会增加磁盘空间和内存使用的负担。
- 动态链接库:在程序运行时才加载到内存中。这样可以减少程序大小,并允许多个程序共享同一份库的代码副本,节省内存和磁盘空间。然而,动态链接库需要确保在运行时能够访问到这些库文件,因此对环境有一定的依赖性。
动态链接库加载过程
- 装载(Loading):操作系统根据应用程序的需求,找到并装载所需的 DLL 文件。Windows 通过
LoadLibrary()
函数,Linux/Unix 系统则通过dlopen()
来实现。 - 符号解析(Symbol Resolution):查找并绑定应用程序引用的所有外部符号(如函数名、变量等)。这通常涉及搜索导入地址表(Import Address Table, IAT),以及定位 DLL 导出符号表中的相应条目。
- 重定位(Relocation):调整代码段中的地址以适应当前进程地址空间布局的变化。
- 初始化(Initialization):执行 DLL 入口点处指定的初始化代码(如有)。在 Windows 上,这是
DllMain
函数;在 Linux 上,通常是.init
节中的代码。 - 调用(Invocation):一旦完成上述步骤,程序就可以开始调用 DLL 中的函数了。
如何避免某些函数被暴露给外部使用
当你创建一个动态链接库时,可能不希望所有的函数都被外部调用。以下是几种保护方法:
- 内部链接(Internal Linkage):对于 C++ 来说,可以使用
static
关键字声明函数为文件作用域内的局部函数,这意味着它们不会出现在导出符号表中,从而无法从其他翻译单元访问。这种方法适用于非成员函数。1
2
3static void internalFunction() {
// 这是一个仅限于本文件内使用的函数
} - 匿名命名空间(Anonymous Namespace):在 C++ 中,可以利用匿名命名空间来限制函数的作用范围,使其不可见于外部。
1
2
3
4
5namespace {
void hiddenFunction() {
// 此函数只能在定义它的编译单元中访问
}
} - 模块化设计:将不想公开的函数放在类的私有部分或者作为私有静态成员函数,这样即使是在同一个动态链接库内部也无法直接调用它们,除非通过公共接口间接访问。
- 编译器特定指令:某些编译器提供特定的指令或属性来控制哪些符号应该被导出。例如,在 Windows 下的 MSVC 编译器中,可以通过
__declspec(dllexport)
导出函数,同时不标记那些不需要导出的函数。而在 Linux 下,GNU 编译器支持__attribute__((visibility("hidden")))
这样的属性来隐藏符号。- Windows 示例:
1
2__declspec(dllexport) void ExportedFunction() {}
void LocalFunction() {} // 不会被导出 - Linux 示例:
1
2__attribute__((visibility("default"))) void exportedFunction() {}
__attribute__((visibility("hidden"))) void localFunction() {}
- Windows 示例:
通过上述方法之一或组合使用,你可以有效地控制动态链接库中哪些函数对外部可见,哪些保持私密状态。
dynamic_cast 的原理
typeid 获取的,typeid 又通过虚函数表确定。因此需要基类起码有个虚函数才能转,而子类不 override 也能转,反正子类肯定保存了基类的虚函数表,所以可以判断从属关系。
除了 typeid 获取的 rtti 类型,还有哪些类型
静态多态类型(包括手写和一大票模板类型如 decltype)
C++ 函数重载原理,为什么 C 语言里面没有重载
C++ 的名字修饰(Name Managling)在编译时会对每个函数名进行编码,将函数名和其参数类型等信息结合在一起生成一个唯一的标识符。C 就没有了,C 只会将函数名加入全局的符号表里。
C++ 加 const 能不能构成重载
- 修饰函数返回值,不构成。函数返回值不能作为区分函数重载的因素。不过返回常引用还是挺常见的,一般来说肯定还要再给成员函数体加个 const,所以重载是在成员函数体那块而不是返回值。
- 修饰函数参数,对引用、指针构成,对值不构成,因为值传递都是复制,加不加 const 都没意义,改不到原主头上。
- 修饰成员函数,构成。但是良好实践是 const 和非 const 成员函数(比如
As()
和AsMut()
)即使差不多的作用也要取个不同的名字以防误用。
C++ 内联函数的作用
在 C++ 中,inline
关键字主要用于建议编译器将特定函数的代码“内联”展开到调用该函数的地方,而不是通过常规的函数调用机制(如保存寄存器状态、跳转到函数入口地址等)。这样做的主要目的是为了减少函数调用带来的开销,特别是在循环内部或频繁调用的小函数中。
什么情况下不进行内联
尽管inline
是一个有用的优化工具,但它并非总是适用。以下是几种情况,在这些情况下编译器可能会选择忽略inline
建议:
- 复杂函数:如果一个函数包含复杂的逻辑或大量的代码行,将其内联可能导致代码膨胀,反而影响性能。
- 递归函数:由于递归函数会反复调用自身,无条件地内联会导致无限复制代码,因此不会被内联。
- 调试模式下:在调试模式下,为了便于调试,编译器通常不会执行内联操作,以便保留原始的函数结构和调用关系。
- 虚拟函数:虚函数的调用机制依赖于虚表(vtable),这意味着在运行时才能确定具体调用哪个版本的函数,因此不适合内联。
- 跨翻译单元的函数:如果一个函数声明为
inline
,但是定义位于不同的源文件中,则无法保证所有翻译单元都能看到相同的函数定义,这违反了 C++ 的一致性要求,因此这种情况下也不能内联。 - 编译器限制:某些编译器可能有自己的规则来决定哪些函数应该被内联。例如,有些编译器可能对函数大小有限制,超过这个大小的函数就不会被内联。