腾讯 云平台架构 二面

1.你这个项目是哪来的,网上找的吗

2. 你的网络框架大致架构是怎样的

3. epoll 能监听磁盘文件吗,为什么普通文件和 socket 在事件模型上表现不同

答案:epoll 从接口上看可以把很多 fd 加进去,但并不是所有 fd 都能像 socket 一样带来有意义的事件驱动收益。普通磁盘文件通常总是“可读”“可写”,因为它们不具备网络连接那种等待对端、等待缓冲区状态变化的语义,所以即使加入 epoll,也不会像网络 fd 那样随着数据到达不断产生值得等待的边缘事件。真正适合 epoll 的通常是 socket、pipe、eventfd、timerfd 这类“状态会变化”的 fd。如果业务是“收到网络请求后把数据写到某个文件”,一般不会去监听文件本身,而是监听网络连接,把文件写入放到异步线程里处理。

4. 如果一个服务既处理网络连接又处理大量文件写入,这两类 IO 应该怎么协同

答案:更合理的做法是把网络 IO 和磁盘 IO 解耦。网络侧继续走 epoll + 非阻塞 socket + 事件循环,磁盘写入侧走异步任务队列或者专门的刷盘线程。网络线程只负责收包、拆包、协议校验和把待写数据组织好,不直接做重磁盘操作。如果磁盘压力大,就通过高低水位做背压,比如落盘队列积压到一定程度后暂停连接读事件、拒绝低优先级请求或者做批量刷盘。真正线上容易出问题的不是“能不能写文件”,而是把慢磁盘路径混进了快网络路径,最后把整个事件循环拖慢。

5. ET 模式下读写一般为什么都要配合非阻塞

答案:因为 ET 只在状态从“不就绪”变成“就绪”的时候通知一次。如果某次收到可读事件后没有把内核缓冲区里的数据读到 EAGAIN,后面可能不会再提醒你;写也是类似,如果发送缓冲区有空间时你没尽量写完,后面不一定再收到新的边缘通知。所以 ET 模式通常要求 fd 设为非阻塞,然后在一次事件处理中循环读或循环写,直到返回 EAGAIN 为止。如果还是阻塞 IO,一旦读写卡住,线程就会被挂住,ET 的优势基本就没了。

代码:

char buf[4096];
while (true) {
    ssize_t n = recv(fd, buf, sizeof(buf), 0);
    if (n > 0) {
        // 处理数据
    } else if (n == 0) {
        // 对端关闭
        close(fd);
        break;
    } else {
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            break;
        }
        close(fd);
        break;
    }
}

6. 阻塞 IO 和非阻塞 IO 的区别,为什么很多高并发场景不直接用阻塞读写

答案:阻塞 IO 的特点是当前系统调用在条件不满足时会一直等,比如没数据就阻塞在 recv,发送缓冲区满了就阻塞在 send。非阻塞 IO 则是在条件不满足时立刻返回,通常配合 epoll 这类多路复用机制使用。高并发场景下如果每个连接都让线程阻塞等待,线程数、栈空间、上下文切换和调度成本都会很高,而且一个慢连接就可能长期占住一个线程。所以更常见的做法是少量 IO 线程用非阻塞 + 事件驱动管理大量连接,把真正耗时的逻辑再拆到业务线程池里。

7. 如果 send 返回了成功,是否说明这次一定把用户数据发到对端了

答案:不一定。send 成功通常只表示数据已经从用户态拷到了内核发送缓冲区,或者至少有一部分已经被内核接收准备发送,并不等于对端应用已经收到,更不等于已经处理完成。如果是阻塞 socket,也不代表一次调用一定把所有数据都写完;如果是非阻塞 socket,更可能只写了一部分,需要自己维护发送缓冲区和写偏移。所以网络编程里不能把一次 send 当成“一条消息发送完成”,而是要自己处理半包发送、重试和写事件续传。

代码:

ssize_t send_all(int fd, const char* data, size_t len) {
    size_t sent = 0;
    while (sent < len) {
        ssize_t n = send(fd, data + sent, len - sent, 0);
        if (n > 0) {
            sent += n;
        } else if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
            break;
        } else {
            return -1;
        }
    }
    return sent;
}

8. 如果连接很多,但服务已经处理不过来了,读事件还要不要继续开着

答案:一般不会无脑继续读。如果业务线程池、发送队列或者落盘队列已经明显积压,继续从 socket 把数据读进用户态,只会把压力从内核缓冲区转移到应用缓冲区,最后导致内存继续上涨。比较常见的做法是做背压:达到高水位后临时关闭该连接或该批连接的读关注,等消费回到低水位再恢复;严重时还会配合连接级限流、请求降级或者直接拒绝新请求。系统设计上更重要的是能在超载时“有序变慢”,而不是把所有流量都接进来然后一起拖死。

9. 手写一个循环队列,核心要注意什么

答案:循环队列最核心的是固定容量、头尾下标推进和判空判满条件。为了区分空和满,最简单的方式是浪费一个槽位:head == tail 表示空,(tail + 1) % cap == head 表示满。如果需要支持泛型对象,还要考虑对象构造析构、异常安全和拷贝成本。如果只是面试手写,通常先把整数版或模板版的 push/pop/front/empty/full 写清楚就够了。

代码:

#include <vector>
using namespace std;

class RingQueue {
private:
    vector<int> buf;
    int head, tail, cap;
public:
    RingQueue(int n) : buf(n + 1), head(0), tail(0), cap(n + 1) {}

    bool empty() const { return head == tail; }
    bool full() const { return (tail + 1) % cap == head; }

    bool push(int x) {
        if (full()) return false;
        buf[tail] = x;
        tail = (tail + 1) % cap;
        return true;
    }

    bool pop() {
        if (empty()) return false;
        head = (hea

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

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

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

全部评论

相关推荐

04-10 18:05
已编辑
深圳大学 Java
一面(放松版)实习LRU(下来后发现一摸一样的代码在本地可以运行,在如流的编译器不行,下次遇到这种可以申请本地试试)LRU为啥不用单向链表(删除保证o1)多线程如何改进(syc,reentrantlock,currenthashmap)reentrantlock比起syc的有点在哪(获取线程失败不卡死也就是不会死锁,更灵活可配置,锁中断,锁状态)cas聊聊(乐观锁的实现,重点在于无锁原子化操作)cas出现的问题(aba,单一变量原子性)aba问题有没有什么解决办法呢(版本号/时间戳,比较并且交换的过程不止比较目标值,还比较版本号)update...where&nbsp;name=a,update...where&nbsp;name=b,一个全表扫描,一个走了二级索引原因(a多)如果这两条update并发执行,发生死锁,什么原因(全表扫描是一行一行锁,锁主键就会隐式锁该行二级索引,二级索引更新是先锁索引再锁主键)openclaw和agent的区别在哪(前者是后者的实现,本地文件系统操作,im接入,skill优先)二面(燃尽版)-------------------------------------分割线--------------------------------------------实习explain可以看什么(key,type,row,extra)走了索引还是慢,什么原因(表太大,回表查询,orderby/groupby没走索引)extra可以看什么(这是个补充信息,看是不是走了索引,索引下推,还是说Using&nbsp;filesort)sql优化手段(建索引,小表驱动大表,分库分表,覆盖索引,关联字段索引)超大分页怎么解决(利用主键的范围查询,禁止跳超大页码)b+树(层高,范围查询)如果一个表没有主键id,它有聚簇索引吗(有,默认寻找非空唯一索引,没有的话会创建一个隐藏字段作为聚簇索引)唯一索引是主键索引吗(nope)索引下推是啥(走索引的时候顺带着筛选符合要求的结果,避免回表)索引合并是啥(走多次的单列索引进行交合操作)索引合并和联合索引哪个效果好(一般是联合索引)索引失效(最左前缀,计算,优化器判断走全表效果更优)什么情况下优化器判断走全表效果更优(表的数据量太小,查询数据占比太大需要大量回表,索引选择性不佳)数据库的隔离性怎么保证(锁,mvcc,隔离级别)数据库有什么锁(共享锁,排他锁,临键锁,元数据锁,表锁,页锁,行锁)乐观锁和悲观锁的使用场景(乐观锁读多写少,悲观锁读少写多)为啥乐观锁适合读多写少(写多的时候,会造成大量的cpu自旋)aba怎么解决(版本号,时间戳)元数据锁是啥(保护表结构的锁)元数据锁啥时候用到(修改表结构,执行select/update都会,防止查询的时候表结构改变)意向锁知道吗(表级别的锁,要加表锁的时候可以快速判断有没有行锁,不需要遍历整个表数据)临键锁怎么实现的知道吗(锁一般是mysql中定义的结构体实现,由于索引是有序的,直接锁住上一条记录和当前记录的间隙,左开右闭)java垃圾收集器(cms,g1)cms说说(从名字也看的出来标记清除算法,追求最短stw,也容易有内存碎片)g1说说(分Region,标记复制算法,可预测停顿,一般是堆内存&gt;=45%开始标记)g1工作流程(初识标记stw,并发标记,最终标记也就是stw,筛选回收stw)如果线上服务出现延时比较高的现象,你怎么排查(全局-&gt;sql,同步,redis,单点-&gt;GC,锁竞争,网络)访问慢可能会排查线程阻塞,那你怎么排查线程阻塞(jstack打印堆栈线程信息)java的两个集合类的根接口(单列collection,双列map)List下面的实现类(arraylist,linkedlist)ArrayDeque的底层数据结构(数组加上双指针)ConcurrentHashMap里什么时候会用CAS,什么时候会用synchronized(cas在目标桶为空的的时候尝试原子无锁插入,如果不为空那么就syc)java对象的组成结构有哪些(对象头,实例数据,对齐字段)mark&nbsp;word存的啥(hashcode,gcage,锁状态,类型指针)你刚才说对象头里有哈希值,那是不是每一种锁状态下,对象头里都会存哈希值?(不是,只有无锁才有,才有空间存储)那你说一说Java锁升级的完整过程。(无锁-&gt;单线程获取锁偏向锁,出现竞争升级为轻量级锁,当线程自旋次数过多或者竞争太大改为重量级锁)对象的哈希值是怎么计算的?(直接生成一个随机数)cpu飙到100%(top,top&nbsp;-hp,jastck配合grep,找到对应的代码)k组反转(秒了)三面(写题目被拷打版,一个没写对该死)-----------------------------------分割线------------------------------------个人情况mysql的索引锁在哪里(锁在叶子节点的索引字段)你刚刚说的是逻辑实现,物理实现呢(锁的内存结构体+全局的哈希索引表)这个全局索引表干嘛的(记录挂在当前索引的锁有哪些)输入url到返回结果的过程(1&nbsp;解析URL,2&nbsp;dns解析获取ip层数据可能是cdn的服务ip,3&nbsp;arp获取mac地址,4&nbsp;tcp三次握手,5&nbsp;tls握手,6&nbsp;发送根据网络模型构建的http请求报文,按照上述过程反向注意这个步骤cdn的命中,&nbsp;8&nbsp;浏览器解析html,如果是接口库数据,交给框架渲染)arp获取的mac地址是服务器的地址吗(下一跳的路由器mac地址)tcp有什么字段(妈的,当时脑子蒙了,一下没答出来。源端口,目标端口,序列号,确认号,标志位)socket的四元组和上面的关系是啥(四元组由tcp头部的端口字段和ip头部的ip字段标识)dns解析一定是基于udp的吗(不一定)流式传输的实现方式有哪些(websocket,sse,streamablehttp)mysql一定要开启事务才会死锁吗(是的,否则单条sql执行完就释放,不会长时间持有锁)写一个mysql的死锁(怎么开启事务给忘了。。。)给个代码优化(其实是写个策略模式,不会)去掉链表中的重复数(傻逼了有个地方当时没调好)反问:我都没反问,心态有点炸了,下次面试一定要语速慢一点镇定一点,反问可以问一下万能的ai取代问题
点赞 评论 收藏
分享
04-17 23:48
西北大学 Java
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

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