上层应用开发的多了以后,对底层技术的接触就愈来愈少了。以致于不少人有了“底层技术无用论”的观点。不少人认为学习框架多好啊,你们都在用,跳槽的时候也能用的上。学习那些底层技术干啥,平时都用不到。前端
本号并不这么认为。咱们先举一个活生生的例子,好比咱们如今有个Web服务应用,崩溃重启后在绑定套接字的时候出现报错(socket_bind(): unable to bind address [98]: Address already in use。),致使服务端没法工做。问题比较明确,是地址(端口)被占用了。你这时候可能会猜想那个程序占了端口呢?你们都清楚,服务器端口的使用都是严格受限的,确定是这个程序。可是可能会疑惑:“这个程序不是刚刚起来吗?!”若是你只是使用API,不懂得底层的原理,别说解决问题,可能都不知道如何下手。这个问题咱们先放到这里,后面再具体解释,这里只是想说明一下底层技术的重要性。算法
另一个比较典型的例子是关于前端开发的。不少人热衷于学习各类框架。框架虽然能帮助咱们解决一些问题,节省开发成本并下降开发周期。可是,学习框架并不能掌握技术的根本,从而致使本身能力没有本质的提高。咱们之前端框架为例,在过去的几年当中,JQuery、Bootstrap、Angular和Vue等等等等,轮番上阵。这个框架你还没用熟悉呢,结果又来了一个新的框架,让你目不暇接。而这些框架最本质的东西其实就是JS、CSS和HTML等内容,只有学会这些基础技术,才能游刃有余。若是这些基础技术不熟悉,而投入大量精力学习框架,这就好像还没学会走,就想着跑,最后本身可能摔得满头是包。编程
可能扯的有点远,前面的例子只是想告诉你们底层技术的重要性。对于咱们搞软件开发的人来讲,底层技术其实至关于大厦的地基,地基不稳,大厦是很危险的。固然,计算机技术的细分领域不少,每一个领域又有本身的底层技术,所以咱们不可能都有涉及。今天咱们介绍的底层技术则是最为通用的技术,也就是计算、存储、网络和数据结构与算法。数组
关于计算相关的内容缓存
计算机技术天然核心是计算了。绝不夸张的说,全部应用都要依赖于计算,小到单机小游戏,大到电商或者云计算平台。所以,计算问题天然是咱们最为关心的问题了。说到计算,最主要的天然是程序的性能了,**若是咱们开发的程序的性能提高一倍,就至关于硬件成本下降了50%。**对于互联网这种须要大量计算资源的应用,其价值可见一斑。性能优化
咱们先看一个具体的例子。下面是一段C语言的代码,代码很简单,就是将二维数组中的内容作加一操做。可是若是你测试一下两段代码的耗时的话,就会发现二者有四倍的性能差别。你们能够观察一下图中两端代码的差别,并思考一下为何有如此之大的差别。前端框架
问题先放一下,咱们回到咱们今天的主角,CPU。CPU是计算依赖的硬件,你们都知道计算是在CPU内完成的。咱们先看一下CPU长什么样。CPU是计算机的核心单元,它负责从存储设备读取数据,通过计算后将生成的新数据再存储起来。这就好像一个大型工厂的生产车间,将原材料加工成半成品或者成品(咱们后面单独用一个章节介绍CPU相关的内容)。服务器
了解了CPU的基本功能,咱们再解剖了看看它的五脏六腑长什么样。下图是一个简化的CPU内部结构图,最为核心的组件就是计算单元(ALU)、寄存器(不少寄存器)和高速缓存。另外就是经过总线接口与外部的内存进行链接。这里面最核心的组件就是ALU了,其原理很简单,就是完成加减乘除运算。网络
CPU要进行运算,就须要原料,而原料须要从内存搬运。有一个事实咱们须要记住,就是访问内存的代价(延时)是访问寄存器的100倍左右。最先的CPU是直接访问内存的,后来随着ALU性能的提高,发现有问题,就在ALU和内存之间增长了缓存。现代CPU缓存一般为3级缓存,分别是L一、L2和L3,其中L1和L2是CPU核独有的,而L3是同一颗CPU的多核共享的。其基本的架构以下图所示。数据结构
这里面有个关键问题是缓存的容量是远远小于主(内)存的容量的,所以,缓存中的数据一般是主存数据的很小的一部分。因为应用访问数据有区域局部性的特色,所以缓存中的数据一般是程序须要的数据,也就是ALU接下来要用的数据。另一个须要注意的地方是从主存读取数据到缓存是有必定粒度(专业术语叫缓存行)的,当前处理器一般是64字节。以下图所示,主存中的内容被读取到缓存中。
而后,咱们回到一开始的关于上面两段程序的性能问题来。上面代码中一个是逐行访问二维数组,另一个是逐列访问二维数组。具体示意图以下图所示。
在逐行访问时,访问的地址是以4字节为单位跳跃的,因为缓存行大小是64字节,所以很容易命中缓存。而逐列访问时,每次跳跃4096字节,远远超越了缓存行的大小,从而致使数据大部分是从内存读取的。也正是由于这个,致使两个程序有四倍的性能差别。
经过上面的介绍,咱们应该记住两个关键点,一个是访问内存的代价比较高,所以在编程时尽可能减小对内存的直接访问;另一个是充分利用缓存的优点。关于如何作到上面两点,具体细节咱们后续专门介绍。
关于存储相关的内容
数据最终都要存储在存储设备上,不然系统一断电全部东西都丢了,这个道理你们都懂。这里的存储包括磁盘和SSD硬盘等内容。本文主要从存储设备及管理设备的文件系统分析存储相关关键技术。存储中最为重要的有两个方面,一个是存储数据的可靠性,另一个是存储数据的性能。
本文先从存储的性能提及,可靠性咱们后续专门介绍。在存储领域使用最多的仍是普通机械磁盘。机械磁盘的内部解剖图以下图所示,其数据的读写是经过一个机械臂完成的。机械臂摆来摆去,想一想就知道不会太快。机械磁盘是IBM发明的,第一块磁盘的寻道时间(机械臂定位到目的位置的时间)在600毫秒左右。而现代的机械磁盘寻道时间有了比较明显的改善,但因为其机械特性的缘由,其耗时仍是比较长的,大概是4-8毫秒的样子。
如下是付费内容
这个耗时是内存的近10万倍,是寄存器耗时的千万倍。所以机械磁盘的速度相对内存来讲,无异于蜗牛对高铁的速度。鉴于机械磁盘的上述缺陷,在软件层面作了不少考量,从而保证性能最佳。
咱们一般在使用硬盘的时候不会直接写代码访问(不排除个例),而是经过操做系统提供的接口访问。这个操做系统的接口一般是文件系统的接口。为了便于理解,咱们先看一下对于Linux操做系统来讲,磁盘系统的整个软硬件栈,从上到下分别是:文件系统、通用块层、设备驱动层和设备层(具体的硬件设备,能够理解为磁盘)。
在这里有两个层面的软件对磁盘的访问作了优化,一个是文件系统,另一个是通用块层。其中文件系统的核心功能是磁盘数据管理的功能,但考虑到磁盘的缺点,所以在读写数据方法作了一些性能方面的优化。而通用块层则主要是针对磁盘的特性进行了各类优化。
文件系统对磁盘访问的性能优化是经过页缓存(页缓存其实就是内存)完成的,这个页缓存与CPU中的缓存有殊途同归之妙。文件系统经过页缓存在数据写和读两方面分别做了优化。
写方面的优化主要是延迟批量写,也就是数据先写到页缓存中,通过积累后再磁盘驱动提交。这种积累和延迟写主要目的是为了增长数据的连续性,也就是为了规避磁盘机械臂的摆动,由于磁盘机械臂摆动是最耗时的。
读方面的优化主要是预读功能,预读就是根据当前应用读取数据的模式,提早将数据读到内存当中。因为应用访问数据的区域局部性特色,这种预读就能够避免应用直接从磁盘读取数据的延时,从而提升读性能。
通用块层的主要做用是针对磁盘作IO调度,通俗的讲就是决定哪一个IO先发送到磁盘,哪一个后发送到磁盘。
针对机械磁盘来讲,最为重要的就是通用块层会进行IO的重排序(根据逻辑地址排序)。如上图所示,假设上层应用按时间顺序发送一、二、三、4和5等5个请求的时候。此时,通用块层并不会按照时间顺序发送给磁盘,而是按照图中红色虚线箭头的顺序(一、五、二、四、3)发送给我。这样,磁盘的机械臂就不用来回摆动,从而大大提高其性能。
其实说了半天,这里有一点是须要咱们注意的,那就是机械磁盘不善于处理IO地址差别比较大的请求(会致使机械臂频繁摆动),这是咱们在作架构设计的时候须要注意的。虽然操做系统和通用块层为咱们作了不少工做,但其能力毕竟有限,所以咱们在设计的时候也必须考虑。后面咱们会经过实例给你们介绍大牛公司在设计应用的时候是如何考虑的。
因为篇幅问题,今天先到这里,后面再给你们介绍其它必备的底层技术。