「105 分钟」 腾讯一面
视频面。本次面试分为两大块:
- 牛客网写代码 「LeetCode easy难度」,这个阶段大概 5-10 分钟
翻转数列
小Q定义了一种数列称为翻转数列:
给定整数n和m, 满足n能被2m整除。对于一串连续递增整数数列1, 2, 3, 4..., 每隔m个符号翻转一次, 最初符号为'-';。
例如n = 8, m = 2, 数列就是: -1, -2, 3, 4, -5, -6, 7, 8.
而n = 4, m = 1, 数列就是: -1, 2, -3, 4.
小Q现在希望你能帮他算算前n项和为多少。
输入描述
输入包括两个整数n和m(2 <= n <= 109, 1 <= m), 并且满足n能被2m整除。
输出描述
输出一个整数, 表示前n项和。
示例1
输入
8 2
输出
8
- 正式进入面试 「100分钟」
腾讯看来的确全部是 c ,面试官也是说基本上都是 c ,没有专门搞 java 的组,所以大家 java 投腾讯还是务必慎重,最开始问我的技术栈是什么,c 是否了解,用的比较多?得知我说基本没咋用过之后,就开始尝试问计算机网络方面的问题。
- 问的很深入,比如说三次握手四次挥手,客户端服务器端各自的状态是什么,对整个建立连接和关闭连接的具体流程是什么,深入到 OSI 模型去讲;
这个我在上面已经详细说过了…
- 然后主要问的是 Socket 编程,讲套接字编程的具体实现流程,代码如何写,如何实现类似于 Nginx 的多服务器的 Socket 编程;
![preview](https://upload-images.jianshu.io/upload_images/14850956-11795469bf5593d1.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/1240)[1] preview[2]
- 然后谈到 select、epoll等相关 c 的知识,我是在 java 层面去讲的如何实现的 「NIO」;
嘿嘿嘿,这个没问题了,可以看我的最新的文章 —《零拷贝及其周边》
- 再者就是聊到数据库,这个是必问的,问我索引如何建立、如何优化索引,然后是一些具体问题对索引的分析;
用 key or index 建立索引,优化索引的方法:尽量复用索引,利用好最左前缀和索引下推原则,尽量减少回表次数,利用覆盖索引。
- 然后谈到事务的四个特性,如何实现原子性、隔离性、一致性、持久性的,内部机制具体如何实现;
原子性:利用 undolog,回滚机制,完成要么全部成功,要么全部失败; 隔离性:主要是靠一致性视图 当前行的 row_id_transaction,来完成的。 一致性:主要靠加锁防止事务冲突,一致性是另外三个的顶层,只要他们三完成了他才有可能完成,还有mvcc的加持,以及 undolog、redolog。 持久性:redolog保证crash-safe,bin-log保证归档。
- 借此谈到 undolog、redolog、binlog,以及 mvcc 的实现;
略
- Redolog 到底是保存在磁盘中的还是在内存中?
redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。 详细分析 Mysql 中的三个日志:redolog、undolog、binloghttps://juejin.im/entry/5ba0a254e51d450e735e4a1f
- 谈一谈 mysql 的运行机制,整个运行过程是怎样的,如何处理的;
这个简单,略
- mysql的索引实现,B 的优点等等;
简单,略
- 全程谈 一致性问题 谈的很多,包括了 mysql 主从复制的一致性如何保证,我说不太清楚,但是借此讲了 kafka 中的高可用机制「ISR」,以及 kafka中的 ack 机制和 kafka 中的消息语义「如何保证数据的一致性」;
Mysql 保证主从一致性: 主库接收到客户端的更新请求后,执行内部事务的更新逻辑,同时写binlog。 备库B跟主库A之间维持了一个长连接。主库A内部有一个线程,专门用于服务备库B的这个长连接。一个事务日志同步的完整过程是这样的:
- 在备库B上通过change master命令,设置主库A的IP、端口、用户名、密码,以及要从哪个位置开始请求binlog,这个位置包含文件名和日志偏移量。
- 在备库B上执行start slave命令,这时候备库会启动两个线程,就是图中的io_thread和sql_thread。其中io_thread负责与主库建立连接。
- 主库A校验完用户名、密码后,开始按照备库B传过来的位置,从本地读取binlog,发给B。
- 备库B拿到binlog后,写到本地文件,称为中转日志(relay log)。
- sql_thread读取中转日志,解析出日志里的命令,并执行。
这里需要说明,后来由于多线程复制方案的引入,sql_thread演化成为了多个线程。 ![image-20200325005851525](https://upload-images.jianshu.io/upload_images/14850956-ed61f7489257fdde.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/1240)[3] image-20200325005851525[4]
因为语言上还是有很多区别的,在后面又问了几个有关于 c 的问题,答得不是很好
- 虚函数是什么 「没答上来」;
略。
- 因为看到我博客有些滑动窗口算法,就问了 tcp 滑动窗口底层的代码实现;
- 进程、线程、协程的区别,我说完之后,又延伸到线程是如何保证同步的,借此谈到了线程安全,然后我自己拓展了 synchronized「详细介绍了锁升级过程」、lock体系、CAS 的实现以及 final 关键字和 ThreadLocal;
协程的应用场景主要在于 :I/O 密集型任务。 这一点与多线程有些类似,但协程调用是在一个线程内进行的,是单线程,切换的开销小,因此效率上略高于多线程。当程序在执行 I/O 时操作时,CPU 是空闲的,此时可以充分利用 CPU 的时间片来处理其他任务。在单线程中,一个函数调用,一般是从函数的第一行代码开始执行,结束于 return 语句、异常或者函数执行(也可以认为是隐式地返回了 None )。有了协程,我们在函数的执行过程中,如果遇到了耗时的 I/O 操作,函数可以临时让出控制权,让 CPU 执行其他函数,等 I/O 操作执行完毕以后再收回控制权。 简单来讲协程的好处:
- 跨平台
- 跨体系架构
- 无需线程上下文切换的开销
- 无需原子操作锁定及同步的开销
- 方便切换控制流,简化编程模型
- 高并发 高扩展性 低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
缺点:
- 无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
- 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序:这一点和事件驱动一样,可以使用异步IO操作来解决
最后再贴个图来总结一下,更清楚: ![img](https://upload-images.jianshu.io/upload_images/14850956-7053fe4d67e0dd96.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/1240)[5] img[6] 作者:程序猿杂货铺 链接:https://juejin.im/post/5d5df6b35188252ae10bdf42来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
- 进行间通信 IPC 有哪些方式「我只说了信号量、共享区域、管道这几个,面试官也没追问」
- 管道。管道的实质是一个内核缓冲区,进程以先进先出的方式从缓冲区存取数据,管道一端的进行顺序的将进程数据写入缓冲区,另一端的进则顺序地读取数据,该缓冲区可以看做一个循环队列,读和写的位置都是自动增加的,一个数据只能被读一次,读出以后在缓冲区就不复存在了。当缓冲区读空或者写满时,有一定的规则控制相应的读进程或写进程是否进入等待队列,当空的缓冲区有新数据写入或满的缓冲区有数据读出时,就唤醒等待队列中的进程继续读写。管道是一种半双工通信方式,数据只能单向流动。需要进行通信时,需要建立2个管道。
- 信号量。进程之间通信的机制,例如 Semphore ?
- 共享内存。进程的不同虚拟内存映射到用一个物理内存上,实现共享。
- 内存泄露问题如何排查,主要问linux如何进行排查「没答上来,就说可以用可视化界面」
- 内存泄露会发生什么情况,系统会假死吗?
最后谈了谈一些数据结构和算法:
- 两个堆如何实现队列「貌似堆不就是优先级队列嘛…」,两个队列如何实现堆;「面试官表述不清楚,可能就是想指堆栈???」
- 两个栈如何实现队列,两个队列如何实现栈;
- 链表如何查找是否有环;
- 链表如何确定环的起点;
- 两条链表找公共处的起点。
还有一些细枝末节的问题,印象已经不深了,大概就是这些吧。
总结一下:面试官非常擅长挖掘面试者的优势,对面试者不太懂的全部不问,基本上我会什么就问什么,所以大概面试了半小时后,就开始对着我的博客问,所以整体上给人的感觉是很好的。大家如果要面腾讯的话建议多看看c ,并且对常见的 计网 和 os 的问题尽量往深处走,面试官只看中你对问题的深度,不会的问题他不会追问。
最大的收获:
- 面试官对自己方方面面的建议。并且给自己推荐了三本书《unix网络编程卷一》《unix网络编程卷二》《linux内核》
部分拓展
- 输入 www.taobao.com 后发生了什么
- 如果是第一次访问 www.taobao.com,客户端「浏览器」会首先去 dns 服务器查找对应的ip,如果是第一次访问 这个网站,那么首先会去走 http 协议,客户端「浏览器」和服务器的 80 端口进行 tcp 连接,然后 服务器 80 端口会返回一个 301/302「具体区别下文会讲」重定向,在服务器响应头中还会添加 HTTP-Strict-Transport-Security,里面有 max-age,用户访问时,服务器种下这个头,下次如果使用 http 访问,只要 max-age 未过期「客户端检验」,客户端会进行内部跳转,可以看到 307 Redirect Internel 的响应码。然后就直接变成和 443 端口建立连接,走 https 连接。
- 如果不是第一次访问,那在拿到ip之后客户端直接校验自己的 max-age字段,如果没过期直接 307 内部跳转,直接和服务器的 443 端口进行 https 的连接了。剩下的就是https的过程了。 作者:蜗牛的北极星之旅 链接:https://juejin.im/post/5d8a34ea6fb9a04dfa09561b来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。告诉我们需要建立 https
- 301、302、307的区别
301重定向是永久的重定向,搜索引擎在抓取新的内容的同时也将旧的网址替换为了重定向之后的网址。 302重定向只是暂时的重定向,搜索引擎会抓取新的内容而保留旧的地址,**因为服务器返回302,所以,搜索搜索引擎认为新的网址是暂时的。**所以302,会导致劫持问题:A站通过重定向到B站的资源xxoo,A站实际上什么都没做但是有一个比较友好的域名,web资源xxoo存在B站并由B站提供,但是B站的域名不那么友好,因此对搜索引擎而言,可能会保存A站的地址对应xxoo资源而不是B站,这就意味着B站出了资源版权、带宽、服务器的钱,但是用户通过搜索引擎搜索xxoo资源的时候出来的是A站,A站什么都没做却被索搜引擎广而告之用户,B站做了一切却不被用户知道,价值被A站窃取了。这里 A 就相当于是我们在输入框输入的域名,然后对 B 重定向 302,导致自己的域名不会被替换却还享用 B 的资源。 307意思就是客户端内部自己做了重定向,就比如 www.baidu.com ,我在有了 HSTS 的 max-age 之后会自动从 http://www.baidu.com 跳转到 https://www.baidu.com。
- https 的形成过程
在传输层和应用层中间有个 ssl/tls 层 流程:
- 验证过程「具体流程:https://www.jianshu.com/p/b0b6b88fe9fe」
- 客户端(通常是浏览器)先向服务器发出加密通信的请求,与服务器的443端口建立连接;
- 服务器收到请求,然后响应,确认加密方法,如 RSA非对称加密,然后将服务器的证书发过去;
- 客户端验证服务器的证书,然后使用公钥加密发送消息;
- 服务器用私钥解密,得到明文。
- 正常传输
跟正常的http传输一致,只不过是密文传输。
- 对称加密算法和非对称加密算法的区别
https://blog.csdn.net/u013320868/article/details/54090295 对称加密,加密解密时间更快,但是密钥管理是个大问题; 非对称加密,更安全,但是加密解密时间较长一些。
- RSA 算法流程
https://zhuanlan.zhihu.com/p/44185847 之所以难解,是因为位数很长,并且去对一个超级大的数进行因式分解,如果没有私钥的话几乎不可能做到。 主要用到的数学知识有欧拉函数、费马小定理等等
- Mysql 的高可用机制是如何实现的
这里我在腾讯面试部分也有提到,但是腾讯那部分主要侧重讲了主从一致是如何实现的,而高可用则是建立在主从一致的基础上的。 先上一个自己画的图: ![高可用机制](https://upload-images.jianshu.io/upload_images/14850956-272af610f82eb466.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/1240)[7] 高可用机制[8] mysql 的高可用的实现,基础就是能够做到主备切换,而主备切换的前提是要保证主从的一致性,这样才能切换,不然肯定会影响数据,但是要保证主从一致性,就会带来主从延迟的情况,具体的主从一致性的实现可以看我上面讲的,主从延迟可能会有图中那些原因,针对不可避免的主从延迟,主备切换有两种策略:
- 可靠性优先策略。具体步骤是等延迟小于某个数时,例如 5 秒,将主库 A 改成只读状态,然后再看主从延迟「seconds_behind_master」,等其变为 0 后,将备库 B 改成可读写状态,然后把业务切换到备库 B 上,这个策略的好处就是能保证主从的完全一致性,但是会有少量时间导致系统处于不可写的状态,不过影响不大,这也是最常用的策略;
- 可用性优先策略。不等主备数据同步,直接把连接切到备库B,并且让备库B可以读写,那么系统几乎就没有不可用时间了。好处就是没有不可用时间,但是容易导致数据逻辑出现问题。
- 再讲第三个,Mysql 的 Write Ahead Logging
Mysql 中常见的三个日志文件分别是 undolog、redolog以及binlog,redolog 主要是负责 crash-safe,也就是崩溃恢复的工作,binlog主要是做一个归档的作用,redolog和binlog是两阶段提交的,这也是常用的分布式的手段吧,大家都okay了才commit,redolog是可擦除的,存储的是物理日志,但是binlog是顺序写,存储的是逻辑日志,并且 redolog 是 Innodb 独有的,binlog则是 server端有的。undlog 主要是负责 mvcc 和回滚,在数据修改的时候,不仅记录了redo,还记录了相对应的undo,如果因为某些原因导致事务失败或回滚了,可以借助该undo进行回滚。undo log和redo log记录物理日志不一样,它是逻辑日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。
- 再讲第四个,Mysql 中的锁
Mysql 从锁的范围上讲分为全局锁、表级锁以及行锁。 全局锁称为 Flash Tables with Read Lock,主要用来全库逻辑备份,这个命令可以使整个库处于只读状态。**使用该命令之后,数据更新语句、数据定义语句和更新类事务的提交语句等操作都会被阻塞。**如果这个命令在主库操作的话,会导致业务停摆,如果再备库操作的话,会导致备库无法写从主库传来的binlog,造成主备延迟,所以我们很少使用它,一般都是使用mysql自带的 mysqldump 去进行全库逻辑备份,因为 mysqldump 有 mvcc 的支持,所以可以一边备份一边读写,但是这个必须要数据库引擎支持rr这个隔离级别,像 MyISAM 就不行。 表级锁又分为表锁和元数据锁MDL,其中表锁需要我们去显式加锁,例如 lock tables … read/write,如果一个线程对其显式加了表读锁,那么其他线程和该线程只能读该表,如果该线程加了表写锁,那么其他线程啥也干不了,所以这个锁表的粒度还是太大,一般不会采用。 表级锁的另外一种是元数据锁MDL,这是系统默认加上的,对表进行 DML 是加读锁,对表进行 DDL 是加写锁,读写互斥,读共享,相当于 ReadWriteLock,但是这里没有写降级的过程。 行锁是我们最经常用的了,也分为读锁和写锁,这里的读单指 select,这里的写指的是 update、delete、insert等等,当然我们也可以对 select 进行显式的加锁,此时 select 就从乐观读锁「跟 StampedLock 类似,一个是通过 version 判断数据有没有变化,一个是通过 stamp 判断」变成了悲观读锁或者写锁了,此时mvcc是失效的,是跟 update等语句一样强制去当前读的。
- 再讲第五个,Mysql 的索引?
索引有很多,常见的就是聚集索引和非聚集索引,聚集索引就是元素的逻辑位置和物理位置保持一致,也称为主键索引,是B 树,因为支持范围查询,并且索引节点只有指针和索引,这样单个节点就能容纳更多的指针域,从而使得树的高度下降。 非聚集索引要注意减少回表的次数,常用的方法就是覆盖索引、使用最左前缀原则尽量减少索引的建立,同时注意可以使用索引下推来减少回表的次数。
- 好勒,数据库差不多了,最后讲一讲范式吧
mysql中常用的范式就是三大范式 第一范式:确保每列的原子性,比如说地址这一属性,有的业务可能对城市用的比较多,我们就就应该细分,这样在对用户使用城市进行分类的时候就非常方便,也提高了数据库的性能。 第二范式:确保表中的每列都和主键相关,去除部分依赖。最典型的场景就是多对多的场景,比如订单-商品表,因为订单中可能会有多种商品,所以要将订单编号和商品编号作为数据库表的联合主键,如果此时把商品信息也写在这个表中,就不符合第二范式了,这样会造成数据冗余,分表会更好。 第三范式:确保没有传递依赖,也就是每个非主属性都要依赖于主属性,例如订单表,和用户是一对多的关系,此时用户的id会是这个表的外键,如果在这里加上用户的名字,就违反了第三范式,也是会造成数据的冗余的。 参考: https://www.cnblogs.com/linjiqin/archive/2012/04/01/2428695.htm
参考资料
[1]
preview: https://tva1.sinaimg.cn/large/00831rSTgy1gd09djaf2lj30u00gwmz5.jpg
[2]
preview: https://tva1.sinaimg.cn/large/00831rSTgy1gd09djaf2lj30u00gwmz5.jpg
[3]
image-20200325005851525: https://tva1.sinaimg.cn/large/00831rSTgy1gd5ibtjmvaj314c0u0trb.jpg
[4]
image-20200325005851525: https://tva1.sinaimg.cn/large/00831rSTgy1gd5ibtjmvaj314c0u0trb.jpg
[5]
img: https://tva1.sinaimg.cn/large/00831rSTgy1gdj56e9yqbj30iw07rdkm.jpg
[6]
img: https://tva1.sinaimg.cn/large/00831rSTgy1gdj56e9yqbj30iw07rdkm.jpg
[7]
高可用机制: https://tva1.sinaimg.cn/large/00831rSTgy1gd8uouyrttj31400u0kjm.jpg
[8]
高可用机制: https://tva1.sinaimg.cn/large/00831rSTgy1gd8uouyrttj31400u0kjm.jpg
絮叨
乔戈里又要来了腾讯面经哈,学弟写这篇一面面经也算是尽心尽力,各位记得在看点一下支持一下!超过100个在看,乔戈里才有动力和学弟继续去要一下面经,毕竟在看数也能说明大家对这种类型的文章感不感兴趣~(对了,点击阅读原文,直达学弟博客。)