⽹络
好未来 Java ⾯试
好未来 主要是搞 K12 教育相关的教育软件产品和 ⼯具,来看看 好未来 25 届开发岗的校招薪资情况
如下:
普通 offer :22k x 14.5 = 31.9w ,北京
sp offer :26k x 14.5 = 37.7w ,北京训练营也有同学好未来 的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 上提供了 sched_setaffinity ⽅法,来实现将线程绑定到某个 CPU 核⼼这⼀功能。

⽹络
URL 从输⼊到响应的流程?

解析URL :分析 URL 所需要使⽤的传输协议和请求的资源路径。如果输⼊的 URL 中的协议或者
主机名不合法,将会把地址 栏中输⼊的内容传递给搜索引擎。如果没有问题,浏览器会检查
URL 中是否出现了⾮法字符,则对⾮法字符进⾏转义后在进⾏下⼀过程。
缓存判断:浏览器会判断所请求的资源是否在缓存⾥,如果请求的资源在缓存⾥且没有失效,
那么就直接使⽤,否则向服务器发起新的请求。
DNS 解析:如果资源不在本地缓存,⾸先需要进⾏DNS 解析。浏览器会向本地DNS 服务器发送
域名解析请求,本地DNS 服务器会逐级查询,最终找到对应的IP 地址 。
获取MAC 地址 :当浏览器得到 IP 地址 后,数据传输还 需要知道⽬的主机 MAC 地址 ,因为应⽤
层下发数据给传输层,TCP 协议会指定源端⼝号和 ⽬的端⼝号,然后下发给⽹络层。⽹络层会
将本机 地址 作为源地址 ,获取的 IP 地址 作为⽬的地址 。然后将下发给数据链路层,数据链路层
的发送需要加⼊通信双⽅的 MAC 地址 ,本机 的 MAC 地址 作为源 MAC 地址 ,⽬的 MAC 地址
需要分情况处理。通过将 IP 地址 与本机 的⼦⽹掩码相结合,可以判断是否与请求主机在同⼀个
⼦⽹⾥,如果在同⼀个⼦⽹⾥,可以使 ⽤ APR 协议获取到⽬的主机的 MAC 地址 ,如果不在⼀
个⼦⽹⾥,那么请求应该转发给⽹关,由它代为转发,此时同样可以通过 ARP 协议来获取⽹关
的 MAC 地址 ,此时⽬的主机的 MAC 地址 应该为⽹关的地址 。
建⽴TCP 连接:主机将使⽤⽬标 IP 地址 和⽬标MAC 地址 发送⼀个TCP SYN 包,请求建⽴⼀个TCP
连接,然后交给路由器转发,等路由器转到⽬标服务器后,服务器回复⼀个SYN-ACK 包,确认
连接请求。然后,主机发送⼀个ACK 包,确认已收到服务器的确认,然后 TCP 连接建⽴完成。
HTTPS 的 TLS 四次握⼿:如果使⽤的是 HTTPS 协议,在通信前还存在 TLS 的四次握⼿。
发送HTTP 请求:连接建⽴后,浏览器会向服务器发送HTTP 请求。请求中包含了⽤⼾需要获取的
资源的信息,例如⽹⻚的URL 、请求⽅法(GET 、POST 等)等。
服务器处理请求并返回响应:服务器收到请求后,会根据请求的内容进⾏相应的处理。例如,
如果是请求⽹⻚,服务器会读取相应的⽹⻚⽂件,并⽣成HTTP 响应。
追问:TCP 连接除了IP 地址 还需要什么 ?
还需要端⼝号,端⼝号⽤于标识⼀个主 机上的特定服务或进程。
源端⼝号是发送端应⽤程序使⽤的端⼝,它是⼀个 16 位的数字,范围从 0 到 65535 。⼀般来
说,客⼾端通常使⽤⼀个⼤于 1023 的临时端⼝号。
⽬的端⼝号是接收端应⽤程序使⽤的端⼝,不同的服务通常使⽤固定的知名端⼝号,例如 HTTP
使⽤端⼝ 80 或 8080 ,HTTPS 使⽤端⼝ 443 ,FTP 使⽤端⼝ 21 等。
追问:流程在传输层做了什么事 情?
主要有以下这些事 情:
建⽴连接:TCP 三次握⼿连接的建⽴,这个过程中会确定双⽅的初始序列号和 滑动窗⼝⼤⼩
数据传输 :当应⽤层的数据传递给传输层时,TCP 会将较⼤的数据分割 成适合在⽹络中传输的
较⼩的段,并为每个段添加 TCP 头部,包括序列号、确认号、窗⼝⼤⼩等信息。当接收⽅收到
TCP 段后,会发送 ACK 数据包来确认收到的数据。客⼾端根据服务器的 ACK 和窗⼝⼤⼩调整数
据发送速 度,继续发送后续 TCP 段。如果发送⽅在⼀定时间内没有收到 ACK ,会认为数据丢
失,会重传相应的 TCP 段,这个时间间隔 称为超时重传时间(RTO )。
关闭连接:关闭 TCP 连接时,会进⾏四次挥⼿过程。
追问:流程在⽹络层做了什么事 情?
传输层的 TCP 段传递到⽹络层,⽹络层将 其封装成 IP 数据包,设置源 IP 地址 和⽬的 IP 地址 ,
添加其他 IP 头部信息。
路由器根据 IP 数据包的⽬的 IP 地址 和⾃⼰的路由表进⾏路由选择,如果数据超过 MTU ⼤⼩,
就会对数据包进⾏分⽚。
数据包在⽹络中传输,经过多个路由器, 每个路由器都会根据路由表转发数据包,并递减
TTL 。
当数据包到达⽬的主机,⽹络层进⾏解封和重组操作,将 TCP 段传递给传输层。
追问:TCP 有TCP 的分段,IP 层有IP 分⽚。这两个 有什么 区别?现在⽤的是
哪个?
两者的区别:
TCP 分段:发⽣在传输层,由 TCP 协议执⾏。当 TCP 从应⽤层接收到较⼤的数据块,超过 MSS
⼤⼩之后,会将其分成多个较⼩的 TCP 段,每个 TCP 段都包含 TCP 头部,接收⽅根据 TCP 段
的序列号将它们重新组装成完整的数据块,然后传递给应⽤层。
IP 分⽚:发⽣在⽹络层,由 IP 协议执⾏。当 IP 数据包的⼤⼩超过链路层的最⼤传输单元
(MTU )时,IP 会将数据包分成多个较⼩的分⽚。每个 IP 分⽚包含 IP 头部,由⽬的主机的⽹
络层进⾏重组。⽬的主机根据 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 分⽚,提⾼传输效率。
拥塞控制介绍⼀下 ?

连接建⽴,cwnd = MSS ,进⼊慢启动阶段,每收到⼀个 ACK ,cwnd 翻倍,发送数据量不断增加。
当 cwnd 达到 ssthresh ,进⼊拥塞避免阶段,cwnd 线性增⻓。

如果发⽣超时,ssthresh 减半,cwnd 重置为 MSS (1 个MSS ),重新进⼊慢启动。

当发送⽅收到三个 重复的 ACK 时,认为数据包丢失,但不是因为⽹络拥塞,⽽是可能某个数据
包的乱序。在快速重传之后,发送⽅进⼊快速恢复阶段。通常将 ssthresh 减半,cwnd 设置为
ssthresh 加上 3 倍的 MSS (因为收到了三个 重复的 ACK ),并继续发送新数 据。

mysql
MySQL 是如何保 障数据不丢 失的?
主要是通过 redolog 来实现事务持久性的,事务执⾏过程,会把对 innodb 存储引擎中数据⻚修改
操作记录到 redolog ⾥,事务提交的时候,就直接把 redolog 刷⼊磁盘,即使脏⻚中途没有刷盘
成功, mysql 宕机了,也能通过 redolog 重放,恢复到之前事务修改数 据⻚后的状态,从⽽保障
了数据不丢 失。
RedoLog 是在内存⾥吗?
事务执⾏过程中,⽣成的 redolog 会在 redolog buffer 中,也就是在内存中,等事务提交的时
候,会把 redolog 写⼊磁盘。
为什么 要写RedoLog ,⽽不是直接写到B+ 树⾥⾯?
因为 redolog 写⼊磁盘是顺序写,⽽ b+ 树⾥数据⻚写⼊磁盘是随机写,顺序写的性能会⽐随机写
好,这样可以提升事务提交的效率。
最重要的是redolog 具备故障恢复的能⼒,Redo Log 记录的是物理级别的修改,包括⻚的修改,如
插⼊、更新、删除操作在磁盘上的物理位置和修改内容。例如,当执⾏⼀个更新操作时,Redo
Log 会记录修改的数据⻚的地址 和更新后的数据,⽽不是 SQL 语句本⾝。
在数据⻚实际更新之前,先将修改操作写⼊ Redo Log 。当数据库重启时,会进⾏恢复操作。⾸
先,根据 Redo Log 检查哪些事 务已经提交但数据⻚尚未完全写 ⼊磁盘。然后,使⽤ Redo Log 中
的记录对这些事 务进⾏重做(Redo )操作,将未完成的数据⻚修改完成,确保事务的修改⽣效。
mysql 两次写(double write buffer )了解吗?
我们常⻅的服务器⼀般都是Linux 操作系统,Linux ⽂件系统⻚(OS Page )的⼤⼩默认是4KB 。⽽
MySQL 的⻚(Page )⼤⼩默认是16KB 。
MySQL 程序是跑在Linux 操作系统上的,需要跟操作系统交互 ,所以MySQL 中⼀⻚数据刷到 磁盘,
要写4个⽂件系统⾥的⻚。

需要注意的是,这个操作并⾮原⼦操作,⽐如我操作系统写到第⼆个⻚的时候,Linux 机器断电
了,这时候就会出现问题了。造成”⻚数据损 坏“。并且这种”⻚数据损 坏“靠 redo ⽇志是⽆法修复
的。

Doublewrite Buffer 的出现就是为了 解决上⾯的这种情况,虽然名字带了Buffer ,但实际上
Doublewrite Buffer 是内存+磁盘的 结构。
Doublewrite Buffer 作⽤是,在把⻚写到数据⽂件之前,InnoDB 先把它们写到⼀个叫doublewrite buffer (双写缓冲区)的共享表空间内,在写doublewrite buffer 完成后,InnoDB 才会把⻚写到数
据⽂件的适当的位置。如果在写⻚的过程中发⽣意外崩溃,InnoDB 在稍后的恢复过程中在
doublewrite buffer 中找到完好的page 副本⽤于恢复,所以本质上是⼀个最近写回的⻚⾯的备份拷
⻉。

如上图所⽰,当有⻚数据要刷盘时:
⻚数据先通过memcpy 函数拷⻉⾄内存中的Doublewrite Buffer (⼤⼩为约 2MB )中,
Doublewrite Buffer 分为两个 区域,每次 写⼊⼀个区域(最多 1MB 的数据)。
Doublewrite Buffer 的内存⾥的数据⻚,会fsync 刷到 Doublewrite Buffer 的磁盘上,写两次到到
共享表空间中(连续存储,顺序写,性能很⾼),每次 写1MB ;写⼊完成后,再将脏⻚刷到 数据磁盘存储.ibd ⽂件上(随机写);
当MySQL 出现异常崩溃时,有如下⼏种情况发⽣:
情况⼀:步骤1前宕机,刷盘未开始,数据在redo log ,后期可以恢复
情况⼆:步骤1后,步骤2前宕机,因为是在内存中,宕机清空内存,和情况1⼀样
情况三:步骤2后,步骤3前宕机,因为DWB 的磁盘有完整的数据,可以修复损坏的⻚数据
由此我们可以得出结论,double write buffer 是针对实际的buffer 数据⻚的原⼦性保证,就是避免
MySQL 异常崩溃时,写的那⼏个data page 不会出错,要么都写了,要么什么 都没有做。
为什么 redolog ⽆法代替double write buffer ?
redolog 的设计 之初,是“账本的作⽤”,是⼀种操作⽇志,⽤于MySQL 异常崩溃恢复使⽤,是
InnoDB 引擎特有的⽇志,本质上是物理⽇志,记录的是 “ 在某个数据⻚上做了什么 修改 ” ,但如
果数据⻚本⾝已经发⽣了损坏,redolog 来恢复已经损坏的数据块是⽆效的,数据块的本⾝已经损
坏,再次重做依然是⼀个坏块 。 所以此时需要⼀个数据块的副本来 还原该损坏的数据块,再利⽤
重做⽇志进⾏其他数据块的重做操作,这就是double write buffer 的原因作⽤。
MySQL 的加锁机制?
在 MySQL ⾥,根据加锁的范围,可以分为全局锁、表级锁和⾏锁三类。

全局锁:通过flush tables with read lock 语句会将整个数据库就处于只读状态了,这时其他线程
执⾏以下操作,增删改或者表结构修改都会阻塞。全局锁主要应⽤于做全库逻辑备份,这样在
备份数据库期间,不会因为数据或表结构的更新,⽽出现备份⽂件的数据与预期的不⼀样。
表级锁:MySQL ⾥⾯表级别的锁有这⼏种:
表锁:通过lock tables 语句可以对表加表锁,表锁除了会限制别 的线程的读写外,也会限制
本线程接下来的读写操作。
元数据锁:当我们对数据库表进⾏操作时,会⾃动给这个表加上 MDL ,对⼀张表进⾏ CRUD
操作时,加的是 MDL 读锁;对⼀张表做结构变更操作的时候,加的是 MDL 写锁;MDL 是
为了 保证当⽤⼾对表执⾏ CRUD 操作时,防⽌其他线程对这个表结构做了变更。
意向锁:当执⾏插⼊、更新、删除操作,需要先对表加上「意向独占锁」,然后对该记 录加独
占锁。意向锁的⽬的是为了 快速判断表⾥是否有记录被加锁。
⾏级锁:InnoDB 引擎是⽀持⾏级锁的,⽽ MyISAM 引擎并不⽀持⾏级锁,⾏级锁如下:
记录锁,锁住的是⼀条记录。⽽且记录锁是有 S 锁和 X 锁之分的,满⾜读写互斥,写写 互斥
间隙 锁,只存在于可重复读隔离级别,⽬的是为了 解决可重复读隔离级别下幻读的现象。
Next-Key Lock 称为临 键锁 ,是 Record Lock + Gap Lock 的组合,锁定⼀个范围,并且锁定
记录本⾝。
为什么 要锁间隙 锁⽽不是加⾏锁?
可重复读隔离级会有间隙 锁,间隙 锁主要是为了 避免其 他事 务插⼊新记录,导致同⼀个事 务前 后
两次查询的结果集数不⼀致的幻读问题。

假设,表中有⼀个范围 id 为(3,5)间隙 锁,那么其他事 务就⽆法插⼊ id = 4 这条记录了,这样就有效的防⽌幻读现象的发⽣。
MVCC 的实现机制?
MVCC 允许多个事 务同时读取同⼀⾏数据,⽽不会彼此阻塞,每个事 务看到的数据版本是该事务开
始时的数据版本。这意味着,如果其他事 务在此期间修改了数据,正在运⾏的事务仍然看到的是
它开始时的数据状态,从⽽实现了⾮阻塞读操作。
对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过 Read View 来实现的,它们的
区别在于创建 Read View 的时机不同,⼤家可以把 Read View 理解成⼀个数据快照,就像相机拍
照那样,定格某⼀时刻的⻛景。
「读提交」隔离级别是在「每个select 语句执⾏前」都会重新⽣成⼀个 Read View ;
「可重复读」隔离级别是执⾏第⼀条select 时,⽣成⼀个 Read View ,然后整个事 务期间都在⽤
这个 Read View 。
Read View 有四个重要的字段:

m_ids :指的是在创建 Read View 时,当前数据库中「活跃事务」的事务 id 列表,注意是⼀个
列表,“活跃事务”指的就是,启动了但还没提交的事务。
min_trx_id :指的是在创建 Read View 时,当前数据库中「活跃事务」中事 务 id 最⼩的事务,也就是 m_ids 的最⼩值。
max_trx_id :这个并不是 m_ids 的最⼤值,⽽是创建 Read View 时当前数据库中应该给下⼀个事务的 id 值,也就是全局事务中最⼤的事务 id 值 + 1 ;creator_trx_id :指的是创建该 Read View 的事务的事务 id 。
对于使⽤ InnoDB 存储引擎的数据库表,它的聚簇索引记录中都包含下⾯两个 隐藏列:

trx_id ,当⼀个事 务对某条 聚簇索引记录进⾏改动时,就会把该事务的事务 id 记录在 trx_id 隐
藏列⾥;
roll_pointer ,每次 对某条 聚簇索引记录进⾏改动时,都会把旧版本的记录写⼊到 undo ⽇志
中,然后这个隐藏列是个指针,指向每⼀个旧版本记录,于是就可以通过它找到修改前的记
录。
在创建 Read View 后,我们可以将记录中的 trx_id 划分 这三种情况:

⼀个事 务去访问记录的时候,除了⾃⼰的更新记录总是可⻅之外,还有这⼏种情况:
如果记录的 trx_id 值⼩于 Read View 中的 min_trx_id 值,表⽰这个版本的记录是在创建 Read View 前已经提交的事务⽣成的,所以该版本的记录对当前事务可⻅。
如果记录的 trx_id 值⼤于等于 Read View 中的 max_trx_id 值,表⽰这个版本的记录是在创建Read View 后才启动的事务⽣成的,所以该版本的记录对当前事务不可⻅。
如果记录的 trx_id 值在 Read View 的 min_trx_id 和 max_trx_id 之间,需要判断 trx_id 是否在
m_ids 列表中:
如果记录的 trx_id 在 m_ids 列表中,表⽰⽣成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可⻅。
如果记录的 trx_id 不在 m_ids 列表中,表⽰⽣成该版本记录的活跃事务已经被提交,所以该
版本的记录对当前事务可⻅。
这种通过「版本链」来控制并发事务访问同⼀个记录时的⾏为就叫 MVCC (多版本并发控制)。
Redis
Redis 和数据库如何保 证数据⼀致性?
对于读数据,我会选择旁路缓存策略,如果 cache 不命中,会从 db 加载数据到 cache 。对于写数
据,我会选择更新 db 后,再删除缓存。

缓存是通过牺牲 强⼀致性来提⾼性能的。这是由CAP 理论决定的。缓存系统适⽤的场景就是⾮强⼀
致性的场景,它属于CAP 中的AP 。所以,如果需要数据库和缓存数据保持强⼀致,就不适合使⽤
缓存。
所以使 ⽤缓存提升性能,就是会有数据更新的延迟。这需要我们在设计 时结合业务仔细思考是否
适合⽤缓存。然后缓存⼀定要设置过期时间,这个时间太短、或者太⻓都不好:
太短的话请求可能会⽐较多的落到数据库上,这也意味着失去了缓存的优势。
太⻓的话缓存中的脏数据会使 系统⻓时间处于⼀个延迟的状态,⽽且系统中⻓时间没有⼈访问
的数据⼀直存在内存中不 过期,浪费内存。
但是,通过⼀些⽅案优化处理,是可以最终⼀致性的。针对删除缓存异常的情况,可以使 ⽤ 2 个
⽅案避免:
删除缓存重试策略(消息队列)
订阅 binlog ,再删除缓存(Canal+ 消息队列)
消息队列⽅案
我们可以引⼊消息队列,将第⼆个操作(删除缓存)要操作的数据加⼊到消息队列,由消费者来
操作数据。
如果应⽤删除缓存失败,可以从消息队列中重新读取数据,然后再次删除缓存,这个就是重试
机制。当然,如果重试超过的⼀定次数,还是没有成功,我们就需要向业务层发送报错信息
了。
如果删除缓存成功,就要把数据从消息队列中移除,避免重复操作,否则就继续重试。
举个 例⼦,来说明重试机制的过程。

重试删除缓存机制还可以,就是会造成好多 业务代码⼊侵。
订阅 MySQL binlog ,再操作缓存
「先更新数 据库,再删缓存」的策略的第⼀步是更新数 据库,那么更新数 据库成功,就会产⽣⼀
条变更⽇志,记录在 binlog ⾥。
于是我们就可以通过订阅 binlog ⽇志,拿到具体要操作的数据,然后再执⾏缓存删除,阿⾥巴巴
开源的 Canal 中间件就是基于这个实现的。
Canal 模拟 MySQL 主从 复制的交互 协议,把⾃⼰伪装成⼀个 MySQL 的从节点,向 MySQL 主节点
发送 dump 请求,MySQL 收到请求后,就会开始推送 Binlog 给 Canal ,Canal 解析 Binlog 字节流
之后,转换为便于读取的结构化数据,供下游程序订阅使⽤。
下图是 Canal 的⼯作原理:

将binlog ⽇志采集发送到MQ 队列⾥⾯,然后编写⼀个简单的缓存删除消息者订阅binlog ⽇志,根
据更新log 删除缓存,并且通过ACK 机制确认处理这条更 新log ,保证数据缓存⼀致性
如果做⼀个⼤流量的⽹站,单Redis ⽆法承压了如何解决?
读写分离:部署多个 Redis 从节点(Slave ),主节点(Master )负责 写操作,从节点负责 读操
作。主节点将数据同步到从节点,从节点可以处理⼤量的读请 求,减轻主节点的压⼒。
构建集群:部署 Redis Cluster 集群,Redis Cluster 将数据⾃动划分 为 16384 个槽(slots ),每
个槽都可以存储键值对。这些槽会被分配到多个 Redis 节点上,通过哈希函数将键映射到相应
的槽,再由槽映射到具体的 Redis 节点。例如,使⽤ CRC16(key) % 16384 来确定键属于哪个槽,然后根据槽与节点的映射关系将键值对存储到相应节点。通过数据分⽚,将数据和请求分
散到多个节点,避免单个节点的负载过 ⾼。不同节点负责 不同的槽,各⾃处理⼀部分请求,实
现负载均衡。
算法
lc. 152 乘积最⼤⼦数组
