简历项目问题

597 阅读12分钟

1. 简历上的项目问题准备

关注技术方面的描述。要描述项目上比较不错的设计,用到的开源库,有什么好处。比如给我们做到了高并发,做到了缓存,解耦了服务端网络层、业务层以及DB层的强耦合,比如说用到了恢复、消息队列、redis,就是把我们的服务层和DB层解耦了。

介绍自己的项目:

描述项目本身想做什么事情,从实际的角度去说,为什么做这个项目。

比如说做池类项目,这个就是解决当服务启动起来以后,再去创建进程、线程,销毁进程、线程,再去不断地创建连接,销毁连接,连接不能复用,这就影响到我们的性能, 这就需要池来提升之前效率低的地方。说连接池项目可以说我为什么要做这个连接池,怎么去实现的,里面用到了封装,数据库MySQL的API操作,各种各样的智能指针,基于C++语言方面的线程实现,队列管理,线程互斥操作(比如用到了C++语言提供的锁),线程间的通信(条件变量wait)。

聊天服务器项目是想真真正正根据某一个业务场景去设计一个高并发的带业务的服务器,因为我主要是做linux C++后台开发的,后台开发一定会涉及到高并发,我想测试一个这个服务启动以后在我32位linux虚拟机上它的一个能力上限能到达多少,比如说支持同时在线一万人的聊天,如果想再提升它,可以加集群或者基于分布式架构把它部署成微服务。

如果面试官觉得写得项目没有必要,如何回答?

可以说我们每次写一个项目都有收获,把前面所学的什么东西在实践上进行了锻炼,真正了解了什么东西。比如智能指针,光学知识知道它是自动释放资源,但是在真正设计一个连接池的时候,我用到了智能指针后,我才真正感受到它在实践的软件模块设计中体会到了它的好处。

描述项目的时候多从安全性和稳定性想想。

2. 协议类问题同一处理

(1)如何保证发送数据不丢不重(聊天项目)

  • 这个项目客户端和服务器是依靠TCP协议通信,这个协议本身就保证了发送数据不丢不重。

不丢:可以把超时重传机制讲讲,TCP在不同的网络环境中,会通过发包得到响应去测试当前网络环境一个网络包发送到响应的平均网络参数时间,再加上网络抖动的时间,基本上超时重传的时间比这两个加起来长一点点,不管是发送方给接收方发送丢了还是接收方给发送方发送丢了,都可以通过超时重传机制保证。

不重:TCP协议是流式协议,它对每一个字节都打了一个sequence,当数据包传到对端以后,对端返回一个ACK,如果发送端接收到了,那就说明传输成功。有可能出现这样的情况,我把这个包发送给对端,对端回复ACK,由于一些原因ACK被阻塞在网络中间,发送方发现在超时重传时间内没有接收到ACK那就会重新发。但是如果接收方已经把数据接收过了,但是发回的ACK由于网络拥塞卡在了网络的一些节点上,作为发送方一直接收不到ACK,就会启动超时重传机制重新发,接收方是不会接收这个数据的,因为同样序列的数据已经接收过了,只是把第二次发送方重传的ACK响应过来就可以了。这就是它在通过序号和确认应答的方式来保证TCP在传输数据中数据不重的数据处理方式。

面试官经常给一种场景让你去设计TCP和UDP。 我们都知道TCP是有连接的数据流协议,UDP是无连接的数据报协议。TCP有错误处理、超时重传、乱序重排、滑动窗口、慢启动和流量控制等各种各样在协议上来保证TCP可靠性传输,UDP是不可靠的,因为它任何保证可靠性传输的机制都没有。首先协议本身做的事情多逻辑处理就多,像TCP协议为了保证可靠性传输,发送一个包还有ACK响应,占用的网络带宽流量也比较大,这是相对来说的,在以前网络比较匮乏的时候,确实TCP对于网络带宽的消耗和UDP比起来还是相对比较明显,但是现在网络的带宽资源是非常庞大,硬件资源充足,基本上在设计网络通信的时候都采用TCP,TCP虽然耗费了那么一点点多余的流量,但是换来了绝对可靠性。所以在现在的互联网时代,在去考虑某些场景用TCP还是UDP的时候,可以根据自己原来的分析,但是不要把TCP进行可靠性传输所引入的机制对于逻辑上的增多对网络资源的占用看成是一种累赘,我们首先保证的是可用性、可靠性,相对来说UDP在这一点上就得不到什么保障,当然UDP现在也有应用的一些场景,比如说可以接受一定概率上的丢包、一些数据损失,这些是可以采用UDP的,比如传音频、视频之类的可以用UDP,除此之外基本上都是用TCP设计的。

  • 从应用保证 A要给B说话,A先发送的数据到达服务器,服务器应该给A发送一个ACK,如果A发到服务器的消息迟迟收不到ACK,那么可以由A启动超时重传机制重新把这个数据发到服务器,相当于把TCP在传输层协议上的机制再应用层再来一遍。

(2)TCP三次握手,四次挥手,握手和挥手发了每一个包和接了每一个包后client和server进入的每一个状态都代表什么意思? client给server发一个SYN包,握一次手客户端进入什么状态,服务器进入什么状态,server再给client返回一个ACK+SYN,然后客户端又要给server返回一个ACK,这是三次握手,为什么不是两次? 四次挥手要注意TIME_WAIT和CLOSE_ACK代表什么意思?为什么要进入TIME_WAIT状态?

3. 四大池?

四大池:进程池 线程池 内存池 连接池

用池是因为池之前的东西造价太大,不断地去生成释放造价大,因此为了更有效地使用做了一个池,用的时候从池里面拿,用完了不用释放放回池中就行。

  • 为什么需用这些池 进程和线程在去创建进程和线程的时候,在linux系统上可以用strace跟踪一下,线程在pthread_create底层用的也是克隆,最后调用的还是fork,所以在真正服务运行过程中,不断地去fork和关闭,造价很大,涉及到内核空间到用户空间的切换,所以可以在启动之前先创建一些线程放到池里面,让它们共同监听一个事件队列,直接从池里面拿一个线程来处理这个事件,处理完事件把当前进程或线程再放到池里面,效率好点。

内存池:对于小块内存的频繁释放来说,malloc和free本身做的事件比较低效,和pt_malloc或者tc_malloc底层实现有关,我们在项目中如果碰到小块内存频繁的开辟释放,就一定要用内存池来提高小块内存的使用效率。

连接池:连接服务器类程序,数据量比较大的话,就会不断地有TCP三次握手、MySQL Server连接认证、MySQL Server关闭连接释放资源、TCP四次挥手,这个造价也是比较大的。我们可以做一个池,事先创建一些连接。

  • 线程池的线程为什么要动态扩容 muduo库就不动态扩容。

基本上来说我们做高性能的网络服务器,采用reacter模型,用一个epoll+线程池,这个线程池里面线程的数量基本上就是IO线程数量+工作线程数量,基本上和CPU的核数相同,因为只有这样我们才能最大利用CPU进行并行处理。

我们有的时候在用线程池去处理业务的时候,比如说muduo库,muduo库之所以采用固定数量的线程池,因为它只是一个网络库,只关注IO线程接收新连接和工作线程监听已连接客户端发生的读写事件,不管至于事件是什么事件,耗不耗费服务端的资源。如果一个工作线程只有这么一个线程专门处理A用户到B用户文件传输请求的话,那么当前这个工作线程其他用户再有什么事件发生的话,这个工作线程就无法及时处理,所以像这种有耗时的IO操作还是需要有线程专门去做的。

所以线程池的线程为什么要动态扩容很明显,首先至于具体的服务端的业务上来以后,具体有什么业务,有没有耗时的IO业务我们是不确定的,那么如果采用固定数量的线程,可能在业务比较耗时的情况下,现有的固定数量的线程如果都被占用的,导致服务器无法给其他用户继续提供服务,一些耗时的业务必须单独开线程去做,但是服务启动以后自己也不知道有这样的业务存在。

线程池的动态扩容:线程池里面维护了和CPU核心数量一样多的线程,比如说四个线程,当我业务需要用到更多的线程线程池会自动去创建新的线程,在业务量大的时候,线程创建比较多,但是线程也不能无限制的创建下去,会规定最多创建线程,超过这个数量就申请不了,除非等待之前某些线程从忙状态转到空闲状态,才可以继续处理这个事情。当业务峰值过去以后,线程池里面的线程只保留初始线程数量,达到最大空闲时间的多余线程就释放掉。

image.png

连接池也是动态扩容的。有初始连接量参数,不管这个池怎么用,最起码能保证有初始连接量的连接;最大连接量参数,如果初始连接量都被占用,再有业务想使用就要创建新的连接,当然创建也不能无限制创建,因为一个连接是要占用当前系统资源,随着业务并发起来以后,池最大只能增长到最大连接量,如果都已经是最大连接量了再去增长就会出现错误,这也是保证我们服务器在合理范围内能够正常运作的最大的限能;最大空闲时间,如果这会的并发操作不多了,那么除了初始连接量个连接,剩下的达到了最大空闲时间就会被释放掉。

4. 假设与数据库交互出现问题了,线程和数据库交互时产生阻塞,线程不能再继续向下执行,会产生什么问题?如何解决?

用聊天的项目来说,因为聊天项目采用的是reacter模型,一个IO线程专门监听集连接,剩下的线程是属于工作线程,一个线程监听了很多客户端的套接字,接收到一个用户的请求以后就开始进行做业务,业务的处理过程中会涉及到数据库的CURD。如果说在当前工作线程在和数据库进行交互产生阻塞,会导致注册在这个工作线程epoll上的所有套接字后边发生的读写事件当前线程无法处理

解决:落实到怎么找代码上造成线程阻塞的地方,通过gdb调试多线程程序找出问题,根据具体问题来具体解决。如果我们在项目上连接不上数据库,引入连接池,从连接池里面取连接不会产生阻塞,即使阻塞有一个阻塞时间(大概是1s还是2s),如果超过这个时间我们还无法取得连接,连接池会直接给调用方(应用)返回一个错误,也就是说我们采用连接池返回连接,和数据库交互的时候不会产生阻塞,这也就从代码设计上杜绝了线程和数据库交互产生阻塞。