C++ / a working model

154 / 163   ·   C11   ·   约 8 分钟

安德鲁文件系统(AFS)

先记住这句话

本章探讨AFS如何借助客户端本地磁盘上的全文件缓存以及服务器主动回调,显著降低服务器负担并实现高可扩展性,同时对比其与NFS轮询模型的差异以及两个版本协议的演进。

本篇内容
  1. 可扩展分布式文件系统的核心目标
  2. 全文件缓存如何减少网络交互
  3. 第一版协议暴露的扩展性瓶颈
  4. 回调与文件标识符带来的协议革新
  5. 运行示例
  6. 动手练习

官方章节 PDF

可扩展分布式文件系统的核心目标

AFS于1980年代在卡内基梅隆大学诞生,其首要追求是让一台服务器能同时服务尽可能多的客户端。设计者把用户可理解的缓存一致性放在首位,而不是像某些系统那样把一致性细节隐藏在超时参数后面。

全文件缓存如何减少网络交互

打开文件时客户端一次把完整内容拉到本地磁盘,之后所有读写都在本地文件系统完成,完全不产生网络流量;只有关闭且内容被改过才会把整文件写回。这种策略与按块缓存在内存中的做法形成鲜明对照。

第一版协议暴露的扩展性瓶颈

早期协议把完整路径名交给服务器去解析,导致CPU大量时间花在目录遍历上;同时客户端频繁发出有效性探测,服务器大部分时间都在回答“没变”。结果一台服务器大约只能撑二十个客户端。

回调与文件标识符带来的协议革新

第二版让服务器向客户端做出“文件若改我会通知你”的承诺,从而彻底取消定期探测。同时用体积标识、文件标识和唯一符组成的FID取代路径名,客户端自己逐步缓存目录项,服务器路径解析负担大幅下降。

常见误区

  • 把AFS的本地磁盘全文件缓存误当成NFS那种内存块缓存
  • 忽视回调要求服务器为每个缓存副本维护状态所带来的内存开销
  • 低估客户端自己完成路径遍历对减轻服务器CPU的关键作用

运行一个例子

最低标准 C11 · 完整程序 · 下载 .c

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

typedef struct {
    char name[64];
    char data[128];
    int ver;
} AFSFile;

AFSFile server_files[2];
int num_files = 2;

typedef struct {
    char cached_name[64];
    char cached_data[128];
    int cached_ver;
    bool has_callback;
    bool is_dirty;
} VenusCache;

void init_server(void) {
    strcpy(server_files[0].name, "notes.txt");
    strcpy(server_files[0].data, "Hello AFS world");
    server_files[0].ver = 1;
    strcpy(server_files[1].name, "data.bin");
    strcpy(server_files[1].data, "binary stuff");
    server_files[1].ver = 1;
}

void fetch_file(VenusCache *cache, const char *fname) {
    for (int i = 0; i < num_files; i++) {
        if (strcmp(server_files[i].name, fname) == 0) {
            strcpy(cache->cached_name, fname);
            strcpy(cache->cached_data, server_files[i].data);
            cache->cached_ver = server_files[i].ver;
            cache->has_callback = true;
            cache->is_dirty = false;
            printf("FETCH: whole file %s v%d cached locally\n", fname, cache->cached_ver);
            return;
        }
    }
}

void store_file(VenusCache *cache) {
    if (!cache->is_dirty) return;
    for (int i = 0; i < num_files; i++) {
        if (strcmp(server_files[i].name, cache->cached_name) == 0) {
            strcpy(server_files[i].data, cache->cached_data);
            server_files[i].ver++;
            cache->cached_ver = server_files[i].ver;
            cache->is_dirty = false;
            printf("STORE: file %s updated to v%d on server\n", cache->cached_name, server_files[i].ver);
            printf("CALLBACK: notified other clients of change\n");
            return;
        }
    }
}

void local_write(VenusCache *cache, const char *newdata) {
    strcpy(cache->cached_data, newdata);
    cache->is_dirty = true;
    printf("LOCAL WRITE: modified cache of %s\n", cache->cached_name);
}

int main(void) {
    init_server();
    VenusCache client1 = {0};
    VenusCache client2 = {0};
    printf("AFS Simulation Start\n");
    fetch_file(&client1, "notes.txt");
    local_write(&client1, "Updated notes by client1");
    store_file(&client1);
    fetch_file(&client2, "notes.txt");
    printf("Client2 reads: %s\n", client2.cached_data);
    local_write(&client1, "Second update");
    store_file(&client1);
    printf("After callback, client2 cache invalid, would refetch\n");
    fetch_file(&client2, "notes.txt");
    printf("Client2 now has: %s\n", client2.cached_data);
    printf("AFS Simulation End\n");
    return 0;
}

在本地编译

gcc -std=c11 -Wall -Wextra -Wpedantic -Werror ostep-50-afs.c -o example && ./example

预期结果

AFS Simulation Start
FETCH: whole file notes.txt v1 cached locally
LOCAL WRITE: modified cache of notes.txt
STORE: file notes.txt updated to v2 on server
CALLBACK: notified other clients of change
FETCH: whole file notes.txt v2 cached locally
Client2 reads: Updated notes by client1
LOCAL WRITE: modified cache of notes.txt
STORE: file notes.txt updated to v3 on server
CALLBACK: notified other clients of change
After callback, client2 cache invalid, would refetch
FETCH: whole file notes.txt v3 cached locally
Client2 now has: Second update
AFS Simulation End

CHECK YOUR UNDERSTANDING

合上答案,试着解释。

回调机制为何比客户端定期探测更能提升AFS的可扩展性?

查看参考答案

探测无论文件是否真正改变都会消耗服务器CPU和带宽;回调仅在实际修改发生时才发送一条通知,把通信从持续轮询变成偶发推送,因此同一台服务器能服务数量级更多的客户端。

继续查证

标准草案与官方章节会更新;版本标记只说明示例最低要求。

回到目录