标签:# 操作系统

深入理解计算机系统——信息的表示与处理

参考内容: 《深入理解计算机系统》(第三版) 字数据大小 前面已经提到过信息=位+上下文,但是基本上的计算机都没有将位作为最小的可寻址单位,而是将字节作为了最小的可寻址单位,内存就是一个非常大的字节数组,它的的每个字节都由一个唯一的数字来标识(这个数字是不需要存的),所有可能的地址集合就是虚拟地址空间。 我们常说的 32 位、64 位指的是一台计算机的字长,用于指明指针数据的的标称大小。有的面试官在面试的时候会问这样一个问题:在 C/C++ 中指针的大小是多少?如果你一下就回答出来时多少个字节了,那基本上不必再问了,因为一个指针的大小取决于计算机的字长,所以应该分 32 位机还是 64 位机的情况。 字长还会决定一个极为重要的系统参数——虚拟地址空间。比如现在有一个 32 位机,每一位可以取值 1 或 总共 32 位,能组合的出局就有 232 个,所以它能访问 232 个地址,其大小也就是 4G,因此你如果给 32 位机装上 8G 的内存条,是起不了多大作用的。 我们平时所说的 32 位程序和 64 位程序并不是指机器的字长,它们的区别在于程序时如何编译的,而不是其运行的机器类型,高版本都应该做到向后兼容,所以 32 位程序一般都能运行在 64 位机器上,而 64 位程序时不能运行在 32 位机上面的。下面两种伪指令就分别用于编译 32 位程序和 64 位程序。 gcc -m32 prog.c gcc -m64 prog.c C 语言在 32 位机和 64 位机上所表现的差别在于long数据类型,一般在 32 位机上是 4 个字节,而在 64 位机上是 8 个字节,而作为程序员要力图程序能在不同的机器上进行编译执行,要做到这一点就需要保证程序对不同数据类型的确切大小不敏感。 曾经某运营商的一个基站版本因为数据范围的不同而造成了巨大的损失,在编程环境中使用的是 32 位机,而基站所使用的处理器没有 32 位,最后表现的效果就是大概每隔 40 天,基站就自动复位了。定位到这个问题都花费了巨大的财力和人力资源。 寻址及字节顺序 上文已经提到,有很多的对象实际上不止占用一个字节,而是占用了多个字节,此时就涉及到如何排列这些字节了,以及如何存储这些字节。以11001100 11001100为例,它占用了两个字节,我们可以选择将这两个字节放在连续的内存中,也可以将两个字节分开放在不连续的内存中;另外我们可以将左边的字节当做起始位置,也可以将右边的字节当做起始位置(更专业的称为大端法和小端法)。 对于字节的排列,到底是用大端法还是小端法,没有技术上的争论,只有社会政治论题的争论,而且机器它对程序员是完全不可见的。几乎所有的机器都将多字节对象存储为连续的字节序列,所使用字节中最小的地址作为对象的地址。 那么什么时候需要注意字节的顺序规则呢,那就是编写网络应用程序的时候,试想你传输的数据是用大端法表示的,而用户的计算机采用的是小端法,那还会有用户使用你的产品吗。所以编写网络程序时需要遵循已经建立的关于字节顺序的规则。 整数表示 程序员对二进制不会不知道,比如 11111111表示的是 255(不考虑补码),很容易就能转换为我们所熟悉的 10 进制数据。这种方式我们默认它是无符号数,如果要加入有符号数就开始变得有趣了。 几乎所有的计算机都是采用有补码来表示有符号整数的,它与无符号整数的区别在于最高位被解释为负权,举个例子:将1111看做补码的话,它的值就为:-23 + 22 + 21 + 20 = -1。 在程序中不可避免的会使用强制类型转换,C 语言中强制类型转换并没有改变数据的位值,只是改变了解释这些位的方式。比如将无符号数(unsigned) 53191 转换为有符号数的结果为 -12345,它们的位值是完全没有相同的。 最容易入坑的地方是,对两个不同类型的数据进行运算时,C 语言将会隐式的将有符号数转换为无符号数,所以就有下面这样一个神奇的结果。 // u 代表无符号数 -1 < 0u // 结果为 0 // 因为 -1 的补码表示为:11...11 // 转换为无符号数后就是范围内最大的数 如果需要扩展一个数的位表示,那么放心的扩展就好了,小的数据类型都能安全的向大的数据类型转换,补码表示的数会在前面补上符号位,原码表示的直接在前面补上 0 即可,而需要注意的是从大往小转,这会不可避免的截断位,造成信息的丢失,所以千万不要这么干。 加法、乘法运算 在编程入门的时候可能都知道两个正数相加的结果可能为负数,还有一个更奇怪的现象就是:x < y和 x - y < 0两个表达式可能会得出不一样的结果,这些神奇的结果都和计算机整数的底层表示和运算有着密切的关系。 C 语言中有无符号数与有符号数之分,而在 Java 中只有有符号数,下面的内容还是基于 C 语言进行说明,毕竟更 C 比 Java 更接近底层嘛。 无符号加法 假设我们使用 w 位来表示无符号数,那么两个加数取值范围即为:0 ≤ x, y <2w,理论上它们的和的范围为:0 ≤ sum < 2w+1,因为只有 w 位表示无符号数(要把和表示出来就需要 w+1 位),所以超过 zw的部分就会造成溢出,如下图所示。 对于无符号数的溢出,计算机采用的处理方式是丢掉最高位,直观的结果就是,当发生溢出了,就将采用取模运算(或者说是减去 2w),举个例子。 只用 4 为来表示无符号数,即 w = 4,现在有 x [1001] 和 y [1100] 相加,其结果应为:[10101] ,但是没有 5 位用来表示,所以丢掉最高位的1,剩下的值为 5 [0101],也就是 21 mod 16 = 5。 那么如何检测是否发生溢出呢?设求和结果为 s,对于加法有 x + y ≥ x 恒成立,即只要没有发生溢出,肯定有 s ≥ x。另一方面,如果确实发生溢出了,就有 s = x + y - 2w,又有 y - 2w < 0,因此 s = x + y - 2w < x。 补码加法 和前面一样,对于两个给定范围的加数 - 2w-1 ≤ x, y ≤ 2w-1 - 1,它们的和的范围就在 - 2w ≤ sum ≤ 2w - 2。要想把整个和的范围表示出来,依旧需要 w+1 位才行,而现在只有 w 位,因此还是需要采用将溢出部分截断。 可以发现,当发生正溢出时,截断的结果是从和数中减去了 2w;而当发生负溢出时,截断结果是把和数加上 2w。 那么对于补码加法如何检测溢出结果呢?通过分析可以发现,当且仅当 x > 0, y > 0,但和 s ≤ 0 时为正溢出;当且仅当 x < 0, y < 0,但 s ≥ 0 时发生负溢出。 无符号乘法 有了前面的基础,乘法就变得简单一些了,对于溢出情况,计算机仍然采用的是求模,比如 0 ≤ x, y ≤ 2w - 1,它们乘积的范围为 0 到 22w - 2w+1 + 1 之间,这可能需要 2w 位来表示,溢出部分直接截掉,如下所示。 补码乘法 对于补码,两个乘数的范围为:- 2w-1 ≤ x, y ≤ 2w-1 + 1,那么其乘积表示范围就为 - 22w-2 + 2w-1 到 22w-2 之间,补码乘法和无符号乘法基本是一样的,只是在无符号基础上多加了一步转换,即将无符号数转换为补码。 乘以常数 我们知道,计算机做加减法、位级运算的速度最快(1 个指令周期),而做乘除法很慢(10 个甚至更多指令周期),平时编写的程序中常常会乘以一个常数,为了使程序运行的更快,编译器可能会帮我们做一些处理。 首先我们考虑常数是 2 的幂。x * 21 可以表示为 x << 1,x * 22 可以表示为 x << 2,依次类推即可。 对于不是 2 的幂的常数,比如 x * 14 可以表示为:(x<<3) + (x<<2) + (x<<1),因为 14 = 23 + 22 + 21;聪明的你可能发现 14 还有另一种表示方法,即 14 = 24 - 21,这种表示比前一种表示方法又少了运算量,所以 x * 14 还可以表示为:(x<<4) - (x<<1)。 实际上,这里有一个通用的解决方案,对于任何一个常数 K,其二进制可以表示为一组 0 和 1 交替的序列:[(0...0)(1...1)(0...0)(1...1)],14可以表示为:[(0...0)(111)(0)],考虑一组从位位置 n 到位位置 m 的连续的 1 (n ≥ m),(对于 14 有 n = 3,m = 1)可以有两种形式来计算位对乘积的影响。 这个优化不是一定的,大多数编译器只在需要少量移位、加减法就足够的时候才使用这种优化。
Read More ~

磁盘到底是怎样工作的?一文理解硬盘结构

数据库系统总会涉及到辅助存储(大多都是磁盘),因为它们能够存储大量需要长期保存的数据,因此我们有必要先了解了解磁盘的相关知识。 根据机械原理,存储器的容量越大其速度就越慢。但是速度越快的存储器,其单位字节的价格就越贵。现代计算机系统可以包含几个不同的可以存储数据的部件,就形成了存储器的层次结构,但是需要注意的是「虚拟内存」是操作系统与操作系统运用机器硬件的产物,它不是存储器的层次之一。 磁盘结构 传统的硬盘盘结构是像下面这个样子的,它有一个或多个盘片,用于存储数据。盘片多采用铝合金材料;中间有一个主轴,所有的盘片都绕着这个主轴转动。一个组合臂上面有多个磁头臂,每个磁头臂上面都有一个磁头,负责读写数据。 磁盘一般有一个或多个盘片。每个盘片可以有两面,即第一个盘片的正面为0面,反面为 1 面;第二个盘片的正面为 2 面......依次类推。磁头的编号也和盘面的编号是一样的,因此有多少个盘面就有多少个磁头。盘面正视图如下图,磁头的传动臂只能在盘片的内外磁道之间移动。因此不管开机还是关机,磁头总是在盘片上面。关机时,磁头停在盘片上面,抖动容易划伤盘面造成数据损失,为了避免这样的情况,所以磁头都是停留在起停区的,起停区是没有数据的。 每个盘片的盘面被划分成多个狭窄的同心圆环,数据就存储在这样的同心圆环上面,我们将这样的圆环称为磁道 (Track)。每个盘面可以划分多个磁道,最外圈的磁道是0号磁道,向圆心增长依次为1磁道、2磁道......磁盘的数据存放就是从最外圈开始的。 根据硬盘的规格不同,磁道数可以从几百到成千上万不等。每个磁道可以存储数 Kb 的数据,但是计算机不必要每次都读写这么多数据。因此,再把每个磁道划分为若干个弧段,每个弧段就是一个扇区 (Sector)。扇区是硬盘上存储的物理单位,现在每个扇区可存储 512 字节数据已经成了业界的约定。也就是说,即使计算机只需要某一个字节的数据,但是也得把这个 512 个字节的数据全部读入内存,再选择所需要的那个字节。 柱面是我们抽象出来的一个逻辑概念,简单来说就是处于同一个垂直区域的磁道称为柱面 ,即各盘面上面相同位置磁道的集合。需要注意的是,磁盘读写数据是按柱面进行的,磁头读写数据时首先在同一柱面内从 0 磁头开始进行操作,依次向下在同一柱面的不同盘面(即磁头上)进行操作,只有在同一柱面所有的磁头全部读写完毕后磁头才转移到下一柱面。因为选取磁头只需通过电子切换即可,而选取柱面则必须通过机械切换。数据的读写是按柱面进行的,而不是按盘面进行,所以把数据存到同一个柱面是很有价值的。 磁盘被磁盘控制器所控制(可控制一个或多个),它是一个小处理器,可以完成一些特定的工作。比如将磁头定位到一个特定的半径位置;从磁头所在的柱面选择一个扇区;读取数据等。 现代硬盘寻道都是采用CHS(Cylinder Head Sector)的方式,硬盘读取数据时,读写磁头沿径向移动,移到要读取的扇区所在磁道的上方,这段时间称为寻道时间(seek time)。因读写磁头的起始位置与目标位置之间的距离不同,寻道时间也不同。磁头到达指定磁道后,然后通过盘片的旋转,使得要读取的扇区转到读写磁头的下方,这段时间称为旋转延迟时间(rotational latencytime)。然后再读写数据,读写数据也需要时间,这段时间称为传输时间(transfer time)。 根据上文的信息,我们可以得出磁盘容量的计算公式为: 硬盘容量 = 盘面数 × 柱面数 × 扇区数 × 512字节 笔试题实战 下面的题目是腾讯某一年校招笔试中的一个题目,题干信息描述为:数据存储在磁盘上的排列方式会影响I/O服务的性能,一个圆环磁道上有10个物理块,10个数据记录R1~R10存放在这个磁道上,记录的安排顺序如下表所示。 物理块 1 2 3 4 5 6 7 8 9 10 逻辑记录 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 假设磁盘的旋转速度为20ms,磁盘当前处在R1的开头处,若系统顺序扫描后将数据放入单缓冲区内,处理数据的时间为4ms(然后再读取下个记录),则处理这10个记录的最长时间是多少? 答案:磁盘会一直朝某个方向旋转,不会因为处理数据而停止。本题要求顺序处理 R1 到 R10,起始位置在 R1,一周是 20ms,共 10 个记录,所以每个记录的读取时间为 2ms。首先读 R1 并处理 R1,读 R1 花 2ms,读好后磁盘处于 R1 的末尾或 R2 的开头,此时处理 R1,需要 4ms,因为磁盘一直旋转,所以 R1 处理好了后磁盘已经转到 R4 的开始了,这时花的时间为 2+4=6ms。这时候要处理 R2,需要等待磁盘从 R5 一直转到 R2 的开始才行,磁盘转动不可反向,所以要经过 8*2ms 才能转到 R1 的末尾,读取 R2 需要 2ms,再处理 R2 需要 4ms,处理结束后磁盘已经转到 R5 的开头了,这时花的时间为 2*8+2+4=22ms。等待磁盘再转到 R3 又要 8*2ms,加上 R3 自身 2ms 的读取时间和 4ms 的处理时间,花的时间也为 22ms,此时磁盘已经转到 R6 的开头了,写到这里,就可以看到规律了,读取并处理后序记录都为 22ms,所以总时间为 6+22*9=204ms。 如何加速对磁盘的访问 对于理解数据库系统系统特别重要的是磁盘被划分为磁盘块(或像操作系统一样称之为页),每个块的大小是 4~64KB。磁盘访问一个磁盘块平均要用 10ms,但是这并不表示某一应用程序将数据请求发送到磁盘控制器后,需要等 10ms 才能得到数据。如果只有一个磁盘,在最坏的情况下,磁盘访问请求的到达个数超过 10ms 一次,那么这些请求就会被无限的阻塞,调度延迟将会变的非常大。因此,我们有必要做一些事情来减少磁盘的平均访问时间。 按柱面组织数据:前这一点在前文已经提到过了。因为寻道时间占平均块访问时间的一半,如果我们选择在一个柱面上连续的读取所有块,那么我们只需要考虑一次寻道时间,而忽略其它时间。这样,从磁盘上读写数据的速度就接近于理论上的传输速率。 使用多个磁盘:如果我们使用多个磁盘来替代一个磁盘,只要磁盘控制器、总线和内存能以 n 倍速率处理数据传输,则使用 n 个磁盘的效果近似于 1 个磁盘执行了 n 次操作。因此使用多个磁盘可以提高系统的性能。 磁盘调度:提高磁盘系统吞吐率的另一个有效方法是让磁盘控制器在若干个请求中选择一个来首先执行,调度大量块请求的一个简单而有效的方法就是电梯算法。回忆一下电梯的运行方式,它并不是严格按先来后到的顺序为乘客服务,而是从建筑物的底层到顶层,然后再返回来。同样,我们把磁盘看作是在做横跨磁盘的扫描,从柱面最内圈到最外圈,然后再返回来,正如电梯做垂直运动一样。 预取数据:在一些应用中,我们是可以预测从磁盘请求块的顺序的。因此我们就可以在需要这些块之前就将它们装入主存。这样做的好处是我们能较好的调度磁盘,比如采用前文的电梯算法来减少访问块所需要的平均时间。 磁盘故障 如果事情都像我们一开始设计的那样进行,那世界肯定会变得特别无聊。磁盘偶尔也会耍耍小脾气,甚至是罢工不干了。比如在读写某个扇区一次尝试没有成功,但是反复尝试后有成功读写了,我们称之为间歇性故障。 一种更为严重的故障形式是,一个或多个二进制位永久的损坏了,所以不管我们尝试多少次都不可能成功,这种故障称之为介质损坏。 另一种相关的错误类型称之为写故障,当我们企图写一个扇区时,既不能正确的写,也不能检索先前写入的扇区,发生这种情况的一种可能原因就是在写过程中断电了。 当然肯定最严重的就是磁盘崩溃,这种故障中,整个磁盘都变为永久不可读,这是多么可怕的事情。 既然会出现上面所述的各种大小故障,那么我们就必须要采取各种措施去应对大大小小的变故,保证系统能正常运行。 规避故障 我们尝试读一个磁盘块,但是该磁盘块的正确内容没有被传送到磁盘控制器中,就是一个间歇性故障发生了。那么问题是控制器如何能判断传入的内容是否正确呢?答案就是使用校验和,即在每个扇区使用若干个附加位。在读出时如果我们发现校验和对数据位不合适,那么我们就知道有错误;如果校验和正确,磁盘读取仍然有很小的可能是不正确的,但是我们可以通过增加趣多校验位来降低读取不正确发生的概率。 此处我们使用奇偶校验来举例,通过设置一个校验位使得二进制集合中 1 的个数总是偶数。比如某个扇区的二进制位序列是 01101000,那么就有奇数个 1,所以奇偶位是 1,这个序列加上它后面的奇偶位,就有 011010001;而如果所给的序列是 11101110,那么奇偶位就是 0。所以每一个加上了奇偶位构成的 9 位序列都有偶数奇偶性。 尽管校验和几乎能正确检测出介质故障或读写故障的存在,但是它却不能帮助我们纠正错误。为了处理这个问题,我们可以在一个或多个磁盘中执行一个被称为稳定存储的策略。通常的思想是,扇区时成对的,每一对代表一个扇区内容 X。我们把代表 X 的扇区对分别称为左拷贝 XL和右拷贝XR。这样实际上就是每个扇区的内容都存储了两份,操作XL失败,那么去操作XR就可以了,更何况我们还在每个扇区中有校验和,把错误的概率就大大降低了。 到现在为止,我们讨论的都是简单的故障,但是如果发生了磁盘崩溃,其中的数据被永久破坏。而且数据没有备份到另一种介质中,对于银行金融系统这将是巨大的灾难,遇到这种情况我们应该怎么办呢? 数据恢复 应对磁盘故障最简单的方式就是镜像磁盘,即我们常说的备份。回忆一下写毕业论文时的做法,那时候大部分同学还不会用版本控制器,所以基本采用每天备份一次数据,并且在文件名称中标注日期,以此来达到备份的效果。 第二种方式是使用奇偶块,比如一个系统中有 3 个磁盘,那么我们再加一个磁盘作为冗余盘。在冗余盘中,第 i 块由所有数据盘的第 i 块奇偶校验位组成。也就是说,所有第 I 块的第 j 位,包括数据盘和冗余盘,在它们中间必须有偶数个 1,冗余盘的作用就是让这个条件为真。 我们举个简单例子,假设快仅由一个字节组成,我们有三个数据盘和一个冗余盘,对应的位序列如下。其中 盘4 为冗余盘,它的位序列是根据前面三个盘计算出来的。 盘 1:11110000 盘 2:10101010 盘 3:00111000 盘 4:01100010 假设现在某个盘崩溃了,那么我们就能根据上面的序列来恢复数据,只需要让每一列 1 的个数为偶数就可以了,但是这种冗余方式也存在很大的不足。 第一个缺陷是,如果是两个盘同时崩溃了,那数据也恢复不出来了。第二个问题在于,虽然读数据只需要一次 I/O 操作即可,但是写数据时就不一样了,因为需要根据其他数据盘来计算冗余盘中的位序列,假设共有 n 个盘,其中一个为冗余盘,所以每次写数据时,都需要进行 n+1 次 I/O 操作(读不被写入的 n-1 个盘,被重写数据盘的一次写,冗余盘的一次写),而 I/O操作又是非常耗时的操作,所以这种方法会大大拖慢系统性能。 另一种方案是没有明显的冗余盘,而是把每个磁盘作为某些块的冗余盘来处理。比如现在有 4 个盘,0 号磁盘将作为编号为 4、8、12 等柱面的冗余,而 1 号磁盘作为编号为 1、5、9 等块的冗余...... 一种更为先进的方式使用海明码来帮助从故障中恢复数据,它在多个磁盘崩溃的情况下也能恢复出数据,也是 RAID 的最高等级,由于本人水平有限,用文字表达不清楚,就不作介绍了,嘿嘿。
Read More ~

FastDFS 分布式文件系统简介

文章内容是刘欣大大(《码农翻身》作者,公众号:码农翻身)的直播课内容,主要是了解一下分布式文件系统,学习FastDFS的一些设计思想,学习它怎么实现高效、简洁、轻量级的一个系统的 FastDFS分布式文件系统简介 国内知名的系统级开源软件凤毛菱角,FastDFS就是其中的一个,其用户包括我们所熟知的支付宝、京东商城、迅雷、58同城、赶集网等等,它是个人所开发的软件,作者是余庆。 我们已经进入互联网时代,互联网给我们的生活带来便捷的同时,也给我们带来了诸多挑战。 对于海量文件的存储,一个机器不够,那么就用多台机器来存储。 如果一个文件只存储一份,那么如果存储这个文件的机器坏掉了,文件自然就丢失了,解决办法就是将文件进行备份,相信大多数人都有备份重要文件的习惯。FastDFS也是如此,为了防止单点的失败,肯定是需要冗余备份的。 FastDFS把应用服务器分为若干个组,每一组里面可以有多台机器(一般采用3台),每一台机器叫做存储服务器(storage server)。同一组内之间的数据是互为备份的,也就是说用户把文件传到任一服务器,都会在同组内其它两个服务器进行备份,因此一个组的存储空间大小是由该组内存储空间最小的那台机器是一样的(和木桶原理一样)。为了不造成存储空间的浪费,同一个组内的三台机器最好都一样。 每个存储服务器(storage server)的存储就够又是怎样的呢?展开来看,它可以分为多个目录,每个目录以M开头,用00、01、02......来划分,一般无需划分这么多目录,只用一个目录就可以了。 在每个根目录下面又划分了两级目录。如图所示,在/data/fastdfs0下又划分出两级目录,每一级有256个目录,这样算下来总共就有65535个目录了。存储文件时,就是通过两次哈希来确定放在哪一个目录下面。 那么问题就来了,有这么多组,到底该选择哪个组的服务器进行存储呢?或者说,访问的时候到底访问哪一个组呢? FastDFS提供的解决思路是引入一个跟踪服务器(tracker server),它用于记录每一个组内的存储服务器信息,存储信息是每个storage主动回报给tracker,有了这些信息之后,tracker就可以做调度工作了,看看谁的存储空间大,就把文件放过去。 FastDFS的特点 组与组之间是相互独立的 同一个组内的storage server之间需要相互备份 文件存放到一个storage之后,需要备份到别的服务器 tracker之间是不交互的 每个storgae server都需要向所有的tracker去主动报告信息 tracker与tracker之间是不知道彼此的存在的 如何上传文件 为方便下载文件的理解,这里假设上传的文件为:Group1/M00/00/0C/wKjGgVgbV2-ABdo-AAAAHw.jpg 如下面的时序图可以看到客户端是如何上传文件到服务器的。首先client向tracker发送上传链接请求,然后由tracker进行调度,查询可用的storage,并把该storgae对应的ip和端口发送给client;拿到了存储服务器信息,client就直接将文件上传到storage即可;storage会生成新的文件名再写入到磁盘,完成之后再把新的文件信息返回给client,client最后把文件信息保存到本地。需要注意的是,storage会定时向tracker回报信息。 如何进行选择服务器 tracker不止一个,客户端选择哪一个做上传文件? tracker之间是对等的,任选一个都可以 tracker如何选择group? round robin(轮询) load balance(选择最大剩余空间group上传) specify group(制定group上传) 如何选定storage? round robin,所有server轮询使用(默认) 根据ip地址进行排序选择第一个storage(ip地址最小者) 根据优先级进行排序(上传优先级由stoage来设置,参数为upload_priority) 如何选择storage path round robin,轮询(默认) load balance,选择使用剩余空间最大的存储路径 如何选择存放目录 选定存放目录? storage会生成一个file_id,采用Base64编码,字段包括:storage ip、文件创建时间、文件大小、文件CRC32校验和随机数 每个存储目录下面有两个256 * 256个子目录,storage会按文件file_id进行两次hash,然后将文件以file_id为文件名存储到子目录下 需要注意的是:file_id由cilent来保存,如果没有保存,你就不知道你上传的文件去那里了 Storage server之间的文件同步 同一组内的storage之间是对等的,文件上传、删除等操作可以在任意一台storage上进行 文件同步只在同组内的stroage之间进行,采用push方式,即源服务器同步给目标服务器 源头数据才需要同步,备份数据不需要再次同步,否则就构成环路了 新增一台storage时,由已有的一台storage将已有的所有数据(包括源头数据和备份数据)同步给该新增服务器 Storage的最后最早同步被同步时间 这个标题有一些拗口,现在有三台服务器A、B、C,每个服务器都需要记录其他两台服务器向自己进行同步操作的最后时间。比如下图中的服务器A,B在9:31向A同步了所有的文件、C在9:33向A同步了所有的文件,那么A服务器的最后最早被同步时间就是9:31。其他两个服务器也是一样。 最后最早被同步时间的意义在于判断一个文件是否存在于某个storage上。比如这里的A服务器最后最早被同步时间为9:31,那么如果一个文件的创建时间为9:30,就可以肯定这个文件在服务器A上肯定有。 Stroage会定期将每台机器的同步时间告诉给tracker,tracker在client需要下载一个文件时,要判断一个storage是否有该文件,只需要解析文件的创建时间,然后与该值作比较,若该值大于创建时间,说明storage存在这个文件,可以从该storage下载。 但是这个算法有缺陷,比如下面的情况:W文件的创建时间是9:33,服务器C已经把9:33之前的文件都同步给B了,因此B服务器里面其实已经有W文件了,但是根据最后最早被同步时间,会认为B中没有W文件。因此这个算法虽然简单,但是牺牲了部分文件。 如何下载文件 首先由client发送下载连接请求,请求的东西本质上就是Group1/M00/00/0C/wKjGgVgbV2-ABdo-AAAAHw.jpg;tracker将查询到的可用storage server(按下文的四个原则进行选择)的ip和端口发送给client;现在client有本地保存的文件信息,也有服务器的地址和端口,那么直接访问对应的服务器下载文件即可。 如何选择一个可供下载的storage server 共下面四个原则,从上到小条件越来越宽松 该文件上传到的源storage(文件直接上传到该服务器上) 文件创建时间戳 < storage被同步到的文件时间戳,这意味着当前文件已经被同步过来了 文件创建时间戳 = storage被同步到的文件时间戳,并且(当前时间-文件创建时间戳)> 一个文件同步完场需要的最大时间(5分钟) (当前时间 - 文件创建时间)> 文件同步延迟阀值,比如我们把阀值设置为1天,表示文件同步在一天内肯定可以完成 FastDFS的使用 用户通过浏览器或者手机端访问web服务器,web服务器把请求转发给应用服务器,应用服务器收到请求后,通过fastDFS API和FastDFS文件系统进行交互。但是这么设计会造成应用服务器的压力,因为上传和下载都经过应用服务器。 为了避免应用服务器压力过大,可以让客户端直接使用Http访问,不通过应用服务器。 FastDFS其他内容 防止盗链 为了防止辛辛苦苦上传的文件被别人盗去,可以通过给URL设置token来解决。FastDFS的防止盗链配置如下: # 是否做tokrn检查,缺省值为false http.anti\_steal.check\_token=true # 生成token的有效时长/秒 http.anti\_steal.token\_ttl=900 # 生成token的密钥,尽量设置长一些 http.anti\_steal.secret\_key=@#$%\*+\*&!~ FastDFS生成token策略为:token = md5(文件名,密钥,时间戳) 合并存储 海量小文件的缺点 元数据管理低效,磁盘文件系统中,目录项、索引节点(inode)和数据(data)保存在介质不同的位置上 数据存储分散 磁盘的大量随机访问降低效率(小文件有可能这个在这个磁道,那个在那个磁道,就会造成大量的随机访问,大量小文件对I/O是非常不友好的) FastDFS提供的合并存储功能 默认大文件64M 每个文件空间称为slot(256bytes = slot = 16MB) 也就是说对于小文件,FastDFS会采用把多个小文件合并为一个大文件的方式来存储,默认建一个大小为64M的大文件,然后再分成多个槽,最小的槽是256bytes,因此如果一个文件小于256bytes,那么它也会占256bytes的大小。就好像我们在医院见到的中药柜子一样,每个抽屉里面再分成多个小格子,根据药材包的大小来选择不同大小的格子。 没有合并时的文件ID 合并时的文件ID 此处不再深入探讨存储合并的机制,因为它带来了一系列新的问题,比如同步时不仅需要记录大文件的名称,还需要进入小文件的名称,一下子变得麻烦多了;原来空闲空间管理直接通过操作系统就能计算出来,但是现在不行了,因为是创建了一个64M的块,这个块里面还有空闲空间,计算起来就很麻烦了。 总结 FastDFS是穷人的解决方案 FastDFS把简洁和高效做到了极致,非常节约资源,中小型网站完全用得起
Read More ~