腾讯 C++开发 一面

PCG

1. 自我介绍

2. 网络服务中大量 CLOSE_WAIT 和 TIME_WAIT 分别是什么原因,会导致什么问题,怎么解决

答案:CLOSE_WAIT 通常表示对端已经关闭连接,本端收到了 FIN,也回了 ACK,但本端应用层还没有调用 close()。如果大量 CLOSE_WAIT 堆积,一般说明服务端代码没有正确关闭 socket,可能是读到 0 后没有 close,异常分支漏 close,或者连接对象生命周期管理有问题。它会导致 fd 泄漏,最终可能出现 too many open files

TIME_WAIT 一般出现在主动关闭连接的一方。它的作用是保证最后一个 ACK 能被对端收到,并让旧连接中的延迟报文在网络中自然消失。大量 TIME_WAIT 不一定是 bug,但如果短连接很多,会占用端口和连接表资源。

解决上,CLOSE_WAIT 要从代码里查连接关闭路径,保证读到 EOF、异常、超时、协议错误时都能释放 fd。TIME_WAIT 要看业务是否能复用长连接、连接池,减少频繁建连断连;必要时调整系统参数,但不能把系统参数当成根因解决。

代码:

ss -ant | awk '{print $1}' | sort | uniq -c
ss -antp | grep CLOSE-WAIT
lsof -p <pid> | wc -l
cat /proc/sys/fs/file-nr

3. 创建 socket 时常见参数有哪些,SO_REUSEADDRSO_REUSEPORTTCP_NODELAY 分别解决什么问题

答案:创建 TCP socket 一般会经历 socket()bind()listen()accept()socket(AF_INET, SOCK_STREAM, 0) 中,AF_INET 表示 IPv4,SOCK_STREAM 表示 TCP 字节流,第三个参数通常填 0 表示使用默认协议。

SO_REUSEADDR 主要用于允许地址复用,常见场景是服务重启时端口还处于 TIME_WAIT,设置后可以更快 bind 成功。SO_REUSEPORT 可以让多个进程或线程绑定同一个端口,由内核做连接负载分发,适合多进程网络服务。TCP_NODELAY 用来关闭 Nagle 算法,减少小包延迟,适合实时交互场景,但可能增加小包数量。

还要关注 SO_KEEPALIVE、收发缓冲区大小、非阻塞模式、listen backlog,这些参数都会影响服务在高并发下的行为。

代码:

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <unistd.h>

int createServerSocket(int port) {
    int fd = socket(AF_INET, SOCK_STREAM, 0);

    int on = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on));
    setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));

    int flags = fcntl(fd, F_GETFL, 0);
    fcntl(fd, F_SETFL, flags | O_NONBLOCK);

    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;

    bind(fd, (sockaddr*)&addr, sizeof(addr));
    listen(fd, 1024);

    return fd;
}

4. TCP 监听队列里半连接队列和全连接队列有什么区别,backlog 参数到底控制什么

答案:TCP 服务端 listen 后,内核会维护连接相关队列。半连接队列存放已经收到 SYN、并回复 SYN+ACK,但三次握手还没完全完成的连接。全连接队列存放三次握手已经完成,但还没被应用层 accept() 取走的连接。

backlog 在不同内核版本里含义有细节差异,但实际排查时要同时关注半连接队列、全连接队列以及系统参数,比如 somaxconntcp_max_syn_backlog。如果应用层 accept 太慢,全连接队列可能溢出;如果遭遇 SYN flood,半连接队列可能被打满。

全连接队列溢出会导致客户端连接失败或重传,服务端可能看不到明显应用日志。排查时不能只看应用层 QPS,还要看内核统计。

代码:

sysctl net.core.somaxconn
sysctl net.ipv4.tcp_max_syn_backlog
ss -lnt
netstat -s | grep -i listen

5. MySQL 中 B 树和 B+ 树有什么区别,为什么 InnoDB 索引常用 B+ 树

答案:B 树的非叶子节点和叶子节点都可以存数据,而 B+ 树通常只有叶子节点存完整数据,非叶子节点主要存 key 和子节点指针。B+ 树的叶子节点之间还有链表连接,非常适合范围查询,比如 betweenorder by、分页扫描。

InnoDB 使用 B+ 树,一个重要原因是磁盘 IO 友好。非叶子节点不存完整行数据,可以容纳更多 key,树高更低,查询时磁盘访问次数更少。另外叶子节点有序链表让范围查询效率更高。如果用普通二叉树或红黑树,树高太高,不适合磁盘页访问模型。

聚簇索引的叶子节点存整行数据,二级索引叶子节点存主键值,所以二级索引查到主键后,如果查询列不在索引里,还需要回表。

6. InnoDB 聚簇索引和二级索引有什么区别,为什么建议主键量自增

答案:InnoDB 的表数据本身按照主键组织,主键索引就是聚簇索引。聚簇索引叶子节点存放完整行数据。二级索引的叶子节点不直接存完整行,而是存索引列和主键值。如果通过二级索引查找非覆盖字段,需要先查二级索引拿到主键,再回到聚簇索引查整行,这就是回表。

主键建议尽量短、稳定、递增。短是因为所有二级索引叶子节点都会存主键,主键太大会放大索引体积。稳定是因为主键变化会导致数据位置变化。递增是因为随机主键会导致页分裂和页内碎片,影响插入性能。不过自增主键也有热点写入问题,在极高并发写入场景下需要结合业务分库分表或其他 ID 生成策略。

7. MySQL 联合索引的最左前缀原则是什么,哪些情况会导致索引失效

答案:联合索引比如 (a, b, c),查询条件要从最左边的 a 开始连续使用,才能较好利用索引。如果直接查 bc,通常无法完整利用这个联合索引。遇到范围查询时,范围字段后面的字段一般不能继续用于有序定位,只能做过滤。

常见索引失效情况包括:对索引列做函数计算、隐式类型转换、前置模糊匹配、使用不符合索引顺序的条件、低选择度字段单独建索引、or 两侧索引不一致等。但“索引失效”不是绝对说法,最终还要看优化器选择,可能因为数据量小、选择度差,优化器认为全表扫描更便宜。

代码:

CREATE INDEX idx_user_scene_time ON t_event(user_id, scene_id, create_time);

-- 可以较好利用 user_id、scene_id、create_time
SELECT * FROM t_event
WHERE user_id = 1001
  AND scene_id = 8
  AND create_time >= '2025-01-01';

-- 不符合最左前缀,通常无法有效使用该联合索引
SELECT * FROM t_event
WHERE scene_id = 8;

-- 对索引列做函数,可能导致无法走索引
SELECT * FROM t_event
WHERE DATE(create_time) = '2025-01-01';

8. MySQL 慢查询怎么排查,EXPLAIN 里重点看哪些字段

答案:慢查询排查一般先打开慢查询日志,确认 SQL、耗时、扫描行数和执行频率。然后用 EXPLAIN 看执行计划,重点关注 typekeyrowsExtratype 越接近 const/ref/range 通常越好,如果是 ALL 就是全表扫描。key 表示实际使用的索引,rows 是优化器估算扫描行数,Extra 里如果出现 Using filesortUsing temporary,要重点关注排序和临时表成本。

慢查询不一定只是没索引,也可能是索引选择度太差、返回数据太多、分页 offset 太大、锁等待、buffer pool 命中率低、SQL 写法导致无法走索引。优化时不能只靠感觉加索引,要结合数据分布和执行计划。

代码:

EXPLAIN SELECT id, title
FROM t_article
WHERE author_id = 100
  AND status = 1
ORDER BY publish_time DESC
LIMIT 20;

SHOW VARIABLES LIKE 'slow_query_log';
SHOW VARIABLES LIKE 'long_query_time';
SHOW PROCESSLIST;

9. MySQL 中 MVCC 是怎么实现的,快照读和当前读有什么区别

答案:InnoDB 的 MVCC 依赖隐藏字段、undo log 和 ReadView。每行记录里会有事务 ID 和回滚指针,回滚指针指向 undo log 中的历史版本。快照读执行时会生成 ReadView,根据活跃事务列表、最小活跃事务 ID、下一个事务 ID 等信息,判断某个版本对当前事务是否可见。如果当前版本不可见,就沿着 undo log 找更老的版本。

普通 select 通常是快照读,不加锁,读的是符合可见性规则的历史版本。select ... for updateup

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

C++ 常考面试题总结 文章被收录于专栏

本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务