大家好,我是小林。
好未来主要是搞 K12 教育相关的教育软件产品和工具,之前受到“双减”的影响,毁约过校招 offer,现在不少同学拿到好未来 offer,都蛮纠结要不要去的,担心又踩雷了。
来看看,好未来 25 届开发岗的校招薪资情况如下:
训练营也有同学好未来的offer
好未来面试难度不算简单,看到一篇同学的 好未来的后端开发面经,同学反馈面试官水平挺高的,408基础比他狂背八股的要深多了,最后也有手撕算法代码。
这是一面的面经,面试时间长达 50 分钟, 面试官挺好的,问完会给出自己的答案以及自己想问的点,主要考察了计算机基础、网络协议、MySQL、Redis 的问题。
你们觉得难度如何?
计算机基础单核CPU如何执行多个程序?
操作系统会为每个程序分配一个时间片,这个时间片是一个很短的时间间隔,例如几十毫秒。
单核 CPU 会轮流执行每个程序,在一个时间片内执行一个程序,当这个时间片用完后,就切换到下一个程序。这种方式给用户一种多个程序在同时运行的假象,因为切换速度非常快,用户难以察觉程序之间的切换。
CPU的流水线设计有了解吗?
一条指令的执行需要经过取指令,翻译指令,执行指令三个基本流程。CPU内部的电路也分为不同的单元:取指单元、译码单元、执行单元等,指令的执行也是按照流水线的工序一步一步执行的。
如果采用流水线技术,则每个时钟周期内只有一个单元在工作,其余两个单元在“观望”
流水线设计将这些操作分成多个独立的阶段,每个阶段由专门的硬件单元负责,使不同指令的不同阶段可以并行执行,流水线的本质就是拿空间换时间。将每条指令的步骤分解到不同的电路单元,从而使得多个指令并行执行。
这就好像我们的后端程序员不需要等待功能上线,就会从产品经理手中拿到下一个需求,开始开发 API。这样的协作模式,就是我们所说的 指令流水线 。这里面每一个独立的步骤,我们就称之为 流水线阶段 或者流水线级。
如果我们把一个指令拆分成“取指令 – 指令译码 – 执行指令”这样三个部分,那这就是一个三级的流水线。如果我们进一步把“执行指令”拆分成“ALU 计算(指令执行)- 内存访问 – 数据写回”,那么它就会变成一个五级的流水线。
例如,在一个简单的 5 级流水线中,当一条指令在执行阶段时,下一条指令可以在译码阶段,再下一条指令可以在取指阶段。
尽管流水线无法减少单条指令执行时的 “延时” 这一性能指标,不过,借助于同时对多条指令的不同阶段展开执行,我们能够有效地提升 CPU 的 “吞吐率”。从外部视角来看,CPU 仿佛具备了 “一心多用” 的能力,在同一时刻,可以同时对 5 条不同指令的不同阶段进行处理。在 CPU 内部,其运行机制就好似一条生产线,不同分工的组件持续处理着从上游传递过来的内容,而无需像传统模式那样,等到一件商品完全生产完毕之后,才开始下一件商品的生产流程。
CPU绑定的操作有了解吗?
在单核 CPU,虽然只能执行一个线程,但是操作系统给每个线程分配了一个时间片,时间片用完了,就调度下一个线程,于是各个线程就按时间片交替地占用 CPU,从宏观上看起来各个线程同时在执行。
而现代 CPU 都是多核心的,线程可能在不同 CPU 核心来回切换执行,这对 CPU Cache 不是有利的,虽然 L3 Cache 是多核心之间共享的,但是 L1 和 L2 Cache 都是每个核心独有的, 如果一个线程在不同核心来回切换,各个核心的缓存命中率就会受到影响,相反如果线程都在同一个核心上执行,那么其数据的 L1 和 L2 Cache 的缓存命中率可以得到有效提高,缓存命中率高就意味着 CPU 可以减少访问 内存的频率。
当有多个同时执行「计算密集型」的线程,为了防止因为切换到不同的核心,而导致缓存命中率下降的问题,我们可以把 线程绑定在某一个 CPU 核心上,这样性能可以得到非常可观的提升。
在 Linux 上提供了 方法,来实现将线程绑定到某个 CPU 核心这一功能。
网络URL从输入到响应的流程?
null
追问:TCP连接除了IP地址还需要什么?
还需要端口号,端口号用于标识一个主机上的特定服务或进程。
追问:流程在传输层做了什么事情?
主要有以下这些事情:
追问:流程在网络层做了什么事情?追问:TCP有TCP的分段,IP层有IP分片。这两个有什么区别?现在用的是哪个?
两者的区别:
TCP 通常会尽量避免 IP 分片,原因是 IP 分片之后,只有第一个分片才有TCP头部信息,那么一旦一个分片丢失,整个数据包都需要重传,会影响传输效率,并且由于 IP 分片是基于网络链路的 MTU,可能会在不同的网络链路中发生多次分片和重组,增加网络设备(如路由器)的处理开销。
在现代网络中,TCP 通常会尽量避免 IP 分片,主要作法是:TCP 在建立连接时,会协商 MSS,它是 TCP 段中数据部分的最大长度,通常是根据链路的 MTU 计算得到。例如,在以太网中,MTU 通常是 1500 字节,TCP 的 MSS 通常是 1460 字节(MTU 减去 IP 头部和 TCP 头部的长度)。这样 TCP 会将数据分成 MSS 大小的段,从而避免在网络层进行 IP 分片,提高传输效率。
拥塞控制介绍一下 ?
是如何保障数据不丢失的?
主要是通过 来实现事务持久性的,事务执行过程,会把对 存储引擎中数据页修改操作记录到 里,事务提交的时候,就直接把 刷入磁盘,即使脏页中途没有刷盘成功, mysql 宕机了,也能通过 重放,恢复到之前事务修改数据页后的状态,从而保障了数据不丢失。
是在内存里吗?
事务执行过程中,生成的 会在 中,也就是在内存中,等事务提交的时候,会把 写入磁盘。
为什么要写,而不是直接写到B+树里面?
因为 写入磁盘是顺序写,而 b+树里数据页写入磁盘是随机写,顺序写的性能会比随机写好,这样可以提升事务提交的效率。
最重要的是具备故障恢复的能力,Redo Log 记录的是物理级别的修改,包括页的修改,如插入、更新、删除操作在磁盘上的物理位置和修改内容。例如,当执行一个更新操作时,Redo Log 会记录修改的数据页的地址和更新后的数据,而不是 SQL 语句本身。
在数据页实际更新之前,先将修改操作写入 Redo Log。当数据库重启时,会进行恢复操作。首先,根据 Redo Log 检查哪些事务已经提交但数据页尚未完全写入磁盘。然后,使用 Redo Log 中的记录对这些事务进行重做(Redo)操作,将未完成的数据页修改完成,确保事务的修改生效。
mysql 两次写( write )了解吗?
我们常见的服务器一般都是Linux操作系统,Linux文件系统页(OS Page)的大小默认是4KB。而MySQL的页(Page)大小默认是16KB。
MySQL程序是跑在Linux操作系统上的,需要跟操作系统交互,所以MySQL中一页数据刷到磁盘,要写4个文件系统里的页。
需要注意的是,这个操作并非原子操作,比如我操作系统写到第二个页的时候,Linux机器断电了,这时候就会出现问题了。造成”页数据损坏“。并且这种”页数据损坏“靠 redo日志是无法修复的。
的出现就是为了解决上面的这种情况,虽然名字带了,但实际上 是内存+磁盘的结构。
作用是,在把页写到数据文件之前,先把它们写到一个叫 (双写缓冲区)的共享表空间内,在写 完成后,才会把页写到数据文件的适当的位置。如果在写页的过程中发生意外崩溃,在稍后的恢复过程中在 中找到完好的page副本用于恢复,所以本质上是一个最近写回的页面的备份拷贝。
如上图所示,当有页数据要刷盘时:
当MySQL出现异常崩溃时,有如下几种情况发生:
由此我们可以得出结论, write 是针对实际的数据页的原子性保证,就是避免MySQL异常崩溃时,写的那几个data page不会出错,要么都写了,要么什么都没有做。
为什么无法代替 write ?
的设计之初,是“账本的作用”,是一种操作日志,用于MySQL异常崩溃恢复使用,是引擎特有的日志,本质上是物理日志,记录的是 “ 在某个数据页上做了什么修改 ” ,但如果数据页本身已经发生了损坏,来恢复已经损坏的数据块是无效的,数据块的本身已经损坏,再次重做依然是一个坏块。所以此时需要一个数据块的副本来还原该损坏的数据块,再利用重做日志进行其他数据块的重做操作,这就是 write 的原因作用。
MySQL的加锁机制?
在 MySQL 里,根据加锁的范围,可以分为 全局锁、表级锁和行锁三类。
行级锁: 引擎是支持行级锁的,而 引擎并不支持行级锁,行级锁如下:
为什么要锁间隙锁而不是加行锁?
可重复读隔离级会有间隙锁,间隙锁主要是为了避免其他事务插入新记录,导致同一个事务前后两次查询的结果集数不一致的幻读问题。
假设,表中有一个范围 id 为(3,5)间隙锁,那么其他事务就无法插入 id = 4 这条记录了,这样就有效的防止幻读现象的发生。
MVCC的实现机制?
MVCC允许多个事务同时读取同一行数据,而不会彼此阻塞,每个事务看到的数据版本是该事务开始时的数据版本。这意味着,如果其他事务在此期间修改了数据,正在运行的事务仍然看到的是它开始时的数据状态,从而实现了非阻塞读操作。
对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过 Read View 来实现的,它们的区别在于创建 Read View 的时机不同,大家可以把 Read View 理解成一个数据快照,就像相机拍照那样,定格某一时刻的风景。
Read View 有四个重要的字段:
对于使用 存储引擎的数据库表,它的聚簇索引记录中都包含下面两个隐藏列:
null
在创建 Read View 后,我们可以将记录中的 划分这三种情况:
一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况:
这种通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)。
和数据库如何保证数据一致性?
对于读数据,我会选择旁路缓存策略,如果 cache 不命中,会从 db 加载数据到 cache。对于写数据,我会选择更新 db 后,再删除缓存。
缓存是通过牺牲强一致性来提高性能的。这是由 CAP理论决定的。缓存系统适用的场景就是非强一致性的场景,它属于CAP中的AP。所以,如果需要数据库和缓存数据保持强一致,就不适合使用缓存。
所以使用缓存提升性能,就是会有数据更新的延迟。这需要我们在设计时结合业务仔细思考是否适合用缓存。然后缓存一定要设置过期时间,这个时间太短、或者太长都不好:
但是,通过一些方案优化处理,是可以 最终一致性的。针对删除缓存异常的情况,可以使用 2 个方案避免:
消息队列方案
我们可以引入 消息队列,将第二个操作(删除缓存)要操作的数据加入到消息队列,由消费者来操作数据。
举个例子,来说明重试机制的过程。
重试删除缓存机制还可以,就是会 造成好多业务代码入侵。
「 先更新数据库,再删缓存」的策略的第一步是更新数据库,那么更新数据库成功,就会产生一条变更日志,记录在 里。
下图是 Canal 的工作原理:
如果做一个大流量的网站,单Redis无法承压了如何解决?
算法
———END———
限 时 特 惠: 本站每日持续更新海量各大内部创业教程,永久会员只需109元,全站资源免费下载 点击查看详情
站 长 微 信: nanadh666