154 / 163 · C11 · 约 8 分钟
安德鲁文件系统(AFS)
先记住这句话
本章探讨AFS如何借助客户端本地磁盘上的全文件缓存以及服务器主动回调,显著降低服务器负担并实现高可扩展性,同时对比其与NFS轮询模型的差异以及两个版本协议的演进。
可扩展分布式文件系统的核心目标
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和带宽;回调仅在实际修改发生时才发送一条通知,把通信从持续轮询变成偶发推送,因此同一台服务器能服务数量级更多的客户端。
继续查证
标准草案与官方章节会更新;版本标记只说明示例最低要求。