浅谈 IO 多路复用

之前面试准备过这里的八股,但觉得理解不够深,故打算再探深些 五种 IO 模型 阻塞 IO 非阻塞 IO IO 多路复用 信号驱动 IO 异步 IO 我们先来看看 DMA 存在的必要性 当数据到达设备端,比如说网卡。网卡接收到数据后,传统非 DMA 的形式,此时会直接触发中断,此时 CPU 介入,负责解析数据,并且将数据 Copy 到 CPU 寄存器中,然后再将 CPU 寄存器中的数据 Copy 到内核内存中,然后解析再送到 Socket 缓冲区中,最后唤醒被阻塞的线程。分析上述流程,CPU 作为整个计算机的大脑,让他来处理这种傻白甜的反复读写操作,过于浪费,这就是 DMA 存在的必要性。当网卡收到数据后,通知 DMA,DMA 负责直接将数据 Copy 到内核内存中,然后网卡触发中断,CPU 将数据进行网络协议栈的解析,然后 Copy 到对应 Socket 的缓冲区中,然后唤醒线程。等到线程被调度的时候,会进入内核态,发现数据已经准备好,然后 Copy 到内存中直接使用,这就是现代计算机的一次阻塞 IO 过程。 阻塞 IO 最简单,最易懂 厨子做菜,做不好服务生也等着,等到菜做好再上菜。 上面其实已经讲过一次阻塞 IO 的流程了,只不过是站在 DMA 和 CPU 的角度来看的。我们来看看阻塞 IO 的瓶颈有哪些? 线程在 Read 数据的时候,如果数据没有准备好,那就陷入阻塞。假设数据不是必要的,有其他重要的事情做,那这里就会成为瓶颈,线程被阻塞,无法做事。 在阻塞 IO 的模型,一个 Scoket 对应一个线程,如果此时有上万个网络连接,那内核线程能否支撑这样大的并发量,所以这里也会成为瓶颈。 所以,阻塞 IO 虽然实现起来傻白甜,那是内核要考虑的问题就多了。 非阻塞 IO 厨子做菜,服务生不断轮询反问厨子有没有做好。 这一点对应到 CPU 就是进程不断调用 read(),如果数据准备好了,read()出数据,如果数据没有准备好,read()出 fasle,得到 false 的进程就去继续调用 read()。 ...

August 11, 2025 · 小石堆

Kotlin + SpringBoot 踩坑实录

最近在做 Kotlin 项目的时候,由于没有系统性学习过Kotlin,遇到了一个坑,当我的 Kotlin 项目的某个模块,使用 @Autowried 注解注入的时候,明明初始化过对象的成员变量,但是获取的时候,仍然为 Null。在排查完问题后,最终写下此篇,内容主要涉及到了 Kotlin 的 final 和 Spring 的代理机制。 问题复现 @Component open class ApiGw { private var endPoint:String? = null @PostConstruct open fun init() { endPoint = "初始化 endPoint" println(endPoint) println("this class" + this.javaClass.toString()) } @Cacheable(cacheNames = ["userCache"], key = "#id") fun printEndpoint(id: Long) { println(endPoint) println("this class" + this.javaClass.toString()) } } @RestController class ControllerA { @Autowired private lateinit var api: ApiGw @GetMapping("/hello") fun sayHello(): String { api.printEndpoint(123) return "Hello from Kotlin Spring Boot!" } } 上面的代码,调用 sayHello() 的时候,我预期 endPoint 的输出为 “初始化 endPoint”。因为在 @PostConstruct 的作用下,endPoint 已经被初始化过了。但是结果并非如此: ...

August 9, 2025 · 小石堆

《深入理解分布式系统》

第一章 Why we need? 以前我理解分布式系统只是简单的认为其作用是分散单机压力,而实际上是肤浅的,分布式系统有以下特点: 扩展性:现在大量数据密集型计算,而对于单机,当数据量增大到一定程度时,单机就无法扩展了,此时,需要分布式来扩展存储节点 可用性:多节点的存在,使得系统可以在规定数量节点宕机的情况下仍然保持提供正常服务。5个 9 的可用性。 高性能:现如今计算机发展早已突破摩尔定律的限制,单机性能不够时,我们可以往上堆料,但是单机的性能仍然是有上限的,而分布式可以简单的拓展系统中的计算节点,从而实现性能的扩展。 必要性:对于一些天然的场景,生来就是分布式系统,比如说跨行转账,两个行的数据库形成了天然的数据隔离,此时,需要分布式系统设计来保证这个场景下的安全。 第二章 两将军问题和拜占庭将军问题所引出的分布式难题! 在分布式系统中:节点是不可靠的,因为节点会宕机;网络是不可靠的,因为网络可能会延迟到达、不到达以及重复到达;时钟是不可靠的,因为多个节点之间的时钟难以同步。 第三章 分布式数据基础 现在大多业务都是数据密集型业务,从单机扩展到分布式的数据存储,既然有存储备份,就会有复制的概念 复制 Why we need replica? 假设系统中目前存在三个节点,为了保证三节点的一致性,replica 就是必然的,这里涉及到了三种复制模型。 单主复制 单主复制是比较简单的模型,例如之前实现的 Raft 就是单主复制模型,对于单主复制,又分为以下两个类型 同步复制:同步复制是指主节点在收到客户端日志后,将日志复制给从节点后,再确认其复制成功后,再将此次请求视为一次成功的请求,最后返回给客户端 异步复制:异步复制是指主节点在收到客户端日志后,在本机上完成操作后,立刻响应客户端,至于日志复制,则由后台异步发送给从节点 上面两种复制方式分别代表了可靠性和效率。 当实现同步的单主复制系统时,一次客户端请求可能会带来较高的延迟,因为需要所有的从节点响应后,才算一次完整的请求。 而异步的方式,虽然能极快的响应客户端请求,但是却无法确保从节点都复制了日志,例如上图,如果两个从节点都无法完成正常的复制,而主节点确正常响应了客户端请求,那么会大大降低系统的可用性。 单主复制的优缺点 优点: 单主复制系统设计简单,容易理解 单主复制的系统,友好高读低写场景,可以将读请求分散到其余从节点上,即使从节点压力过大,单主复制系统也易于扩展 由于其他节点并不处理写请求,只有 Leader 需要考虑并发请求,因此只要 Leader 保证自己的操作顺序,则其他节点亦能保证 缺点: 不难发现,单主复制只有 One Leader,当 Leader 节点宕机后,整个系统会陷入不可用的状态,这个不可用的时长和选主 or Leader恢复时长紧密相关 单主复制的写请求只由 Leader 节点来承担,所以写请求的性能瓶颈由 Leader 节点决定 多主复制 既然单主写拉跨,那就也由多个节点来承担写请求 多主复制系统在一定程度上分散了写请求的压力,但是不难看出,系统是容易出现不一致的。 eg. 例如上图,按照图示,整个系统最后的 x 值应该为3,但是我们设想一种情况,当 x = 1 的日志复制给主节点 1 和主节点 2 后,从节点的 x = 1 请求由于网络延迟还在路上,此时主节点 2 发起了 x = 3 的请求,这个请求成功的复制到了所有节点,次后,x = 1 的请求由来到了从节点上,导致两个主节点 x = 3,从节点 x = 1。此时系统产生不一致性。 通过上面的例子,我们不难发现,多主分布式系统要解决的第一个难题就是数据冲突(虽然单主也会发生数据冲突,但易于解决) ...

May 24, 2025 · 小石堆