csig_腾讯云(一面)
面经
C++ 八股
- 继承和多态这种面向对象的设计?(把共同点抽出来复用,把不同点留给子类自己实现)
- 多态解决了什么问题,
C++为什么要使用多态?- 多态:主要用于对同一个接口,进行不同的实现;
C++使用多态主要是解决代码复用;
- 不用继承,多态的这种设计应该怎么做呢?
- 静态多态:模板、函数重载、工厂函数
- 多态的虚函数可以是内联函数嘛?
- 虚函数可以是内联函数,类内定义的成员函数默认就有
inline属性,在语法上完全没问题; - “能声明成
inline” 和 “调用时真的被内联展开” 不是一回事。inline本质上只是给编译器一个建议 - 虚函数在通过 父类指针/引用调用时,是 运行时动态绑定,编译器在 编译阶段 通常不知道最终会调用哪个版本,所以一般不能直接内联;
- 虚函数可以是内联函数,类内定义的成员函数默认就有
- 纯虚函数是干嘛的?
- 这个行为必须有,但父类没法给出通用实现;
- 纯虚函数不可以被实例化;
- 强制子类实现接口,子类没有实现父类的纯虚函数,那子类自己也还是抽象类。
STL了解吧,一起讲map和unordered_mapmap- 底层使用 红黑树;
- 按
key是有序的; - 插入和查找的复杂度
O(N)
unordered_map- 底层使用 哈希表;
- 按
key是无序的; - 插入和查找的平均复杂度
O(1)
- 是线程安全的嘛?(
STL中的实现不是线程安全的,需要自己进行加锁) - 有哪些手段实现访问
STL是线程安全的?- 外部加锁
- 通过原子变量实现无锁
STL
- 分别讲解一下 自旋锁 和 读写锁 的使用场景?
- 自旋锁:
- 使用场景:临界区非常短、锁持有时间很短、竞争不重、线程很快就能拿到锁;
- 读写锁:
- 使用场景:读多写少,多个线程可以同时拿共享读锁,但写锁必须独占。
- 自旋锁:
项目
- 进程、线程、协程的区别?
- 进程:资源分配的基本单位
- 拥有独立的资源:文件描述符、虚拟内存地址空间、页表;
- 切换开销最大;
- 适合强隔离任务,防止以后模块影响另一个模块;
- 线程:
CPU调度的基本单位- 共享进程的资源:地址空间、堆、全局变量、打开的文件
- 自己的资源:程序计数器、线程栈、寄存器
- 切换开销小于进程;
- 适合利用多核并行执行任务;
- 协程:用户态线程,程序员自己进行保存协程的上下文
- 共享线程的资源:一个线程里可以跑很多协程;
- 自己的资源:协程栈、程序计数器、寄存器;
- 切换开销最小;
- 适合大量
I/O并发任务;
- 进程:资源分配的基本单位
- 进程、线程、协程的上下文是什么呢?
- 进程:
CPU执行现场:- 程序计数器
- 通用寄存器
- 栈指针
sp - 内核栈信息
- 进程运行环境:
- 虚拟内存空间
- 页表项
- 管理的文件描述符
- 进程控制块
PCB
- 线程:
- 共享进程的资源
- 私有资源:
- 程序计数器、线程栈、寄存器、线程控制块
pcb
- 程序计数器、线程栈、寄存器、线程控制块
- 协程:
- 共享进程/进程的资源
- 私有资源:
- 协程栈、程序计数器、寄存器
- 进程:
- 有栈协程和无栈协程的区别?
- 有栈协程:
- 独立的栈空间
- 栈指针
- 调用链
- 内存开销更大、切换不灵活
- 无栈协程:
- 没有独立调用栈
- 必要状态保存到一个对象里,通常像状态机
- 一个可暂停、可恢复的函数状态对象
- 内存开销更小、切换灵活、但是只能在特定挂起点挂起
- 有栈协程:
- 你有没有对这个协程服务器进行调研选型?这方面的轮子不少,为什么想自己做一个?
- 调研过一些网络库的实现,比如
muduo网络库,它主要用于 主从多Reaactor模式; - 我做这个项目不是为了替代成熟框架,而是为了在一个可控的小系统里吃透协程调度和网络模型,并验证关键设计;
- 如果是生产优先场景,我会更倾向直接用成熟框架。
- 调研过一些网络库的实现,比如
- 你有没有针对性各个模块设计一些测试用例去自测功能,看看是不是完备的,或者说对你这服务器有没有验证性能是符合你自己要求的?
- 有做过
- 针对于每个模块,我都会去写测试样例,去验证这个模块是否是符合我的预期的,比如协程,我会去调用多个协程,去查看调用链是否是正确的;
- 针对这个服务器的性能,我主要是做了
Apach Benchmark,去查看了一下相关的指标,主要是看看在不同并发程度下系统什么时候出现瓶颈,瓶颈是在I/O、调度器调度不过来了 还是 锁竞争。
- 有做过
- 协程被饿死或者线程上的协程负载不均,现在交给你这个任务,你要去覆盖这种用例以及针对性的去评估这方面的性能,你觉得你会怎么去做?
- 问题描述:
- 针对我这个项目,协程确实会发生 协程饿死
- 我的协程是非抢占式的,只要某个协程长期不
yield,它所在的线程上其他协程就可能被拖死。
- 我的协程是非抢占式的,只要某个协程长期不
- 针对我这个项目,同样会发生 线程上的协程负载不均
- 因为我使用的是 全局任务队列,可能会发生某个线程刚好比较快,连续抢到很多任务以及某个线程刚好在忙,抢不到新任务。
- 针对我这个项目,协程确实会发生 协程饿死
- 方案评估
- 首先会做的是 补观测指标
- A. 比如说给 协程 添加一个时间戳:
- 创建时间:
create_time; - 单次调度等待时延:
enqueue_time; - 首次运行时间:
first_run_ts; - 终止运行时间:
finish_ts;
- 创建时间:
- B. 协程的调度行为:
- 被调度次数
- 主动
yield次数 - 因
IO挂起次数 - 恢复次数
- 连续运行时长最大值
如果某个协程连续运行很久几乎不
yield那它就是 “疑似霸占线程” 的元凶。
- A. 比如说给 协程 添加一个时间戳:
- C. 每个线程的负载指标
- 执行的协程数
- 累计运行时间
idle时间epoll唤醒次数- 平均队列等待时间
- D. 调度器的指标
- 全局队列长度
- 队列长度峰值
ready协程数pending IO数tickle次数- 空唤醒次数
- 每秒调度次数
- 首先会做的是 补观测指标
- 覆盖测试样例
- 样例一:单个 “坏协程” 霸占线程
- 验证:没有时间片时,单协程是否会饿死同线程其他协程
- 做法:
1个CPU密集协程:死循环计算200ms,不主动yield;100个轻协程:每个只做很少计算,然后yield;100个IO协程:定时器/pipe/loopback/socket周期唤醒
- 查看:
- 坏协程 所在线程的其他协程等待时间是否暴涨;
- 是否出现 “
ready很久但一直没运行” 的协程; - 线程间是否出现明显失衡;
- 样例二:热点连接集中到同一个线程
- 验证:某一批高频活跃连接是否会长期压在同一个线程上,导致该线程过热、其他线程空闲
- 做法:
4个工作线程;1000个连接,其中:800个高频连接:持续小包收发,周期性触发可读/可写事件;200个低频连接:低频发送或基本空闲;
- 故意让这
800个高频连接优先由同一个线程accept或恢复; - 或者在
schedule时,把大部分高频协程指定到同一个threadid
- 样例一:单个 “坏协程” 霸占线程
- 问题描述:
- 讲一下
IO多路复用以及epoll的底层机制?IO多路复用:使用一个线程来管理多个socket连接epoll:- 红黑树: 管理
socket; - 就绪链表: 放入已经就绪的连接;
- 等待队列: 放入
socket的进程id以及对应的回调函数用于唤醒该进程
- 红黑树: 管理
- 你刚才讲了一下从网卡这个入口,讲了一下
epollIO多路复用的处理的逻辑链条,但是epoll只能通过网卡触发嘛,还是可以通过别的入口进到这个处理流程?epoll监听的对象不是 “网卡中断”,而是 “文件描述符上的就绪事件”;epoll(7)的定义就是监视多个file descriptor,看它们是否可进行I/O;- 网卡只是让某些
socket fd进入ready状态的一个来源; - 监听
socket上有新连接可accept、已连接socket上有数据可读、发送缓冲区腾出了空间可写,这些都会表现成socket fd的就绪事件;
epoll的监听集合使用红黑树,是出于什么考虑?- 红黑树是平衡搜索树,查找、插入、删除的复杂度都是稳定的
O(log n); - 红黑树天然适合这种 内核里长期维护的一组有序唯一键对象;
- 红黑树是平衡搜索树,查找、插入、删除的复杂度都是稳定的
- 对比
epoll,poll又是用什么进行监听的呢?poll是让用户态每次传一个struct pollfd数组,pollfd中的每个元素包含fd/events/revents;- 用户态会拷贝
pollfd到 内核态,内核收到后会遍历这些fd,当fd就绪后,poll会逐个调用fd的poll回调判断是否就绪; - 内核通过
poll_table+wait_queue把当前线程挂到这些fd的等待队列上。如果没有事件就睡眠,被唤醒后再重新扫描,并把结果写回各个pollfd.revents。
- 不同的进程可以监听同一个端口吗?
Linux上显式使用SO_REUSEPORT,可以让多个进程/线程绑定完全相同的地址和端口。SO_REUSEPORT指的是多个socket可以同时bind到同一个IP:Port,数据包一路到传输层后,内核根据四元组找到目标socket;如果目标端口对应的不是单个socket,而是一组开启了SO_REUSEPORT的socket,那么内核会在这组socket之间选一个来接收这个连接或数据包。
- 结合你讲的从网卡、
DMA、硬中断、软中断的方式通知,那么去匹配不同的进程是哪个环节进行的呢?- 完整链路: 网卡 →
DMA到内存 → 硬中断 → 通知CPU→ 软中断里跑收包 → 进入IP/TCP/UDP协议栈 → 查socket→ 把数据/连接挂到这个socket→ 唤醒等这个socket的进程/线程/epoll。 - 如果启用了
SO_REUSEPORT- 多个进程都在
listen。 - 选中
socket之后,内核再通过socket的等待队列和epoll的回调机制,唤醒阻塞在recv/accept/epoll_wait上的线程。 - 也就是说,内核先匹配
socket,再间接匹配到进程。
- 多个进程都在
- 完整链路: 网卡 →
- 两个线程可以监听同一个端口嘛?(这需要分情况)
- 两个线程共享同一个监听
socket,这个是可以的。- 一个线程先
socket->bind->listen,然后把同一个监听fd交给两个线程,它们都可以在这个fd上accept(),这是常见的多线程服务器模型之一。 - 传统方式就是 “多个线程竞争同一个
listeningsocket上的accept()”。 accept(2)也说明了,它只是从这个监听socket的等待队列里取出一个连接请求,并返回一个新的已连接socket,原来的监听socket不受影响。
- 一个线程先
- 两个线程各自创建 “不同的监听
socket”,再去绑定同一个ip:port。默认不行。- 同一个本地
(ip, port)一般只能绑定一个IP socket; - 如果已经有一个
active listening socket绑定在这个地址上,再绑定通常会失败。 - 但如果两个线程各自的
socket都提前设置了SO_REUSEPORT,那就可以。
- 同一个本地
- 两个线程共享同一个监听
计网
tcp三次握手?tcp三次握手可以缩短成两步嘛?- 恶意客户端不发送第三次握手,这种情况会造成什么影响呢?
- 对已经建立好的连接有影响吗?
- 如何解决
SYN Flood攻击?SYN-Cookie:在三次握手完成前尽量不为半连接分配状态,把必要状态编码进SYN-ACK的序列号里,等客户端真正回ACK再重建连接状态。- 这样即使
SYN很多,也不容易把半连接状态耗尽。
tcp解决什么问题?有哪些技术手段?- 关于网络阻塞这里,我了解网络的不同层其实都有这些手段,你有了解过吗?想看你这方面是否有了解?
- 数据链路层: 数据包缓冲队列
- 网络层:
- 路由器和交换机在转发时排队
- 主动队列管理
AQM - 在包头打标记,让接收方通知发送方降速;
- 传输层: 流量控制和拥塞控制
- 应用层: 限流、背压、超时/重试/退避、消息队列
Https和Http的区别?- 引用数字证书为了什么呢?
- 引入数字证书主要是为了解决 公钥分发中的信任问题。
- 客户端在建立安全连接时,不仅需要拿到服务器的公钥,还需要确认这个公钥确实属于目标服务器。
- 数字证书通过
CA签名把服务器身份和公钥绑定起来,从而实现身份认证,并防止中间人伪造公钥。
MySQL
- 介绍
MySQL有几种log,对比的进行去描述?- 回滚日志
undo log:Innodb存储引擎层生成的日志,实现了事务中的 原子性,主要用于 事务回滚 和MVCC; - 重做日志
redo log:Innodb存储引擎层生成的日志,实现了事务中的 持久性,主要用于 故障恢复; - 归档日志
bin log:Server层生成的日志,主要用于 数据备份 和 主从复制,事务执行完成后、真正提交前先写binlog;
- 回滚日志
undo log和bin log的刷盘时机?bin log:MySQL提供一个sync_binlog参数来控制数据库的binlog刷到磁盘上的频率:sync_binlog = 0的时候,表示每次提交事务都只write,不fsync,后续交由操作系统决定何时将数据持久化到磁盘;sync_binlog = 1的时候,表示每次提交事务都会write,然后马上执行fsync;sync_binlog = N的时候,表示每次提交事务都write,但累积N个事务后才fsync。
undo log:undo先写入内存中的undo页;undo页修改生成redo;- 提交时
redo按规则决定是否刷盘;
redo log:- 通过参数判断
innodb_flush_log_at_trx_commit0:每次事务提交,将redo log留在redo log buffer中,该模式下在事务提交时不会主动触发写入磁盘的操作。1:每次事务提交,都将缓存在redo log buffer里的redo log直接持久化到磁盘,这样可以保证MySQL异常重启之后数据不会丢失。2:表示每次事务提交时,都只是缓存在redo log buffer里的redo log写到redo log文件,相当于写入到了PageCache。
- 通过参数判断
- 索引的原理?联合索引也是通过
B+树来承载的嘛,有什么特点嘛? - 联合索引
(a,b)?a > 1 and b > 1,很好的利用联合索引嘛? - 别的中间件有一些了解不?比如
Redis,MQ? - 讲一下你对
kafka的理解? kafka怎么保证不丢消息的呢?
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 GYu的妙妙屋!
