learn-tech/专栏/计算机基础实战课/40内功心法(一):内核和后端通用的设计思想有哪些?.md
2024-10-16 10:18:29 +08:00

156 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

因收到Google相关通知网站将会择期关闭。相关通知内容
40 内功心法(一):内核和后端通用的设计思想有哪些?
你好我是LMOS。
前面我们学过了很多基础知识点,但你也许心中还是有点打鼓。要想跳出“边学边忘”的糟糕循环,除了温故知新,加深记忆,更重要的是把“内功心法”迁移到更多场景中。理解了技术的本质之后,在底层和应用层穿梭不是问题,在前端和后端切换也会更加游刃有余。
接下来的两节课,我会带你一起看看内核和后端通用的设计思想都有哪些,它们又是如何用在具体技术里的?这节课我先分享三大通用“心法”,分别是并行化、异步和调度。
内功心法之并行化
我们专栏最前面讲过图灵机,刚开始接触到它的时候,是不是感觉图灵机的串行纸带模型对计算机做了非常好的抽象呢?然而,现实世界里我们如果只使用串行模型来解决问题,恐怕就比较低效了。
那么如何才能解决串行处理的低效问题呢?这就不得不说到并行化了。
关键路径和阿姆达尔定律
我先描述一个现象,你看看是不是很熟悉:一段程序放在面前,你需要对它进行性能优化,但你辛辛苦苦调了许久,优化效果却并不明显。
之所以会遇到这样的问题,核心原因是我们没有梳理清楚这段程序的关键路径,并对关键路径做有效优化。那么如何使用关键路径这种工具呢?我给你讲个番茄炒蛋盖饭的故事。
你没走错片场,咱们梳理一下做一道番茄炒蛋盖饭,都需要做什么。我们先在脑中把整个过程拆解成下图中的具体步骤。然后,在每一个步骤上标出这个步骤的耗时。你可以参考后面这张流程图看一下。
对照示意图我们就会发现吃上盖浇饭的最短时间其实是实线部分的35分钟这条最短路径就是做成这件事情的关键路径。
当我们想要优化做这道菜的时间的时候我们可以先考虑优化黑线中的关键路径比如我们可以考虑买10个电饭锅并行化让每个电饭锅煮少一点这样可以熟得更快一些把煮饭时间也缩短到5分钟。这样整体时间就会得到优化。这个例子可能和实际做饭的情况不大一样不过这里我们主要是为了说明并行化这件事也期待你找到一个更贴切的事情做类比。
其实我们做程序优化的时候也是如此,很多时候明明优化了却不太见效,本质上是因为没有找对程序运行中的关键路径。
那怎么解决这种问题呢我们可以根据日志等信息把整个程序的运行步骤梳理清楚绘制出上面这样的PERT图之后优化的重点就一目了然了。也许这时候你会发现之前自己根本就没有优化对地方。
有了前面把关键路径上的某个环节并行化的例子,你可能会好奇,是不是并行化无所不能,以后就靠并行化来优化系统就行了呢?
其实不然,并行化也有自己的局限性,这里就要提到阿姆达尔定律了。阿姆达尔定律是计算机工程中的一条经验法则,它的定义是:在并行计算中用多处理器的应用,加速受限于程序所需的串行时间百分比。
只说定义不好理解举个例子如果你有一段程序其中有一半是串行的另一半是并行的那么这段程序的最大加速比例就是2。
这就意味着不管你如何优化程序,无论是让它运行在多核,或者分布到不同的机器上,这个加速比例都没有办法提高。这种情况下,我们可以优先考虑改进串行的算法,可能会带来更好的提升。
后端场景中的并行化思想
内核中并行化思想有很多应用。比如说支持SMP处理器、并行IO、使用MMX/SSE/AVX指令基于向量化的计算方式优化程序性能之类的操作本质上都是在用并行化的思路来提升性能。
而在后端场景下,并行化思想其实又进一步做了扩展。后端的并行化并不仅仅局限于单机上的物理机资源的并行化,我们还可以基于多进程/线程/协程等抽象的概念,并发请求网络上的不同机器进行计算,从而实现更高的效率。
当然需要注意的是发起多(进程/线程/协程)调用的客户端节点,有可能是单核的,也可能是多核心的。如果是多核心情况下的调用,我们称之为并行;而单核心时我们会叫做并发。
虽然概念和实现略有不同但并行化的核心思想本质是相通的。举个例子吧比如当我们使用下边这段程序开启多个协程来同时发起http请求的时候本质就是在借助并行化的思想来提升效率
func main() {
i := 0
// 使用WaitGroup原语等一组goroutine全部完成之后再继续
wg := &sync.WaitGroup{}
for i < 10 {
// 增加计数器
wg.Add(1)
url := "https://time.geekbang.org"
go func(url string) {
resp, _ := http.Get(url)
defer resp.Body.Close()
data, _ := ioutil.ReadAll(resp.Body)
// 释放计数器
wg.Done()
}(url)
i++
}
// 阻塞住等待所有协程执行完毕时再释放
wg.Wait()
fmt.Println("end")
}
不难发现虽然发起了10次请求但其实多个Goroutine是并发发起请求的所以最终响应时间只取决于最慢的那一次请求我们可以发现整体耗时要比串行发起10次请求短多了
内功心法之异步化
学习了并行化这个思想之后我再来说说其他更有趣的优化思路异步化思想这也是我们经常用来解决问题的一个神器
当一个事情处理起来比较消耗资源我们就会考虑把这个事情异步化比如我们去某个网红饭店点菜如果是同步处理的话我们需要每隔一分钟就把服务员叫过来问一次菜好了没有这样做会同时占用你和服务员的资源估计问不了几次你就崩溃了
这时候聪明的服务员就想到了一个办法当你点单之后就给你发一个号码牌等菜做好了之后服务员再按照号码牌把菜送上来这样在等待的过程中你还可以干点别的事情服务员也不会被一桌客人给锁定住无法服务别的顾客由此效率就得到了提升这样的操作就是异步化处理
我们在之前虚拟内存的时候可以回顾[第二十四节课]其实就已经接触过异步化了当我们的程序配置好中断之后就可以运行别的逻辑去了这样当中断发生的时候内核才会调用对应的中断处理函数这其实就是一种异步化的思路
内核里异步化思想随处可见不光中断机制Linux内核中的信号机制工作队列workqueue_struct其实也都大量使用了异步化思想
异步化思想在后端中架构中也有很多应用比如为了提高后端服务的吞吐能力我们可以使用AIOepoll做IO消息处理的异步化当我们有较多写入请求为了避免击穿下游系统我们也可以用下图中的队列思路来进行异步的削峰填谷
再比如一个A系统原本通过直接调用耦合了下游BCD子系统需要等下游处理完毕才能返回的时候我们也可以基于队列进行异步处理从而降低耦合提升响应时间你可以对照后面的流程图理解一下这段话
掌握了异步化思想之后你就可以基于相同的思路举一反三来设计出分布式事务分布式计算框架之类等更多有用的中间件啦
内功心法之调度
现实世界里我们手里的资源往往是有限的但需求却往往趋近于无限怎么平衡这种矛盾呢没错为了更好地利用资源就出现了调度这个概念调度思想的核心就是通过各种调度手段让有限的资源尽可能得到更高效的利用
操作系统内核中调度无处不在比如为了更好地抽象CPU资源OS内核抽象出了进程/线程面对CPU资源有限有CPU资源需求的进程/线程可能有无限多的情况OS内核设计出了各种调度算法
就拿CFS调度器来说它在调度上非常公平它记录了每个进程的执行时间哪个进程运行时间最少就让那个进程运行更多细节我在第一季操作系统实战45讲[第二十七课]详细分享过感兴趣的话你可以去看看
再比如为了更好地使用物理内存OS内核抽象出了虚拟内存那如何调度这些内存呢内核又设计出了页面调度算法还有就是为了管理磁盘中的数据OS抽象出了文件概念这还没完如何提读写升效率呢OS又设计出了各种磁盘调度算法
调度思想在后端架构中其实也很常见以GolangJava编程语言为例在语言内的运行时库中也会包含对进程/线程/协程内存的调度管理策略
在业务层面我们很多时候也会开发很多后台作业为了提升这些作业的性能和作业的可用性我们也会基于分布式任务调度框架进行后台作业的分布式调度我给你举个具体点的例子带你看看Apache DolphinScheduler的架构图
当然为了满足分布式大数据领域的各种业务场景Apache DolphinScheduler设计的其实比较复杂但是到回归架构设计上我们发现大多数分布式任务调度系统都会包含这以下五个部分
控制台用于展示调度任务的配置依赖关系任务状态等信息-
接入将控制台的作业转化下发给调度器模块并且向注册中心注册任务-
调度器接收接入下发的调度任务进行任务拆分下发在注册中心找执行器然后把任务下发到执行器执行同时也注册到注册中心-
执行器接收调度任务并且上报状态给注册中心-
注册中心主要用于节点任务状态的协调与同步
虽然这五个部分看起来有点复杂但是我们回归到设计一个调度系统问题的本质上来思考调度系统解决的关键问题其实是将一些资源分配给一些并且保证能按照一定的顺序在一定资源开销的前提下处理完顺着这条主线理解起来就会清晰很多了
总结
今天我带你了解了三种内核和后端通用的设计思想我也举了不少例子方便你了解这些思想如何用在后端应用层和内核软件里
其实今天的课程内容属于偏抽象的架构思想目的是帮你拓宽思路把学过的知识融会贯通因此建议你学习完了之后再结合你自己的兴趣自行拓展延伸如果你领会到了这思想的本质不妨试试应用在技术实践上相信会让你的开发工作更得心应手
另外我还挑选了三个代表性的项目它们很好地应用了今天所讲的设计思想你可以课后了解一下
并行化可以参考Hadoop项目https://hadoop.apache.org/
异步化可以参考Pulsar项目https://pulsar.apache.org/
调度可以参考前文中提到的DolphinScheduler项目https://dolphinscheduler.apache.org/
最后我给你梳理了一张导图供你做个参考
思考题
今天我们学习了在计算机系统中常用的并行化异步化和调度这三种通用的设计思想那么请你思考一下自己工作生活中还有哪些场景用到了这些思想呢
期待看到你的分享我在留言区等你如果觉得这节课还不错别忘了转发给更多朋友跟他一起交流学习