分布式文件系统代码详解

全局设置

Dataserver

定义 proto 远程调用函数

service DataServer
{
    // 创建文件,对应touch命令
    rpc CreateFile(CreateFileRequest) returns (BaseResponse) {}
    // 创建文件夹,对应mkdir命令
    rpc CreateDirectory(CreateDirectoryRequest) returns (BaseResponse) {}
    // 删除文件,对应rm命令
    rpc DeleteFile(DeleteFileRequest) returns (BaseResponse) {}
    // 重命名或移动文件,对应mv命令
    rpc RenameFile(RenameFileRequest) returns (BaseResponse) {}
    // 读取文件,对应cat命令
    rpc ReadFile(ReadFileRequest) returns (ReadFileResponse) {}
    // 上传文件
    rpc UploadFile(UploadFileRequest) returns (BaseResponse) {}
    rpc UploadFileWithoutSync(UploadFileRequest) returns (BaseResponse) {}
    // 下载文件
    rpc DownloadFile(DownloadFileRequest) returns (DownloadFileResponse) {}
    // 复制文件或文件夹
    rpc CopyFile(CopyFileRequest) returns (BaseResponse) {}
    // 写文件
    rpc WriteFile(WriteFileRequest) returns (BaseResponse) {}
    // 打开文件
    rpc OpenFile(OpenFileRequest) returns (BaseResponse) {}
    // 关闭文件
    rpc CloseFile(CloseFileRequest) returns (BaseResponse) {}
    // 通知下线
    rpc NotifyOffline(NotifyOfflineRequest) returns (BaseResponse) {}
    // 列出文件,对应ls命令
    rpc ListFile(ListFileRequest) returns (ListFileResponse) {}
    // ChangeDir
    rpc ChangeDir(ChangeDirRequest) returns (BaseResponse) {}
}
  • 消息体定义
message BaseResponse
{
    // 用于追踪请求,是由客户端生成的Snowflake ID
    int64 sequence_id = 1;
    int32 success     = 2;
    string message    = 3;
}

message CreateFileRequest
{
    int64 sequence_id = 1;
    string path       = 2;
    float ctime       = 3;
    float mtime       = 4;
}

message CreateDirectoryRequest
{
    int64 sequence_id = 1;
    string path       = 2;
    bool parent       = 4;
}

message DeleteFileRequest
{
    int64 sequence_id = 1;
    string path       = 2;
    bool recursive    = 3;
}

message RenameFileRequest
{
    int64 sequence_id = 1;
    string src        = 2;
    string dst        = 3;
}

message ReadFileRequest
{
    int64 sequence_id = 1;
    string path       = 2;
}

message ReadFileResponse
{
    int64 sequence_id = 1;
    int32 success     = 2;
    string content    = 3;
}
message UploadFileRequest
{
    int64 sequence_id = 1;
    string path       = 2;
    bytes content     = 3;
}

message DownloadFileRequest
{
    int64 sequence_id = 1;
    string path       = 2;
}

message DownloadFileResponse
{
    int64 sequence_id = 1;
    int32 success     = 2;
    bytes content     = 3;
}

message CopyFileRequest
{
    int64 sequence_id = 1;
    string src        = 2;
    string dst        = 3;
    bool recursive    = 4;
}

message NotifyOfflineRequest
{
    int32 e = 1;
}

message OpenFileRequest
{
    int64 sequence_id = 1;
    string path       = 2;
}

message WriteFileRequest
{
    int64 sequence_id = 1;
    string path       = 2;
    bytes content     = 3;
}

message ListFileRequest
{
    int64 sequence_id = 1;
    string path       = 2;
}

message ListFileResponse
{
    int64 sequence_id     = 1;
    int32 success         = 2;
    repeated string files = 3;
}

message ChangeDirRequest
{
    int64 sequence_id = 1;
    string path       = 2;
}

message CloseFileRequest
{
    int64 sequence_id = 1;
    string path       = 2;
}

函数定义

  • __init__(self, id=None, host=None, port=None, data_dir=None)
    注册dataserver信息,初始化一个nameserver客户端实例,远程调用 RegisterDataServer 方法,将自己的host和port注册。

  • ListFile(self, request, context)
    获取request的headers,提取出jwt,调用stub.VerifyJWT,验证是否成功登录。若成功登录,则返回f’{self.data_dir}{request.path}'路径下的文件列表。

  • CreateFile(self, request, context)
    从元数据中获取JWT,验证JWT。创建f’{self.data_dir}{request.path}'文件,nameserver客户端实例添加这个文件信息。返回成功信息。

  • DeleteFile(self, request, context)
    文件路径f'{self.data_dir}{request.path}',若可递归删除,则os.removedirs(file_path),否则仅可删除文件。返回删除成功响应。

  • ReadFile(self, request, context)
    文件路径f'{self.data_dir}{request.path}',返回content给request.sequence_id。

  • CreateDirectory(self, request, context)
    文件夹路径f'{self.data_dir}{request.path}',如果指定parent=False,则只会建立最后一层的文件夹,否则会递归建立。返回是否创建成功。

  • RenameFile(self, request, context)
    src = f'{self.data_dir}{request.src}'
    dst = f'{self.data_dir}{request.dst}'
    os.rename(src, dst),返回是否成功状态信息。

  • CopyFile(self, request, context)
    src = f'{self.data_dir}{request.src}'
    dst = f'{self.data_dir}{request.dst}'
    recursive = True 复制文件夹;recursive = False 复制文件
    返回是否成功的状态信息。

  • UploadFile(self, request, context)
    文件路径 f'{self.data_dir}{request.path}'
    文件内容 request.content
    写入文件,获取所有副本服务器的地址,将文件同步到所有副本服务器上。
    返回是否成功的状态信息。

  • UploadFileWithoutSync(self, request, context)
    文件路径 f'{self.data_dir}{request.path}'
    文件内容 request.content
    直接写入不需要同步,用于UploadFile()调用。
    返回是否成功的状态信息。

  • DownloadFile(self, request, context)
    文件路径 f'{self.data_dir}{request.path}'
    以二进制读取该文件,并将内容发送给sequence_id。
    返回文件内容。

  • NotifyOffline(self, request, context)
    收到消息后,关闭服务器

  • WriteFile(self, request, context)
    文件路径 f'{self.data_dir}{request.path}'
    采用覆盖写方式写入文件。
    返回是否成功的状态信息。

  • OpenFile(self, request, context)
    文件路径 f'{self.data_dir}{request.path}'
    判断该文件路径是否是文件,向NameServer请求加锁

  • CloseFile(self, request, context)
    文件路径 f'{self.data_dir}{request.path}'
    向 NameServer 请求解锁
    返回是否成功的状态信息

  • ChangeDir(self, request, context)
    目录路径:f'{self.data_dir}{request.path}'
    返回是否成功的状态信息。

启动函数

def start_server():
    id = getId()
    host = DFS_SETTINGS['DATASERVER']['HOST']
    port = DFS_SETTINGS['DATASERVER']['PORT']
    data_dir = DFS_SETTINGS['DATASERVER']['DATA_DIR']

    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    ds_grpc.add_DataServerServicer_to_server(
        DataServerServicer(id, host, port, data_dir), server)
    server.add_insecure_port(f'[::]:{port}')
    server.start()
    try:
        server.wait_for_termination()
    except KeyboardInterrupt:
        # 下线通知
        channel = grpc.insecure_channel(
            f'{DFS_SETTINGS["NAMESERVER"]["HOST"]}:{DFS_SETTINGS["NAMESERVER"]["PORT"]}')
        stub = ns_grpc.NameServerStub(channel)
        stub.LogoutDataServer(ns_pb2.DataServerInfo(
            id=id, host=host, port=port))

Nameserver

定义 proto 远程调用函数

service NameServer
{
    // 注册数据服务器
    rpc RegisterDataServer(DataServerInfo) returns (Response);
    // 获取数据服务器列表
    rpc GetDataServerList(empty) returns (GetDataServerListResponse);
    // 下线数据服务器
    rpc LogoutDataServer(DataServerInfo) returns (Response);
    // 注册用户
    rpc RegisterUser(RegisterRequest) returns (Response);
    // 登录
    rpc Login(LoginRequest) returns (LoginResponse);
    // 文件锁,lock_type: 0:读锁,1:写锁
    rpc LockFile(LockFileRequest) returns (Response);
    rpc UnlockFile(UnlockFileRequest) returns (Response);
    // 检查缓存
    rpc CheckCache(CheckCacheRequest) returns (Response);
    // 添加新文件(夹),提供完整文件信息
    rpc AddFile(FileInfo) returns (Response);
    // 删除文件(夹),提供文件路径
    rpc DeleteFile(DeleteRequest) returns (Response);
    // 修改文件(夹),提供原始文件路径,新文件路径,新文件大小,修改时间
    rpc ModifyFile(ModifyFileRequest) returns (Response);
    // 获取文件信息, 提供文件路径
    rpc GetFileInfo(GetFileInfoRequest) returns (FileInfoResponse);
    // 验证JWT
    rpc VerifyJWT(VerifyJWTRequest) returns (Response) {}
}
  • 消息体
message Response
{
    int32 success  = 1;
    string message = 2;
}

message DataServerInfo
{
    int64 id    = 1;
    string host = 2;
    int32 port  = 3;
}

message empty
{
    int32 e = 1;
}

message GetDataServerListResponse
{
    int32 success                              = 1;
    string message                             = 2;
    repeated DataServerInfo dataServerInfoList = 3;
}

message RegisterRequest
{
    string username = 1;
    string password = 2;
}

message LoginRequest
{
    string username = 1;
    string password = 2;
}

message LoginResponse
{
    int32 success  = 1;
    string message = 2;
    string jwt     = 3;
}

message LockFileRequest
{
    int32 lock_type = 1;
    string filepath = 2;
}

message UnlockFileRequest
{
    int32 lock_type = 1;
    string filepath = 2;
}

message CheckCacheRequest
{
    string absolute_path = 1;
    float mtime          = 2;
}

message GetFileInfoRequest
{
    string absolute_path = 1;
}

message FileInfo
{
    string absolute_path = 1;
    int64 size           = 2;
    bool is_dir          = 3;
    float ctime          = 4;
    float mtime          = 5;
}

message DeleteRequest
{
    string absolute_path = 1;
}

message ModifyFileRequest
{
    string old_absolute_path = 1;
    string new_absolute_path = 2;
    int64 new_size           = 3;
    float mtime              = 4;
}

message FileInfoResponse
{
    int64 size     = 1;
    bool is_dir    = 2;
    float ctime    = 3;
    float mtime    = 4;
    int32 success  = 5;
    string message = 6;
}

message VerifyJWTRequest
{
    string jwt = 1;
}

模块类

定义leveldb中存储的对象


class User(object):
    def __init__(self, username, password):
        self.username = username
        self.password = password

    def __repr__(self):
        return f'{self.username} password={self.password}>'


class File(object):
    def __init__(self, absolute_path, size, is_dir, ctime, mtime):
        self.absolute_path = absolute_path
        self.size = size
        self.is_dir = is_dir
        self.ctime = ctime
        self.mtime = mtime

    def __repr__(self):
        return f'{self.absolute_path} size={self.size} is_dir={self.is_dir} ctime={self.ctime} mtime={self.mtime}>'


class DataServer(object):
    def __init__(self, did, host, port):
        self.did = did
        self.host = host
        self.port = port

    def __repr__(self):
        return f'{self.did} host={self.host} port={self.port}>'


class TrieNode(object):
    def __init__(self):
        self.children = {}
        self.is_dir = False
        self.end_of_path = False

Trie

  • Trie 类是前缀树的主要类,包含一个根节点 root。
  • insert(path, is_dir=False):将一个路径插入到前缀树中。它从根节点开始,逐个字符遍历路径,如果字符不存在于当前节点的子节点中,则创建一个新的子节点。
  • check_dir(path):检查指定路径是否代表一个目录。它从根节点开始,逐个字符遍历路径,如果路径存在于前缀树中并且最后一个节点表示一个目录,则返回 True,否则返回 False。
  • delete(path):删除指定路径。它从根节点开始,逐个字符遍历路径,找到路径的最后一个字符,然后将该节点的 end_of_path 属性设置为 False,表示不再代表一个路径的结束。如果节点没有子节点并且不代表其他路径的结束,则可以删除该节点。
  • search(path):搜索指定路径是否存在于前缀树中。它从根节点开始,逐个字符遍历路径,如果路径存在于前缀树中并且最后一个节点的 end_of_path 属性为 True,则返回 True,表示路径存在。
  • get_children(path):获取指定路径的所有子节点的字符。它从根节点开始,逐个字符遍历路径,然后返回最后一个节点的所有子节点的字符列表。

class Trie(object):
    def __init__(self):
        self.root = TrieNode()

    def insert(self, path, is_dir=False):
        node = self.root
        for part in path:
            if part not in node.children:
                node.children[part] = TrieNode()
            node = node.children[part]
        node.end_of_path = True
        node.is_dir = is_dir

    def check_dir(self, path):
        node = self.root
        for part in path:
            if part not in node.children:
                return False
            node = node.children[part]
        return node.is_dir

    def delete(self, path):
        return self._delete(self.root, path, 0)

    def _delete(self, node, path, depth):
        if not node:
            return None

        # 如果已经到达路径的最后一个部分
        if depth == len(path):
            # 如果该节点是一个路径的结束
            if node.end_of_path:
                node.end_of_path = False
            # 如果该节点没有子节点,那么可以删除该节点
            return node if node.children else None
        # 如果还没有到达路径的最后一个部分
        else:
            part = path[depth]
            node.children[part] = self._delete(
                node.children.get(part), path, depth + 1)
            # 如果该节点不是一个路径的结束,并且没有子节点,那么可以删除该节点
            if not node.end_of_path and not node.children:
                return None
        return node

    def search(self, path):
        node = self.root
        for part in path:
            if part not in node.children:
                return False
            node = node.children[part]
        return node.end_of_path

    def get_children(self, path):
        node = self.root
        for part in path:
            if part not in node.children:
                return []
            node = node.children[part]
        return list(node.children.keys())

数据库操作定义

db.py 文件是一个使用 LevelDB 数据库的 Python 脚本,专门设计用于一个名为 nameserver 的系统。LevelDB 是一个轻量级的键值存储数据库,由 Google 开发。这个脚本提供了多种与用户和文件相关的操作,例如用户注册、登录、文件的创建、删除、更新等。

  • 初始化数据库,如果数据库里没有 trie 树记录,则新建。
nameserver_data_dir = DFS_SETTINGS['NAMESERVER']['DATA_DIR']

# 数据库路径
db_path = f'{nameserver_data_dir}/nameserver.db'

# 创建数据库文件的父目录,如果它不存在的话
os.makedirs(os.path.dirname(db_path), exist_ok=True)

# 初始化数据库
db = leveldb.LevelDB(db_path, create_if_missing=True)

key = pickle.dumps('TRIE')
try:
    value = db.Get(key)
except KeyError:
    # 创建TRIE树
    trie = Trie()
    db.Put(key, pickle.dumps(trie))

get_user(username)

def get_user(username):
    key = pickle.dumps(f'USER:{username}')
    try:
        value = db.Get(key)
        return pickle.loads(value)
    except KeyError:
        return None

register_user(username, password)

    # 判断用户名是否合法,只包含字母、数字、下划线,长度为1-20
    if not re.match(r'^\w{1,20}$', username):
        return False, 'Username is invalid!'
    # 判断用户是否存在
    key = pickle.dumps(f'USER:{username}')
    user = get_user(username)
    if user:
        return False, 'User already exists!'
    # 创建用户
    password = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
    user = User(username=username, password=password)
    value = pickle.dumps(user)
    try:
        db.Put(key, value)
    except Exception as e:
        return False, "System Error: Register failed!"
    return True, 'Register successfully!'

login(username, password)

def login(username, password):
    # 判断输入是否合法
    if not re.match(r'^\w{1,20}$', username):
        return False, 'Username is invalid!'
    
    if not re.match(r'^.{1,20}$', password):
        return False, 'Password is invalid!'
    
    user = get_user(username)
    if not user:
        return False, 'User not exists!'
    if not bcrypt.checkpw(password.encode(), user.password):
        return False, 'Password is wrong!'
    return True, 'Login successfully!'

create_file(absolute_path, size, is_dir, ctime, mtime)

def create_file(absolute_path, size, is_dir, ctime, mtime):
    # 判断文件是否存在
    key = pickle.dumps(f'FILE:{absolute_path}')
    try:
        value = db.Get(key)
        return False, 'File already exists!'
    except KeyError:
        pass
    # 创建文件
    file = File(absolute_path=absolute_path, size=size,
                is_dir=is_dir, ctime=ctime, mtime=mtime)
    value = pickle.dumps(file)
    try:
        db.Put(key, value)
    except KeyError:
        return False, 'System Error: Trie not exists!'
    except Exception as e:
        return False, "System Error: Create file failed!"
    return True, 'Create file successfully!'

delete_file(absolute_path)

def delete_file(absolute_path):
    key = pickle.dumps(f'FILE:{absolute_path}')
    try:
        db.Delete(key)
        trie = pickle.loads(db.Get(pickle.dumps('TRIE')))
        trie.delete(absolute_path.split('/'))
    except KeyError:
        return False, 'System Error: Trie not exists!'
    except Exception as e:
        return False, "System Error: Delete file failed!"
    return True, 'Delete file successfully!'

update_file(absolute_path, size, is_dir, mtime)

def update_file(absolute_path, size, is_dir, mtime):
    key = pickle.dumps(f'FILE:{absolute_path}')
    try:
        value = db.Get(key)
    except KeyError:
        return False, 'File not exists!'
    file = pickle.loads(value)
    if size != None and size > 0 or size < 1024 * 1024 * 10:
        file.size = size
    if is_dir != None and is_dir in [True, False]:
        file.is_dir = is_dir
    file.mtime = mtime
    value = pickle.dumps(file)
    try:
        db.Put(key, value)
    except Exception as e:
        return False, "System Error: Update file failed!"
    return True, 'Update file successfully!'

get_file(absolute_path)

def get_file(absolute_path):
    key = pickle.dumps(f'FILE:{absolute_path}')
    try:
        value = db.Get(key)
        return pickle.loads(value)
    except KeyError:
        return None

get_trie() & update_trie(trie)

def get_trie():
    key = pickle.dumps('TRIE')
    try:
        value = db.Get(key)
        return pickle.loads(value)
    except KeyError:
        return None


def update_trie(trie):
    key = pickle.dumps('TRIE')
    try:
        db.Put(key, pickle.dumps(trie))
        return True, 'Update trie successfully!'
    except Exception as e:
        return False, "System Error: Update trie failed!"

函数定义

  • __init__(self, id, host, port)
    初始化nameserver信息
    self.trie = db.get_trie(),获得 trie 树。TRIE 树用于快速检索和管理文件路径。

  • stop(self)
    更新self.trie,通知所有DataServer停止

  • RegisterDataServer(self, request, context)
    self.DataServerList添加元素,id、host、port。

  • GetDataServerList(self, request, context)
    返回self.DataServerList列表给request

  • LogoutDataServer(self, request, context)
    从 self.DataServerList 中移除所有 id 与 request.id 相等的元素。
    返回是否成功的状态信息。

  • RegisterUser(self, request, context)
    在数据库中添加一条信息保存用户名和密码。
    在trie树中根据username添加一个节点。
    返回是否成功的状态信息。

  • Login(self, request, context)
    首先检查用户是否已经登录,然后尝试使用用户名和密码登录,如果登录失败,它会检查是否存在一个与用户名相对应的目录,如果不存在,则在数据服务器上创建这个目录。

  • LockFile(self, request, context) & UnlockFile(self, request, context)
    首先判断用户是否登录,获取文件路径和锁的类型,根据不同类型,更改self.ReadLockDist和self.WriteLockDist

  • AddFile(self, request, context)
    添加File元数据到数据库,将文件路径添加到trie树中

  • DeleteFile(self, request, context)
    在db数据库中,删除信息

  • ModifyFile(self, request, context)
    首先判断old_absolute_path和new_absolute_path是否相同,若相同,则更新数据库中old_absolute_path的值;若不同,删除old_absolute_path信息,并添加new_absolute_path信息。

  • GetFileInfo(self, request, context)
    根据request.absolute_path在数据库中获取指定信息,返回size,is_dir,ctime,mtime

  • CheckCache(self, request, context)
    检查文件是否被修改。如果file.mtime > request.mtime 表示文件已经被修改。

  • VerifyJWT(self, request, context)
    判断jwt是否有效

  • gen_token(self, username)
    针对用户名和密码生成token

  • verify_token(self, token)
    token解码为user,查看数据库中是否存在这个用户

启动函数

id = getId()
host = DFS_SETTINGS['NAMESERVER']['HOST']
port = DFS_SETTINGS['NAMESERVER']['PORT']
Nserver = NameServerServicer(id, host, port)
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
ns_pb2_grpc.add_NameServerServicer_to_server(Nserver, server)
server.add_insecure_port(f"{host}:{port}")
server.start()
try:
    server.wait_for_termination()
except KeyboardInterrupt:
    Nserver.stop()
    server.stop(0)

Client

  • __init__(self):
    初始化

    self.current_dir = '/'
        self.username = None
        self.jwt = None
        self.cache_path = DFS_SETTINGS['CLIENT']['DATA_DIR']
        ns_host = DFS_SETTINGS['NAMESERVER']['HOST']
        ns_post = DFS_SETTINGS['NAMESERVER']['PORT']
        ns_channel = grpc.insecure_channel(f'{ns_host}:{ns_post}')
        self.ns_stub = ns_grpc.NameServerStub(ns_channel)
    

    用户选择一个dataserver,然后连接到这个dataserver

  • register(self, username, password)
    向nameserver请求注册用户

  • login(self, username, password)
    向nameserver请求登录,会收到jwt,将jwt设为自身值。

  • touch(self, path)
    在client上创建一个文件,并将这个空文件上传到dataserver中

  • ls(self, path)
    向data_server请求获取path下的文件

  • mkdir(self, path, parent=False)
    请求dataserver创建文件夹,然后client本地创建文件夹

  • cat(self, path)
    如果本地有该文件则直接在本地读取,否则从dataserver中读取,并保存文件

  • rm(self, path, recursive=False)
    删除本地和dataserver中的文件。

  • mv(self, src, dst)

  • cp(self, src, dst, recursive=False)

  • download(self, path)

  • openfile(self, path)

  • closefile(self, path)

  • cd(self, path)

你可能感兴趣的:(分布式,数据库,python,linux)