Intro
随着单机性能进入瓶颈,storage与serve的压力与日俱增,因此,这两个职责被分布在不同服务器上。由于原本单机的文件访问变为跨服务器,因此NFS(Network File System)诞生了。
大存储服务器负责文件系统,应用服务器负责响应客户端
但是,如果我不想进行原本代码的修改,而想让通过网络进行的文件访问看起来如同之前本地的访问一样呢?我们现在一般使用RPC(Remote Procedure Call)在原有的单机文件系统上进行一层封装,使之成为NFS.程序员所面对的编程接口依然和往常的接口相同,而变化的仅仅是底层实现。
RPC
允许进程在远端执行而无需编码交互细节
我们使用Stub中间件隐藏通信的交互细节,真正的RPC通过Stub进行,而用户代码毫无察觉。
Stub隐藏了通信的细节,使得上层的调用无需修改
Client stub
- request中放置参数
- send requset to server
- 等待response
Service stub
- 等待message
- 获取request参数
- 进程调用
- response中放置结果与状态(success?/accecpted?)
- send respones to client
问题在于,message中应该放置什么,以下是一些比较重要的信息
当前的call的标识-Transaction ID
调用什么方法–Service ID (e.g., function ID)
身份认证-Auth Stuff
使用什么参数–Service parameter (e.g., function parameter)
由于引用失效,参数的再编排–Using marshal / unmarshal
//跨地址空间引用失效,因此需要进行序列化/反序列化(及处理网络通信的大小端)
Components
为了搭建一个RPC框架,我们需要
1.RPC格式标准(UDP or TCP or HTTP2?)
2.marshal / unmarshal工具库
3.Stub Generator:产生Stub
Client:marshal arguments, call, wait, unmarshal reply
Server:unmarshal arguments, call real function, marshal reply
4.Framework:
Client:
正确分发message到对应的server stub
跟踪所有发出去的请求
将收到的响应匹配到对应的call
多个caller共用一个socket
请求超时、重传的处理
Server:
对每个thread/callback正确分发reply(每个请求分配一个线程,或者请求多时维护线程池)
5.Binding:Client如何找到对应的Server
6.网络传输(如socket)
网络通信导致的Trade-off
1.性能开销(但不是传文本可以不用HTTP,会快些)
2.超时造成的额外问题
一旦发生超时,有这么几种解决方案,一般RPC使用第一或者第二
At Least Once:
重复resend,直到收到响应(但是可能会收到一堆响应)->要求调用无副作用
At Most Once:
重复resend, Server只保留一个request而忽略重复进行处理 -> 要求幂等性,多次调用如一
Exactly Once:
难以实现(没学)
3.错误隔离(C/S崩溃不影响彼此)
NFS
eg: mount –t nfs 10.131.250.6:/nfs/dir /mnt/nfs/dir
在应用程序调用文件系统接口时,NFS的所有调用如下。
NFS顺序图
值得注意的几点
fd<-->fh (file handler)
和下面Server无状态有关,而fd在Client内存中。因此传递对Server有用的fh,内含:
- file system ID
- inode number(path name可能会被rename)
- generation number-维护一致性(如果inode被删除后,又被复用number)
NFS Server并没有open/close
Server stateless,状态由Client维护(即使Server崩溃重启,由于无状态,依然能处理request)
回传file attributes
维护Client上的metadata
Cache
Server Reply Cache:
由于网络传输延迟或丢包,Client可能重复request,因此根据Transaction ID建立Cache。这样重复的request可以返回相同的响应。
Client Cache:
存储一些最近使用的vnode(virtual node),attributes,blocks。减少RPC延迟。
Cache coherence :
Read/write:
与单机不同,无法保证获取最新数据,自行负责解决
Close-to-open:
open时获取modified time,与cache中进行比较,更新到最新数据。
close时写回(类似于cache被淘汰时写回内存)
左图中:C2open时能获取最新的数据
右图中:C2open时,由于C1未close,因此open时没有更新,因此read脏数据。
由于上述情况,一般需要另外进行并发的处理,例如对文件加锁。
Vnode
虚拟化
把文件属于local还是remote抽象化,左侧的箭头可以指向NFS client,也可能指向Local file system,从而让程序员忽视了实现细节。
GFS(Google FS)
随着规模的增大,单文件服务器也无法承受了。为了scalable,GFS使用一个服务器作为转发,多个文件服务器进行数据传输。这里的核心架构在于,将控制流和数据流解耦。
Flat Namespace
尽管namespace看起来有树形结构,实际上并没有directory,而是把整个路径映射到chunk上(因为目录访问需要多次访问chunk,而网络传输的开销远高于硬盘)
GFS Cluster
Single Master
内存中维护metadata(没有inode,没有symlink,没有hard link)
使用前缀编码进行压缩(每个entry小于64bytes)
- namespace
- 访问控制
- 映射表
- chunk的位置
Multiple Chunkserver
存储数据(chunk64mb)
传送heartbeat信息(如果崩溃了,需要让master保持同步)
Cache
Client和Chunkserver没有数据的cache(因为chunk的容量很大),但是Client有chunkserver的cache,下次访问可以不通过master。
(由master进行lease,进行临时的权限移交)
Fault Tolerance
Chunkserver
在这里使用三备份,当三个备份都写入完成后再进行response。三个buffer一般不会同时崩溃,所以写入buffer后就可以视为写入完成。
对于每个chunk产生32bit的checksum
定期扫描罕用文件,检查一致性
Master
- 对于所有metadata修改log
- shadow master
- 状态在多机器冗余存储
Typical Workload
搜索引擎特点
大的流读取 小的随机读取(因此适合这种大chunk)
大量顺序写操作(append),先前爬取的数据较少修改
多client同时append一个文件(因此没有什么对已有内容的修改,写方面不追求效率)