- 依赖追加而不是覆盖 。
- 检查点
- 写入时自验证 。
- 自标识的记录 。
实际中 , 我们所有的应用都是通过 append 而不是 overwriting 来修改文件 。在一个典型的例子中,一个写操作生成一个文件,并从头到尾的写入数据 。它将写入完数据的文件原子地重命名为一个不变的名字,或者周期性的创建检查点来记录有多少执行成功的写操作 。
检查点也含有应用层的校验和 。读操作仅验证并处理上一个检查点之后的文件区域,这个文件区域应该是已定义的 。即使一致性和并发性问题,这个方法很好地满足了我们的需求 。追加写操作比随机写操作更有效率 , 对应用的错误更有弹性 。检查点允许写操作逐步地进行重启,并防止读操作处理已成功写入但在应用视角上没有处理完毕的文件数据 。
在另一个典型应用中 , 许多写操作并行的追加一个文件,如进行结果的合并或者作为一个生产者-消费者队列 。记录追加方式的“至少一次追加”的特性保证了每个写操作都有输出 。
读操作使用下面的方法处理偶尔的填充数据和重复数据:
- 每个写操作写入的记录都包含了一些额外的信息,如校验和,以使这个记录能够被验证 。
- 一个读操作能够通过校验和识别并丢弃额外的填充数据和记录碎片 。
- 如果它不能忍受偶现的重复数据(如会导致非幂等的文体) , 它能通过记录中的唯一标识来进行过滤 。这些标识通常应该可以用于命名相关的应用实体 , 如网页文档 。
- 这些处理 I/O 的函数(除了删除重复数据)都在应用的共享库中,并且适用于 Google 其它的文件接口实现 。
3. 系统交互我们设计这个系统时,需要最小化所有操作与 Master 的交互 。依照这个前提,我们描述了客户端、Master 和块服务器之间如何进行交互 , 来实现数据的修改、原子记录追加,以及快照 。
3.1 租约(Lease)和修改操作顺序一个修改操作是一个改变了内容或块元数据的操作,如一个写操作或者一个追加操作 。一个修改操作将在一个块的所有副本上进行 。我们使用租约来保证一个修改在副本间的顺序一致性 。
- Master 将一个块租约授予所有副本中的一个,这个副本成为 Primary 。
- 此时 Master 会找到一个持有最新版本数据的块作为 Primary,并将这个 Primary 自增后的新版本号作为最新的版本号,除非在后面发现其他块中有更新的版本号,那它会认为他在寻找 Primary 时出错了 。正常情况下除非 Master 崩溃过,那么他一定知道最新的版本号是什么 。
- Primary 对一个块的所有修改操作选择一个串行的顺序,所有的副本在执行修改时都按照这个顺序 。
- 因此 , 全局的修改操作的顺序首先由 Master 选择的授予租约的顺序决定,然后由租约中Primary 分配的序列号决定 。
主节点有时会在租约到期之前尝试取消它(如,当 Master 想要禁止对一个被重命名的文件的修改操作时,就不再授予先前的 Primary 重命名操作的租约) 。即使 Master 与 Primary 失去通信,它也能在前一个租约到期后安全的将一个新租约授予另一个副本 。
下图是写操作的控制流和数据流:

文章插图
在图2中,我们通过跟踪一个写操作的控制流,描述了整个过程:
- 客户端询问 Master 哪个块服务器持有这个块的当前租约 , 以及这个块的其它副本位置 。如果没有任何一个块服务器拥有租约,则 Master 选择一个副本并授予一个租约(没有在图上显示) 。
- Master 回复客户端 Primary 的标识,以及其它(Secondary)副本的位置 。客户端缓存这些数据用于以后的修改操作 。只有当 Primary 不可达或者接收到 Primary 不再持有租约时才需要再一次请求主节点 。
- 客户端将数据推送到所有的副本 。一个客户端能够以任意顺序进行推送 。每个块服务器将数据保存在内部的 LRU 缓存中,直到数据被使用或者过期被替换掉 。
推荐阅读
- JAVA的File对象
- Codeforces 1670 E. Hemose on the Tree
- 二 沁恒CH32V003: Ubuntu20.04 MRS和Makefile开发环境配置
- 驱动开发:内核监控FileObject文件回调
- 齐博X1-栏目的调用2
- Blazor组件自做十一 : File System Access 文件系统访问 组件
- How to get the return value of the setTimeout inner function in js All In One
- System.IO.FileSystemWatcher的坑
- Docker | dockerfile构建centos镜像,以及CMD和ENTRYPOINT的区别
- prometheus监控实战