Skip to content

Introduction

1. 什么是分布式系统?(What is a Distributed System?)

广义上,从弱耦合的互联网到强耦合的多核处理器都属于分布式系统。

1.1 本课程定义

一组“独立计算机(independent computers)”,通过“网络(network)”通信与协调行为,对用户呈现为一个统一的“系统(single coherent system)”。

关键特征与动机

特征含义为什么重要
独立计算机每个节点独立运行,可独立失效必须设计成允许“部分失败”,不能像单机那样“一挂全挂”
网络连接无共享内存,通信靠消息延迟、丢包、乱序常见 → 分布式算法复杂性来源
消息传递使用 send/receive 协调行为所有同步机制都必须基于消息协议实现
呈现为一个系统对用户隐藏内部复杂性用户看到的应该是一个整体,而不是数百台机器的集合

2. 一个分布式系统应当能做到什么?

分布式系统核心目标:
透明(transparent)・开放(open)・可扩展(scalable)

注意:透明(transparency)并不是“看得见”,而是“对用户无感”。 这里的透明性的意思其实是用户使用资源的时候仿佛没有经过分布式系统一样,是无感性。 分布式系统使用中间层隐藏底层网络、位置、复制等细节,使用户感觉就像在使用一个单机系统。


2.1 可用(Availability)

系统必须允许用户或其他系统使用其资源,不因部分节点失败而中断。


2.2 资源抽象化(Resource Abstraction)

资源包括文件、打印机、数据库等。

为什么要抽象?

  • 将底层复杂性对用户隐藏,提供统一接口。
  • 抽象 = 用统一接口表示资源(类似 OOP 的 interface)
  • 提高可管理性、可移植性,并隐藏复杂性

2.3 隐藏具体结构(Transparency)

用户不需要知道:

  • 资源的位置
  • 资源的复制数量
  • 资源在内部的移动/迁移

2.4 动态扩展(Add/Remove Resources at Runtime)

分布式系统的主要使命之一就是扩容。

  • 动态添加节点(scale-out)
  • 故障节点的动态移除
  • 系统不停机维护

用户希望系统永远保持可用(always-on)。


2.5 可扩展性(Scalability)

系统必须能适应:

  • 用户数量增长
  • 数据量增长
  • 请求量增长
  • 地理分布扩展

与动态扩展不同:Scalability 的范围更广,关注整体成长性,而不仅是简单增减机器。

3. 分布式系统实例(Examples)

Internet

  • 透明性:使用 IP 形成逻辑网络,使用户无感知到位置差异
  • 开放性:资源可自由加入或退出网络
  • 扩展性:IPv4 提供约 40 亿地址(尽管理论上不够,但现实世界这玩意够用了几十年)

术语(Before We Continue)

在进入后面章节之前,需要定义一些基础名词。

  1. 主机(host)/节点(node)/机器(machine)
    分布式系统的基本计算单元。

    • client:使用资源
    • server:提供资源
  2. 站点(site)
    含有多个主机的物理位置(如数据中心、机房)。

  3. 资源(resource)
    主机上的程序或数据,可由系统共享或访问。

  4. 任务(task)
    一组目标明确的操作,由分布式系统执行。

4. 分布式系统的优缺点

分布式系统从一开始的内含的复杂性就远高于普通的中心化服务器,那为什么要构建一个分布式系统呢?

构建分布式系统的难点:
必须考虑 组件分工、位置分布、通信方式、协作模式、负载管理、容错

我们要看看常见的领域来分析优缺点:


4.1 资源分享(Resource Sharing)

节点可以使用其他节点的资源(如 A 用 B 的打印机)。

优点

  • 能获取原本没有的资源
  • 能共享闲置资源,提高系统利用率

缺点

  • 资源管理与冲突处理变复杂
  • 例如:A 与 C 同时想使用 B 的打印机;或 B 的打印机已宕机但 A 尚不知情

4.2 并发处理(Concurrency)

多个主机可同时处理多个任务。部分任务可拆分后并行加速。

优点

  • 提升吞吐量
  • 并行化降低任务完成时间

缺点

  • 容易出现负载不均(某节点过载)
  • 任务调度、通信开销可能抵消并行收益
  • 正确性问题(如并行写文件时的冲突)

4.3 全局时间(Global Time)

许多分布式任务需要共有的时间概念,比如在金融系统里的订单顺序,或者两个机器人决定同步提高速度,提升效率的时候需要同步时间。

问题

  • 物理限制使真正的全局一致时钟不存在
  • 现有的同步算法(NTP 等)都有一定程度上的误差。
  • 因此事件排序不能依赖物理时间 → 必须使用逻辑时间(Lamport Clock)来决定顺序。

4.4 可靠性(Reliability)

可靠性:在部分系统组件失效时仍能继续提供服务。

  • 对系统而言:节点挂了不应导致整体宕机
  • 对用户而言:某个资源不可用时应能找到替代资源

理论上,分布式系统因冗余设计更可靠;但现实中由于复杂性增加,并不总是更稳定。


4.5 拓展性(Scalability)

理论上:“遇事不决加机器”。
但实际扩容会带来许多问题。

扩容的现实问题

  • 新机器是否有需要的资源?
    如你需要打印机,但新增节点没有

  • 扩容对网络的影响?
    若系统缺乏规划,大量节点可能让网络压垮
    (例如最简单的通信方式就是每节点占用一个端口,但是当机器数大于端口数的时候就炸了)

  • 扩容会降低整体可靠性?
    节点越多 → 故障概率越高
    更多副本 → 更多同步成本 → 更多复杂性

5. 分布式系统挑战

5.1 异质性(Heterogeneity)

系统必须运行在多种硬件、操作系统、语言、网络环境中。
这是构建分布式系统最根本的困难之一。


5.2 开放性(Openness)

开放系统允许:

  • 功能扩展
  • 集成新的资源/服务
  • 使用公开接口(API/协议)
  • 使用统一通信方式

现实中由于商业、兼容性、安全性等因素,开放性常常只能部分实现。


5.3 安全性(Security)

安全性关注保护敏感数据:

  1. 通信加密(encryption)
  2. 身份认证(proof of identity)

外部攻击包括:

  • 中间人攻击
  • 数据篡改
  • 拒绝服务攻击(DoS)

5.4 崩溃处理(Failure Handling)

节点会失败,因此系统必须支持:

  • 错误检测(detect)
  • 错误隐藏(mask)
  • 容忍(tolerate)
  • 恢复(recover)
  • 冗余(redundancy)

容错能力是分布式系统可靠性的基础。


5.5 透明性(Transparency)

透明性:用户不需要知道系统是分布式的。

系统应实现:

  • 资源移动透明
  • 负载重新配置透明
  • 结构扩展透明

用户操作不应因内部变化而改变。

6. 模型(Models)

6.1 系统模型(System Models)

描述分布式系统面临的外部困难

  1. 使用模式多样(workload 多变)
  2. 系统环境多样(异构硬件/网络)
  3. 内部问题(时钟不同步、冲突更新、硬件故障)
  4. 外部威胁(DoS、攻击等)

System Model = 分布式系统的外部制约(external constraints)。


6.2 结构模型(Architectural Models)

系统设计蓝图,包括:

  1. 功能模型(各类型节点的职责)
  2. 组件如何分布在网络中(部署方式)
  3. 组件之间如何通信(交互方式)

结构模型决定系统的整体组织方式。


6.3 分布式系统层级

自底向上:

  • 平台(Platform):硬件 + 操作系统
  • 中间件(Middleware):提供统一接口,隐藏差异
  • 应用服务(Applications):实际的分布式程序

7. 中间件(Middleware)

中间件是一个介于应用与底层之间的软件层,为开发者提供统一抽象,避免直接处理网络、硬件、通信协议等复杂性。


7.1 中间件的功能

  1. Remote Method Invocation(RMI)
  2. Group communication
  3. Event Notification(发布/订阅)
  4. 共享数据的分区/放置/查询
  5. 数据复制与一致性维护
  6. 实时多媒体传输(视频、语音等)

例子:

  • Sun RPC
  • CORBA
  • Java RMI
  • Web Services(SOAP/REST)
  • Microsoft DCOM

7.2 中间件的缺陷

  1. 中间件通常假设通信是原子的(atomic)
    现实中通信常包含:部分成功、部分失败、中断、断点恢复

  2. 需要应用语义的功能无法靠中间件实现
    如 FTP 断点续传必须在应用层记录传输偏移量

  3. 端到端原则(End-to-End Argument)
    某些正确性检查只能在应用端点实现,而非中间件或网络层
    → 可能导致部分功能重复,但这是必要的设计


基本架构

设计需求

性能

对于性能来说有三个影响要素:

  1. 响应性
  2. 吞吐量
  3. 负载平衡 用户与系统交互的时候期望快速而连续的响应(用户界面的快速更新)。但是服务器经常会访问一些共享的资源,因此服务器当前的负载网络延迟中间层消耗和代码运行的时间都会影响响应性。通过
  • 减少应用的软件层级
  • 减少客户端和服务器之间传输数据的量。
    来提升性能。
    吞吐量基本就是服务器处理任务的能力。跟响应性的概念差不多,但是会受到客户端和服务器两端的影响。在应用层面,软件各个层级的瓶颈也会影响整体表现。 负载均衡的意思就是使用所有可用的计算资源,在不争抢相同资源的情况下运行多任务。比如JS用客户端分散运行,或者部署多个服务器然后分散负载。

软件层级是指构成一个分布式系统的层级,举个例子: 前端->web服务器->中间层->数据库。 要是根据你的应用逻辑,你的数据处理服务器(后端)还有不同步骤的话,软件也就分了层。

QoS(服务质量)

基本由四要素构成:

  1. 可靠性
  2. 安全性
  3. 性能
  4. 适应性(新增) 适应性指的是能适应资源的变化,系统设置的调整,基本上就是弹性的意思。 影响性能的还有时间敏感性:数据在进程之间的传递必须在固定时间内完成。多见于视频网站或者游戏的需求。

核心模型

客户端-服务器模型(client-server)

客户端通过独立的进程向服务器请求由服务器管理的资源 服务器在请求资源的时候也是一个客户端。

p2p模型(peer-to-peer)

节点之间互相连接,互相分享资源。节点之间没有server和client这种级别的差距

变种模型

多服务器模型

一个服务根据逻辑拆分到多个服务器上,有多个服务器整体提供一个服务。

数据库+数据处理服务器+前端界面服务器

中间服务器(代理+缓存)

中间服务器可以用来应对上面的性能和QoS需求。 缓存服务首先检查有无可用copy,无的话则向后请求最新,然后存下。它可以显著减少客户端的响应时间(响应性) 缓存服务可以是本地的,也可以是在一个共享的代理服务器上。 代理服务器是一个架设在clent和server之间的中间层,作转发,记录和缓存

移动代码(mobile code)

对于某些不依赖于平台的代码来说,这些代码可以先发送到主机上,然后再运行。 优点是方便,而且可以用客户端自己的性能不占服务器.
缺点是安全性问题,代码发送到本地意味着很容易被操作。

网络电脑/瘦客户端

跟传统主机不同,网络电脑本地就基本界面都没有,操作系统和应用本体都通过网络下载。主机只拥有最小程度的存储,且大部分储存都是缓存,用来加速访问。应用是本地跑的,但是文件什么的都在远程。优点是成本低,好维护并且移动用户的成本低。
瘦客户端跟网络电脑的区别是连应用都是远程跑的。 缺点是断网或者网络波动都有可能造成数据的丢失,如果图像比较复杂可能会影响性能。(X11居然是按client-server模式的瘦客户端设计的)

缓存可以减少响应时间,复制可以减少网络开销,平衡负载。

分布式并发问题

一个中心化系统有单独的内存和时钟,因此没有并发问题,但是在分布式系统中确定事件发生时间就很麻烦。因此我们只要使用一个普适的时钟就好了!

物理时钟

如今的“准确”时间是通过天文测定的,也就是所谓的太阳日(solar day),但是地球是在慢慢减速的,地核运动,地球到太阳的远近也会导致波动误差。通过平均太阳日可以修正一点误差得到当今的24小时。
当今的精确时间使用的是铯原子钟,精度极高。TAI(国际原子时)直接由原子时钟计算,UTC(协调世界时)是TAI加入闰秒。通过广播或者卫星可以获得UTC的时间。
对于每个电脑来说,它们主板上都有一个独立供电的内置硬件时钟,用于初始化操作系统的时间。但是实际上不能保证两个机器的时间变化速度是完全一致的,因为晶振频率是是有偏差的导致了时钟偏移(clock skew)。
制造商可以给出每秒的最大偏移率ρ(每秒最多快/慢多少ticks),想要保证两机子误差不超过δ,就得每δ/2ρ秒使用时钟同步算法同步一次。

时钟同步算法

中心化算法(由一个主机负责系统的全局时间)

Cristian’s 算法:
一个主机负责授时,其它客户端每δ/2ρ秒请求CUTC并同步一次时间。

  • 小问题: 延迟
    解决方法: 客户端记录自己收发时间为T1, T0, 然后(T1T0)/2作为延迟的估计,加在CUTC就行

延迟时间是客户端记录的,跟它的时钟有关,不过只要时钟漂移不是很大就可以忽略。

  • 大问题: 客户端的时间比授时主机快
    客户端的时间变慢,主动降低每秒tick数等主机追上来。(而不是直接倒流时间)

Berkeley 算法:
这是一个局域网内同步时间的算法。一个电脑为master,其它电脑为slave。主机定期获取所有slave的时间,计算平均值作为系统(自己)的时间。然后告诉给每台机器各自该怎么调整时间。核心思想是多个client时间的平均值会比单个client的时间要精确。

分布式算法

平均算法/NTP: 所有机器定期播报自己的时间,机器的时间设定为受到的时间的平均值。

逻辑时钟

这是另一种处理分布式同步问题的思路,也就是使用一个所谓的相对时间(relative time). 这个相对时间处理的不再是精确的时间,而是事件的顺序:

  • 时间发生顺序
  • 因果顺序(内含了发生顺序)

Lamport 逻辑时钟

通过观察,我们可以发现两个基本原理:

  1. 如果两个进程没有交互的关系,那么它们的时钟不需要同步,因为不影响他们的执行。
  2. 对于进程来说,有没有一个共享的时间概念不重要,重要的是能不能确定一个两边都认可的事件顺序。

因此为了表示进程之间的时间关系,Lamport 引入了"“happens-before”关系(用->表示) happens-before关系的特性:

1. 如果在同一个进程内A早于B,那么A->B
2. 如果A向B发送消息,B收到消息,那么A->B
3. 这个关系有传递性,A->B, B->C, 那么A->C
4. 如果A和B之间没有->的关系,那么他们同时(concurrent)发生

那么现在根据这个定义就有一个问题了: 如果A->B,但是A的本地时间CA > CB,那么就会出现问题,Lamport的解决方法是B本地的时间改为CB:=max(CB, CA)+1 这样可以保证时间一直往大走。
局限性: 如果 A->B, 可以推出 CA > CB,但是CA < CB不能推出A和B之间的因果关系。例子:

P0: A(8) ------>
P1: B(9) ------>
两线无交叉,单纯就是A时间戳比B小

向量时间(Vector Time)

为了解决这个因果问题,引入了一个多维向量(其实就是一个进程数长度的数组Array[N]),这个向量储存了主机视角下全局发生的事件(快照)。 对于 N 个进程,每个进程维护一个长度为 N 的向量 V:

  • V[i] 表示“我所知道的,进程 i 已经发生了多少事件”

向量时间算法步骤:

  1. 所有进程初始化为0
  2. 每次发生事件,进程 i 的向量 V[i] + 1
  3. 发送消息的时候,把向量 V 发给接收方
  4. 接收方收到消息,首先把自己的V[self]+1 然后对每个分量 i:V[i] = max(V[i], V_msg[i])。

这样为什么解决了因果关系呢?因为如果两个event,A->B,那么A的V就发给过B,B的所有的V[i] >= A的V[i](B的已知历史由A前进了一些)如果没有因果关系,那么就会存在有某一个维度的V[i] < A的V[i](这个地方挺好想的但是我不太会解释,按照视角这个思路想一下就行,略)。

全局状态

在很多时候,我们不止需要某一个process的状态,而是要储存一个全局的状态。
最简单的做法是给每一个进程都发信息,收到信息的进程把自己的状态写到硬盘里。(慢而且不一定连续)
Cut:时间的前沿,在cut之前的所有时间在这个cut里,之后发生的在cut外。
一个连续的cut指的是cut内的所有事件:如果A->B, B 在cut内那么A在cut内。
选取到一个连续的cut作为全局状态就是我们的目的。

快照算法

这个算法要求很多前置条件:

  • 所有的通道和进程都不会失效,每一个发出的信息都是会被收到的。
  • 通道双向且遵循FIFO原则。
  • 强连通图
  • 任何一个节点在任何时间都可能开始全局快照
  • 在收发快照信息的时候,其它信息传递不会停下。

有一个博客的这个算法讲的很好,我建议看她的博客.
算法步骤: 每个节点维护一组入站通道状态,比如节点1有Array[C(2,1),C(3,1),C(4,1)....] 这个算法的核心就是两条规则 Marker发送规则

1.记录自己的状态
2.对于自己的每一个出站通道,发送一个Marker

Marker接收规则

第一次通过通道C接收到Marker:
记录自己的state
通道C设置为empty
向所有的出站通道发送Marker
开始记录其它所有入站通道的信息
其它:
C的进展通道记录保存为之前记录的信息

最终每个节点都会有自己的状态和一个通道里收到的信息的状态。所有节点的状态加在一起就是全局的快照。

资源互斥

对于一个分布式系统来说,互斥是保护共享资源的一种手段。非分布式系统中可以使用类似信号量(Semaphores)之类的技术实施临界区(Critical region)来实现互斥。在分布式系统中也采用了类似的技术。

中心化算法

使用一个coordinator管理所有的资源,如果想进临界区就发请求给coordinator,可用回OK,不可用等着。用完资源通知coordinator。 优点:

  • 简单
  • 能用
  • 公平
  • 不会饿死 缺点:
  • 单点故障,容易有瓶颈。

分布式算法(Ricart-Agrawala)

申请进入临界区就广播[申请者ID,资源ID,时间戳],所有进程回OK就进。如果有进程在临界区就会回复NO,如果有进程在等着这个资源就根据时间戳(时间戳小获胜)来决定发NO还是YES。 优点:

  • 不会单点故障
  • 能用
    缺点:
  • 变多点故障了,任意故障都会造成阻塞
  • 消息多
  • 所有节点要维护所有节点列表 ppt老师总结是烂完了。

Token-Ring算法

所有进程逻辑上组成一个环,持有token的进程可以进临界区,用完token给下一个 优点:简单,公平,不会饿死 缺点:

  • token丢了
  • 进程挂了
  • 环不好维护
  • 想再次进入临界区得等一圈

总结

这仨都各种问题,只有在崩溃少的系统里勉强能用。

选举算法

分布式系统中有些情景需要选出一个特殊职能的节点(比如leader什么的),这时候我们就需要一个算法来进行这个步骤。
在开始介绍算法之前,我们应该先确定这个选举算法的一些性质:

  1. 所有的节点都应该能参与
  2. 选出的那一个结果所有节点都应该同意。
  3. leader在被选出之时就开始形式leader的职能直到fail或者行使了什么退休机制。

选举算法的效率是通过计算算法占用的带宽和时间来确定的。

环形算法

得名环形算法的原因是因为,所有的process被视作在一个逻辑环里。

  • 所有的进程从左进程收到消息,并将消息向右传。
  • 所有进程在本地持有一个活跃进程表

算法流程:(ppt)

某个进程检测到leader挂掉之后开始选举流程:

  1. 构建一个election消息,放入自己ID
  2. 每经过一个进程就加入自己的ID(进程失效就往下跳,直到找到一个活跃的)
  3. 发起者发现自己的ID在election消息里就说明走完了
  4. 将ID最大者定为leader,发布一条coordinator消息
  5. coordinator消息环形走一圈通知所有人。

算法流程:(经典版)

某个进程检测到leader挂掉之后开始选举流程:

  1. 构建一个election消息,放入自己ID
  2. 每经过一个进程就比较里面ID的大小,如果比自己小就替换,大就继续传,一样就就说明自己就是那个最大的
  3. 将自己(最大者)定为leader,发布一条coordinator消息
  4. coordinator消息环形走一圈通知所有人。

算法分析

我认为老师的算法复杂度分析这里有问题,按照老师的ppt的版本这里好坏都是2N,但是ppt里给出的是3N-1,我就解释一下: 经典版的最坏情况是最大的节点在最后一个节点,也就是发起节点的左节点,那么一共就要N-1个election消息才能到最大节点,然后这个最大节点需要消息再从所有的节点过一遍(N)才能第二次收到这个election消息并发现自己是最大节点,之后要发一个coordinator消息环形走一遍通知所有人(N),也就是3N-1

霸凌算法(bully algorithm)

顾名思义,这个算法的核心就是ID大的可以欺负ID小的。

假设

  • 所有的进程互相都知道ID和地址
  • 通信可靠

算法流程

  1. 节点P 发现原 leader 挂了(或刚恢复):
  • 如果自己 ID 最大 → 直接发 COORDINATOR 给所有人,当 leader。
  • 否则:给 所有 ID 比自己大的进程发 ELECTION。 
  1. 如果没有收到任何 ANSWER:
  • 说明所有比自己 ID 大的都挂了 → 自己发 COORDINATOR,当 leader。 
  1. 如果收到某个更大 ID 的 ANSWER:
  • 自己就老老实实等,不再发选举消息,等别人发 COORDINATOR。 
  1. 如果 P 收到一个来自 ID 更小的进程的 ELECTION:
  • 回一个 ANSWER(告诉他“我比你大,我活着”)
  • 然后自己也按第 1 步发起一轮新的选举(“抢权”) 
  1. 收到 COORDINATOR 消息 → 认这个 sender 为新 leader,更新本地记录。 

算法分析

最坏带宽:约 N^2 - 1 条消息。 

  • 最坏情况:最小 ID 的进程发现故障,它会给所有更大的发 ELECTION,然后一串人层层再发选举,消息N^2 - 1。
  • 最坏 turnaround:大约 N−1 轮“波次”。  最好带宽:N-2 条消息。
  • 最好情况:第二大 ID 发现 leader 挂了,直接发 COORDINATOR: 带宽 ~ N−2
  • turnaround time只要 1(所有并行发)。

分布式文件系统

分布式系统的核心目标之一就是实现资源的共享。文件可以通过“数据库”,“共享内存”等方式存储,但同时也需要一个更高层的文件系统来管理。

分布式文件系统功能

文件系统一般负责文件的组织(organization),存储(storage),检索(retrieval),命名(naming),共享(sharing)和保护(protection)。
模块划分(逻辑上):

  • 文件夹模块(名字 → 文件 ID)
  • 文件模块(文件 ID → 实际文件)
  • 访问控制模块(权限)
  • 文件访问模块(读写)
  • 磁盘/设备模块(磁盘块、I/O)
    一个分布式系统的组件:
  1. 文件服务器(File Servers): 提供储存,处理客户端请求
  2. 客户端(Clients): 发送请求,给用户提供统一接口
  3. 文件服务接口(File Services) 定义基本操作(增删改查)

DFS的例子有 Novell 网络组件和Windows文件共享。

DFS的关键问题

在这个文件系统中,主要要处理的就是6个关键问题

1. 命名(Naming)和透明性(transpaency)

在实际的硬件存储中,文件是由文件ID和物理地址来表示的,命名其实是由名称再加上逻辑上的分组构成的。听起来有点抽象,但是在正常的目录文件系统中指的就是文件名和他的地址,比如/home/user/file.txt。这种命名的手段让我们可以把逻辑上操作的文件和实际物理上的那个文件分开考虑。这为DFS提供了位置透明性位置独立性。 位置透明性: 文件的物理储存位置不显示在文件名上。(有助于文件分享)
位置无关性:在文件的物理存储位置改变时,我们不需要去动文件名。(更好的文件抽象,将逻辑上的文件和硬件层分开)
访问透明(access transparency): 一个小问题是当今的文件系统大部分还都是单机系统,因此在我们想要透明的(transparently)访问文件时,我们要先连接到资源,这个行为叫做挂载(mounting)。当你挂载之后,你就可以像是操控本地文件一样管理远程文件了,因为它们使用了一个接口。

全局命名:我怎么能知道/home/john/thesis.tex和/mnt/john/thesis.tex是同一个文件呢?使用全局命名。 这样所有的机器看到的都是同一棵目录树。(理想情况下DFS下组合出的文件系统应该跟本地系统相同,但是实际上因为有设备树和某些机器特有文件的原因不能实现)

2. 远程文件访问

下载/上传模型

IDEA: 把需要访问的文件下载到本地,然后访问,用完后上传回去。
因此这个模型只提供了两种基本的文件操作:读和写。读操作把文件从服务器传输给客户端,写操作把文件从客户端上传到服务器。 优点:简单 缺点:客户端首先要有足够存储空间,而且大多时候都不必要传输整改文件的。

远程访问模型

IDEA:远程控制文件系统,文件系统操作服务器上的文件。
因此这个文件支持所有的文件操作。
优点:客户端不需要很大空间,在只需要一小部分文件碎片的时候不拉取整个文件。 缺点:会多次访问同一个文件(缓存能有所帮助)↓ 将最近访问的磁盘块加入缓存,这样重复访问可以本地处理了。

缓存

缓存位置:硬盘VS内存 硬盘?:可靠,持久,重启还在。 慢 内存?:快,无磁盘主机可用,内存越大越快。服务器端端数据总是在内存里,如果客户端也放内存就可以共用逻辑了。 不可靠

缓存更新逻辑:立刻写入VS延迟写/关闭写入 立即?:可靠,速度慢 延迟?:写入快,效率高,统一写入减少了无效操作(写了又删)。可靠性不好,本地崩溃时没写入的东西会丢。

缓存一致性问题:客户端VS服务器端 客户端?: 客户端检查本地缓存是不是最新的,不是就从服务器端申请。服务端再检查客户端的文件是不是跟最新一致。 服务器端?: 服务器记录每一个客户端缓存的文件。当服务器端发现有缓存文件不一致时就做出反应(比如推送最新文件)

优点?:

  • 加速远程文件的访问,让它们的访问体验跟本地类似。
  • 缓存在读多写少的应用中表现优越。(写多时在解决一致性上的开销会变大)
  • 减少整体网络开销。(传完整大文件>零碎小文件)
  • 本地有硬盘或大内存时缓存有效。(无盘和小内存主机不应该使用缓存)

3. 有状态(stateful)VS无状态(stateless)

有状态文件系统(类似TCP)

服务器与客户端保持一个连接,服务端记住客户端的操作和当前状态,然后更新和保留这个状态直到声明这个操作结束。

优点: 高性能。有状态所以服务端知道操作类型,如果是顺序读写可以预读(read adead)

无状态文件系统(类似UDP)

每一次请求服务器都当是一个自包含(self-contained)的,请求要指出要访问的文件名和文件内数据的位置。 无状态请求可以不用维护和终止连接,每次发的都是完整的一个操作而已。

Versus!

  • 崩溃恢复: 有状态每次崩溃会丢掉整个操作,崩溃恢复很复杂。,无状态的崩溃回复几乎不会被察觉,重新请求一次操作就行。 无状态很慢,一个稳定的无状态服务代表着更长的请求信息和更慢的请求处理。
  • 有些情况要求有状态,比如之前的缓存由服务端管理就需要服务端保有每个客户端的信息。

3. 文件复制

一个文件复制多份放在不同的主机上,命名系统记住文件的每一个副本,但是向用户隐藏。在逻辑上只存在一个文件。

WHY?

复制提升性能。用户访问文件的时候可以访问据他最近的服务器上的文件副本。提高可用性,一个服务器崩了不会丢失文件。 复制又带来了一堆问题。

问题(这玩意目前老师好像也没给解决办法,就纯摆着)

更新问题: 一个文件的更新要同步到所有文件上(慢,而且带宽开销大) 需求复制: 访问非本地文件导致本地又存储了一个缓存,这个缓存也就是那个文件的又一个副本,我们怎么处理这个副本? 并发问题: 两个副本被同时更新,存那个?

4. 安全

安全性是DFS的关键,它代表着访问控制和用户授权。 在DFS中,每一个操作之前都需要验证权限。这里有两种方式

名称解析(类似cookie的token)

服务器验证名字,如果符合权限就返回一个capability凭证,包含有文件id,允许操作,加密签名。客户端凭借这个凭证可以操作文件。在每一次RPC(remote procedure call远程操作请求)时会检查这个凭证。

RPC检查

对于每一个PRC都放上客户端自己的加密签名,然后每次服务端都验证。

平面文件系统(Flat FIlesystem)

我们现在来考虑一下怎么设计一个DFS?

在平面文件系统中不存在目录结构,所有的文件都是通过一个独特的ID来标识的。这样实现了名称和文件位置的脱钩。 现在服务器的文件系统由两个服务构成:目录服务和平面文件服务,目录文件负责将目录加文件名映射成UFID(Naming操作),平面文件服务通过UFID获取文件。客户端的文件模块负责提供接口来调用远程服务,隐藏服务端的实现细节。

文件组(File Group)是一组位于同一个服务器文件的集合,是打了包的一批文件,方便管理 平面文件系统是一个模型。而SUN NFS是一个这个模型现实中的实现。

SUN NFS

NFS是Sun公司开发的一个网络文件系统,它基于TCP/IP协议,是Sun公司提供的一个开源文件系统。 NFS引入了UNIX下的虚拟文件系统,它可以接入UNIX作为客户端。
懒得弄了,剩下的都是工程性的,弄了个大概的表,想看细的就看PPT吧。

项目FFS(Flat File System)Sun NFS
目标(Objective)参考架构简单且可靠
安全性(Security)目录服务 / RPC基于 Sun RPC
加密Kerberos 集成
命名(Naming)位置透明性位置透明性
文件访问(File Access)远程访问模型 + 缓存远程访问模型 + 缓存
缓存更新策略(Cache-Update Policy)写直达(Write-Through)周期性刷新(磁盘同步)
不适用(N/A)写直达 / 提交时写回
一致性(Consistency)不适用(N/A)基于时间戳
服务类型(Service Type)无状态(Stateless)无状态(Stateless)
复制(Replication)不适用(N/A)不适用(N/A)