面经整理:百度C++游戏面试经历(2021-7-19)

在这里说明一下,整理的面试经历的来源是牛客网,不是我本人的面试经历~

我做这个整理的目的,一是为了自己学习和巩固知识点,再一个是可以让找C++相关工作的朋友,快速过一下知识点。

整理的内容可能出现错误,欢迎指出!

**面经来源:**https://www.nowcoder.com/discuss/684874?source_id=discuss_experience_nctrack&channel=-1
(侵删)

在这里插入图片描述


前置知识
什么是Redis?
Redis是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库。
Redis将数据存储在内存里面,读写数据的时候就不会收到磁盘IO速度的限制,所以速度极快,效率高。
Redis作为缓存,就是将很多的请求抗住,过滤掉。

在这里插入图片描述

Redis与其他key-value缓存产品相比有以下三个特点:
1 Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
2 Redis不仅仅支持简单的key-value类型的数据,同时还提供list、set、zset、hash等数据结构的存储。
3 Redis支持数据的备份,即master-slave模式的数据备份。

Redis优势:
1 性能极高,Redis能读的速度是11w次/s,写的速度是8.1w次/s。
2 丰富的数据类型,Redis支持二进制案例的Strings,Lists,Hashes,Sets以及Ordered Sets数据类型操作。
3 原子,Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的,多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
4 丰富的特性,Redis还支持publish/subscribe,通知,key过期等等特性。

Redis与其他key-value存储有什么不同?
1 Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。
2 Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面它们是紧凑的以追加的方式产生的,因为它们并不需要进行随机访问。


Redis缓存击穿
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这是由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间加大,造成过大压力。
通俗的解释就是某一个热点数据,缓存中某一时刻失效了,因而大量并发请求打到数据库上,就像被击穿了一样。

那么既然redis作为缓存,要么就会给key设置过期时间,在一段时间后清楚;或者,就是LRU、LFU,清除冷数据(应该是清楚最长时间不用的数据)。
所以说,只要作为缓存,那么就一定存在这种情况:一个key,某一时间,要么过期了,要么LRU清除。
假如突然有人来访问这个数据,就好像是在redis上打了一个窟窿,击穿了,穿过去了。
本来redis就是为了能让系统抗住更高的并发,让更少的请求打进我们的数据库中,结果,就因为一个key过期了,这么一个小小的事情,导致所有的请求疯狂打进我们的数据库。

系统中存在以下两个问题时需要引起注意:
1 当前key是一个热点key(例如一个秒杀活动),并发量非常大。
2 重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的SQL,多次IO,多个依赖等。
在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。

解决方案
1 设置热点数据永不过期,但是在现在这个时代,热点数据是无法评估的,它在不断变化,因此必然会存在新数据进入缓存,旧数据被淘汰。
2 借口限流与熔断,降级。重要的接口一定要做好限流策略,防止用户恶意刷接口,同时要降级准备,当接口中的某些服务不可用的时候,进行熔断,失败快速返回机制。
3 布隆过滤器。Bloomfilter就类似于一个hash set,用于快速判断某个元素是否存在于集合中,某典型的应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小。
主要是避免代价高昂的磁盘查找,能大大提高数据库查询操作的性能。
4分布式互斥锁。只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。
隐患:如果在查询数据库+重建缓存的时间过长(key失效后进行了大量的计算),也可能存在死锁和线程池阻塞的风险,高并发情境下吞吐量会大大降低!


Redis为什么是单线程的?
官方答案:
因为redis是基于内存的操作,CPU不是redis的瓶颈,redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。

关于cpu不是redis的瓶颈:
1 cpu不可能对redis一点影响没有,但是相比别的数据库,redis受cpu的限制太少了。
2 redis在内存中,省下了cpu从硬盘里取数据拿到内存的时间。
3 redis是单线程的,省下了cpu切换线程恢复线程的时间
4 redis是单线程的,所以服务器是单核还是多核cpu对它没什么影响,因为它的多次读写都是在一个cpu上的。

详细原因:
1 不需要各种锁的性能消耗
Redis的数据结构并不全是简单的key-value,还有list,hash等复杂的结构,这些结构有可能会进行很细粒度的操作,比如在很长的列表后面添加一个元素,在hash当中添加或者删除。
一个对象,在多线程中这些操作可能就需要加非常多的锁,导致的结果是同步开销大大增加。
总之,在单线程的情况下,就不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。

2 单线程的威力实际上非常强大,核心效率也非常高,多线程自然是可以比单线程有更高的性能上限,但是在今天的计算环境中,即使是单机多线程的上限也往往不能满足需求了,需要进一步摸索的是多服务器集群化的方案,这些方案中多线程的技术照样是用不上的。
所以单线程、多进程的集群不失为一个时髦的解决方案。

3 CPU消耗
采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU。
(所以CPU一般不会成为redis的瓶颈)


http状态码,301、302的区别,499是什么意思?502呢?
http状态码:
表示客户端http请求的返回结果,标记服务器端的处理是否正常或者出现的错误,能够根据返回的状态码判断请求是否得到正确的处理很重要。
状态码由3位数字和原因短语组成,第一个十进制数字定义了状态码的类型,后两个数字是分类的作用。

2XX(3种)
2XX的响应结果表明请求被正常处理了。
200 OK:表示从客户端发送给服务器的请求被正常处理并返回;
204 No Content:表示客户端发送给客户端的请求得到了成功处理,但在返回的响应报文中不含实体的主体部分(没有资源可以返回);当浏览器在发送请求后接收到204响应,它的显示页面不会发生更新。
通常应用在只需要客户端往服务器端发送信息,而服务端不需要发送新信息的情况下使用。
206 Patial Content:表示客户端进行了范围请求,并且服务器成功执行了这部分的GET请求,响应报文中包含由Content-Range指定范围的实体内容。

URL:Internet上每一个网页都具有一个唯一的名称标识,通常称之为URL,Uniform Resource Locator,统一资源定位器)。它是www的统一资源定位标志,简单地说URL就是web地址,俗称“网址”。

3XX(5种)
3XX响应结果表明浏览器需要执行某些特殊的处理以正确处理请求。
301 Moved Permanently:永久性重定向,表示请求的资源被分配了新的URL,以后请求该资源应使用新的URL;
302 Found:临时性重定向,表示请求的资源被分配了新的URL,希望客户端本次能使用新的URL访问;
301和302的区别:前者是永久移动,后者是临时移动(后者可能还会更改URL)
303 See Other:和302状态码有异曲同工之妙,表示请求的资源被分配了新的URL,应使用GET方法定向获取请求的资源。
如果浏览器原本是用POST方法去请求服务器,收到303状态码以后,会改用GET并访问资源新的URL。
POST和GET是用来请求资源的。

302和303的区别:后者明确表示客户端应当采用GET方法获取资源
304 Not Modified:表示客户端发送附带条件的请求时,服务器端允许访问资源,但是请求未满足条件的情况下返回该状态码;
例如,客户端请求的资源在客户端本地已有缓存,会在请求头部中加入“if-Modified-Since”,“if-None-Match”等字段,服务器根据这些字段信息判断这些资源信息是否经过修改,如果没有则返回304状态码,客户端可以直接使用缓存中的资源。也就是说,某些情况下,服务器端虽然可以发送你客户端请求的资源,但是没有必要,所以就不发送这个资源。
307 Temporary Redirect:临时重定向,与303有着相同的含义,307会遵照浏览器标准不会从POST变成GET。

4XX(4种)
400 Bad Request:表示请求报文中存在语法错误;当错误发生时,需修改请求的内容后再次发送请求。另外,浏览器会像200 OK一样对待该状态码。
401 Unauthorized:未经许可,需要通过HTTP认证;第一次收到401状态码表示需要进行用户认证,第二次收到401状态码说明用户认证失败。
403 Forbidden:服务器拒绝该次访问(访问权限出现问题)。
404 Not Found:是我们最常见的状态码之一,表示服务器上无法找到请求的资源,除此之外,也可以在服务器拒绝请求但不想给拒绝原因时使用;
499:客户端发起请求后,一段时间内没有收到代理服务器的应答,导致连接失败。
有两种可能:
1 代理服务器认为客户端发起的请求过于危险,所以主动给断了。
2 代理服务器实在没办法连接到其他服务,导致timeout。

5XX(2种)
5XX的响应结果表明服务器本身发生错误。
500 Inter Server Error:表示服务器在执行请求时发生了错误,也有可能是web应用存在的bug或某些临时的错误时;
502 bad gateway:网关错误,服务器不具备完成请求的功能。例如,服务器无法识别请求方法时可能会返回此代码。
503 Server Unavailable:表示服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。


如何理解IO复用?poll,select,epoll有什么区别?
例子来自于《TCP/IP网络编程》)
某教室有10名学生和1名老师,这些学生上课会不停的提问,所以一个老师处理不了这么多的问题。那么学校为每个学生都配一名老师,也就是这个教室目前有10名老师。此后,只要有新的转校生,那么就会为这个学生专门分配一个老师,因为转校生也喜欢提问题。如果把以上例子中的学生比作客户端,那么老师就是负责进行数据交换的服务端。则该例子可以比作是多进程的方式。
后来有一天,来了一位具有超能力的老师,这位老师回答问题非常迅速,并且可以应对所有的问题。而这位老师采用的方式是学生提问前必须先举手,确认举手学生后在回答问题。则现在的情况就是IO复用。

IO即为网络IO,多路即为多个TCP连接,复用即为共用一个线程或者进程,模型最大的优势是系统开销小,不必创建也不必维护过多的线程或者进程。
所谓IO多路复用,就是同时监控多个IO,有一个准备好了,就不需要等待其他IO,可以开始处理,提高了同时处理的能力。
IO多路复用就是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但是select、poll、epoll本质上都是同步IO,因为它们都需要在读写事件就绪后自己负责读写,也就是说这个读写过程是阻塞的,而异步IO则无需自己负责进行读写,异步IO的实现会负责把数据从内核拷贝到用户空间。

1 Select 时间复杂度O(n)
它仅仅知道有IO事件发生了,却不知道哪几个流,所以只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对它们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。

2 poll 时间复杂度O(n)
Poll本质上和select没有差别,但是它没有最大连接数的限制,原因是它是基于链表来存储的。

3 epoll 时间复杂度O(1)
Epoll不同于轮询和无差别轮询,epoll会把哪个流发生了怎样的IO事件通知我们。就不需要像select和poll那样去遍历所有流来寻找是哪个流发生了IO事件,所以epoll的时间复杂度降到了O(1)。


Redis怎么过滤(布隆过滤器)以防止缓存穿透?
布隆过滤器:可以用来告诉你“某样东西一定不存在或可能存在”

布隆过滤器会有一定的概率误判(布隆过滤器判断存在,但实际上不一定存在),但访问一个数据如果布隆过滤器判断不存在,说明数据一定不存在,就不会请求数据库了,能够有效避免大量无效请求来访问数据库。
1 将数据库所有的数据加载到布隆过滤器
2 查布隆过滤器(如果未命中直接结束)
3 查redis缓存数据(如果未命中查询数据库)
4 查询数据库

我的疑问?
因为加载到布隆过滤器的是数据库的数据,那么一个key值,我查布隆过滤器命中了,但是这个key在redis里面失效了,不还是会有大量的请求来访问数据库么?还是会造成缓存穿透!

我的理解:发现这个key在redis中过期后,它们不会直接向数据库请求,而是从数据库中将这个key更新到redis缓存中,然后这些请求再查询redis缓存,而不是大量的请求去访问数据库。


三次握手、四次挥手
三次握手:就是指建立一个TCP连接的时候,需要客户端和服务器总共发送三个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。实质上就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小信息。

第一次握手:客户端给服务器端发送一个SYN报文,并指明客户端的初始化序列号。首部的同步位SYN=1,初始序号seq=x;
第二次握手:服务器端接收到客户端的SYN报文后,在确认报文段中SYN=1后,将ACK=1,确认号ack=x+1,指定自己的初始化序列号seq=y。
第三次握手:确认报文段ACK=1,确认号ack=y+1,seq=x+1(初始化为seq=x,第二个报文段所以要+1)。客户端发送了ACK报文后就处于ESTABLISHED状态,服务器端接收到ACK报文并确认无误,也会处于ESTABLISHED状态,此时双方就建立起了连接。

为什么需要三次挥手,两次不行吗?
为了防止A发出的已经失效的连接请求突然传送到了B,因而产生错误,比如这种情况:A发出的第一个连接请求报文段并没有消失,而是滞留在了网络结点中,以至于延误到连接释放后才到达B,假如是两次挥手,B就认为A发出了一次连接请求,于是向A发出了确认报文段,同意建立连接,一直等待A发来数据,这样B的资源就被白白浪费了。
假如三次挥手,由于A实际上没有发出建立连接的请求,所以不会理睬B的确认,也不会向A发送数据,由于B得不到确认报文,就知道A没有要求建立连接。

为什么不需要四次挥手?
三次挥手以后,客户端和服务器端已经可以确认之前的通信状况,都收到了确认信息。我们要知道,完全可靠的通信协议是不存在的,所以即便增加握手次数也不能保证后面的通信完全可靠,所以是没有必要的。

四次挥手:
终止一个连接要经过四次挥手。这由TCP的半关闭造成的,所谓的半关闭,其实就是TCP连接的一端在结束发送后还能接收来自另一端数据的能力。
TCP连接的拆除需要发送四个包,因此称为四次挥手,客户端或服务端均可主动发起挥手动作。

第一次挥手:客户端发送一个FIN报文,即发出连接释放报文段,是FIN=1,seq=u;并停止再发送数据,主动关闭TCP连接。

第二次挥手:服务器收到FIN报文后,发送ACK报文,表明服务器端已经收到连接释放报文并发出确认报文,ACK=1,ack=u+1,seq=v(等于服务器端前面传送过的数据的最后一个字节的序号+1);
这个时候TCP处于半连接状态,即客户端没有数据要发送了,若服务端还有数据要发送,客户端仍然接收。客户端收到服务器端的确认后,等待服务器端发出连接释放报文段。

第三次挥手:假如服务器端没有要向客户端发送的数据后,这时候服务端发出连接释放报文,使FIN=1,ACK=1,ack=u+1,seq=w(随机生成一个序列号),等待客户端的确认。

第四次挥手:客户端收到FIN后,一样发送一个ACK报文作为应答,ACK=1,ack=w+1,seq=u+1(上一个报文的序列号+1)。
此时客户端进入TIME_WAIT状态。此时TCP未释放掉,需要等待2MSL后,客户端才进入CLOSED状态,而服务端一收到客户端的确认报文就进入CLOSED状态。

为什么TIME-WAIT状态必须等待2MSL的时间呢?
1 为了保证A发送的最后一个ACK报文段能够到达B。A发送的这个ACK报文可能丢失,因而使B收不到已发送的连接释放报文的确认。B会超时重传这个连接释放报文,而A就能在2MSL时间内(超时+1MSL重传)收到这个重传的连接释放报文。接着A重传一次确认,重新启动2MSL计时器。
假如A不等待2MSL时间而是立即进入CLOSED状态,那么就无法收到B重传的释放连接报文段,因而就不会重传ACK报文,B就无法进入CLOSED状态了。


1-100以内的素数
一个数m,假如它不能被2-\sqrt m的任意一个数整除,那么它就是素数。
在这里插入图片描述

  • 6
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值