当前位置: 首页 > news >正文

分布式文件存储 RPC 服务实现

基于 BRPC+Protobuf+Etcd 的分布式文件存储 RPC 服务实现详解

一、系统整体架构:从 “通信” 到 “治理” 的全链路设计

这套文件存储系统是一套分布式 RPC 服务解决方案,核心目标是提供 “可靠的文件上传 / 下载能力”,并通过服务治理确保分布式环境下的可用性。整体分为 5 层,各层职责清晰、解耦协作:

层级核心组件作用
协议定义层Protobuf 文件(file.proto 等)定义 RPC 通信的 “契约”:数据结构 + 服务接口
业务逻辑层FileServiceImpl实现文件读写的核心业务逻辑
服务封装层FileServer、FileServerBuilder封装 BRPC 服务器,简化服务启停与配置
服务治理层Registry、Discovery、ServiceManager基于 Etcd 实现服务注册 / 发现、负载均衡
客户端测试层GTest 测试用例验证服务可用性,覆盖全量 RPC 接口

简单理解:客户端通过 “服务治理层” 找到可用的服务节点,通过 “协议定义层” 构造请求,调用 “业务逻辑层” 的文件处理接口,整个过程由 “服务封装层” 保障服务稳定运行。

二、协议定义层:用 Protobuf 制定 “通信规则”

Protobuf(Protocol Buffers)是 Google 的序列化协议,这里用来定义 “客户端和服务端该怎么说话”—— 包括传递的数据格式(消息)和可调用的方法(服务)。这是整个系统的 “语言基础”,客户端和服务端必须完全遵守。

2.1 核心消息(Message)定义

消息是 RPC 通信的 “数据载体”,每个消息对应一个具体的数据结构,以下是关键消息的解析:

消息名称作用关键字段说明
GetSingleFileReq单文件下载请求request_id(请求唯一标识)、file_id(文件 ID)
GetSingleFileRsp单文件下载响应success(是否成功)、errmsg(错误信息)、file_data(文件数据)
GetMultiFileReq多文件下载请求file_id_list(文件 ID 列表,repeated 表示 “多个”)
GetMultiFileRsp多文件下载响应file_data(map 结构:file_id→文件数据,方便匹配)
PutSingleFileReq单文件上传请求file_data(包含文件名、大小、二进制内容)
PutSingleFileRsp单文件上传响应file_info(返回文件元信息:file_id、大小、名称)
FileDownloadData文件下载数据载体file_id(文件 ID)、file_content(二进制文件内容)
FileUploadData文件上传数据载体file_name(文件名)、file_size(大小)、file_content(二进制内容)
MessageType消息类型枚举(扩展用)STRING(文字)、IMAGE(图片)、FILE(文件)、SPEECH(语音)
UserInfo/ChatSessionInfo聊天相关(扩展用)支持将文件服务集成到聊天系统,存储头像、附件等

关键特性解析

  • optional:字段可选(如 user_id,非必填);

  • repeated:字段可重复(如 file_id_list,对应 “列表”);

  • map:键值对结构(如多文件响应的 file_data,快速通过 file_id 查文件);

  • oneof:消息类型互斥(如 MessageContent 的 msg_content,只能是文字 / 图片 / 文件 / 语音中的一种)。

2.2 服务(Service)定义

服务是 RPC 的 “方法集合”,定义了客户端可调用的远程函数。这里FileService包含 4 个核心接口,覆盖文件的全量操作:

service FileService {// 下载单个文件:传入file_id,返回文件数据rpc GetSingleFile(GetSingleFileReq) returns (GetSingleFileRsp);// 下载多个文件:传入file_id列表,返回file_id→文件数据的映射rpc GetMultiFile(GetMultiFileReq) returns (GetMultiFileRsp);// 上传单个文件:传入文件数据,返回文件元信息(含生成的file_id)rpc PutSingleFile(PutSingleFileReq) returns (PutSingleFileRsp);// 上传多个文件:传入多个文件数据,返回多个文件的元信息rpc PutMultiFile(PutMultiFileReq) returns (PutMultiFileRsp);
}

三、服务端实现:从 “业务逻辑” 到 “服务启停”

服务端是文件存储的 “核心执行层”,负责接收客户端请求、处理文件读写,并通过 BRPC 提供 RPC 能力。代码采用 C++ 实现,用 “面向对象 + 设计模式” 封装,可读性和可维护性强。

3.1 业务逻辑核心:FileServiceImpl

FileServiceImplFileService接口的具体实现,所有文件读写的业务逻辑都在这里,相当于 “干活的工人”。

3.1.1 构造函数:初始化存储路径
FileServiceImpl(const std::string &storage_path): _storage_path(storage_path) {umask(0); // 清除文件权限掩码,确保生成的文件可读写mkdir(storage_path.c_str(), 0775); // 创建存储目录(权限:所有者读写执行,组读写执行)if (_storage_path.back() != '/') _storage_path.push_back('/'); // 确保路径以“/”结尾
}

作用:初始化文件存储目录,避免后续拼接文件名时出错。

3.1.2 核心方法:单文件下载(GetSingleFile)

逻辑流程:

  1. 从请求中获取file_id(文件 ID 即存储的文件名);

  2. 拼接存储路径(存储目录+file_id),读取文件内容;

  3. 构造响应:若读取成功,填充文件数据;若失败,返回错误信息。

关键代码:

void GetSingleFile(...) {brpc::ClosureGuard rpc_guard(done); // 自动释放RPC回调资源,避免内存泄漏response->set_request_id(request->request_id()); // 回传request_id,方便链路追踪std::string fid = request->file_id();std::string filename = _storage_path + fid; // 拼接文件路径std::string body; // 存储读取的文件内容// 调用utils.hpp的readFile函数读取文件if (!readFile(filename, body)) {response->set_success(false);response->set_errmsg("读取文件数据失败!");LOG_ERROR("{} 读取文件数据失败!", request->request_id());return;}// 读取成功,构造响应response->set_success(true);response->mutable_file_data()->set_file_id(fid); // 设置文件IDresponse->mutable_file_data()->set_file_content(body); // 设置文件二进制内容
}
3.1.3 核心方法:单文件上传(PutSingleFile)

逻辑流程:

  1. 生成唯一file_id(通过uuid()函数,避免文件名冲突);

  2. 拼接存储路径,将请求中的文件内容写入磁盘;

  3. 构造响应:返回文件元信息(file_id、大小、名称)。

关键差异:上传需要 “生成唯一 ID”,下载需要 “根据 ID 找文件”,这是两者的核心区别。多文件上传 / 下载逻辑类似,只是通过循环批量处理多个文件。

3.2 服务封装:FileServer 与 FileServerBuilder

FileServiceImpl只负责 “干活”,而FileServer负责 “管理服务”,FileServerBuilder负责 “创建服务”—— 这是典型的构建者模式,把 “对象创建” 和 “对象使用” 解耦,方便配置不同参数(如端口、线程数、Etcd 地址)。

3.2.1 FileServer:服务 “管理者”

封装 BRPC 服务器实例和 Etcd 注册客户端,只暴露一个start()方法启动服务:

class FileServer {
public:using ptr = std::shared_ptr<FileServer>; // 智能指针,自动管理内存// 构造函数:传入Etcd注册客户端和BRPC服务器FileServer(const Registry::ptr &reg_client, const std::shared_ptr<brpc::Server> &server): _reg_client(reg_client), _rpc_server(server) {}// 启动服务:阻塞直到收到停止信号(如Ctrl+C)void start() { _rpc_server->RunUntilAskedToQuit(); }
private:Registry::ptr _reg_client; // Etcd注册客户端std::shared_ptr<brpc::Server> _rpc_server; // BRPC服务器实例
};
3.2.2 FileServerBuilder:服务 “创建者”

分 3 步创建FileServer实例,步骤清晰,支持灵活配置:

  1. make_reg_object:创建 Etcd 注册客户端,将服务注册到 Etcd;

  2. make_rpc_server:创建 BRPC 服务器,添加FileServiceImpl业务逻辑,配置端口、线程数;

  3. build:校验配置,生成FileServer实例。

关键代码(make_rpc_server):

void make_rpc_server(uint16_t port, int32_t timeout, uint8_t num_threads, const std::string &path = "./data/") {_rpc_server = std::make_shared<brpc::Server>();FileServiceImpl *file_service = new FileServiceImpl(path); // 创建业务逻辑实例// 将业务逻辑注册到BRPC服务器(SERVER_OWNS_SERVICE:服务器负责释放实例内存)if (_rpc_server->AddService(file_service, brpc::ServiceOwnership::SERVER_OWNS_SERVICE) == -1) {LOG_ERROR("添加Rpc服务失败!");abort(); // 严重错误,终止程序}// 配置BRPC服务器:超时时间、线程数brpc::ServerOptions options;options.idle_timeout_sec = timeout; // 空闲连接超时时间(秒)options.num_threads = num_threads; // 处理请求的线程数// 启动BRPC服务器,监听指定端口if (_rpc_server->Start(port, &options) == -1) {LOG_ERROR("服务启动失败!");abort();}
}

四、服务治理层:让分布式服务 “可管可控”

在分布式环境中,服务可能有多个节点(如多台服务器部署文件服务),客户端需要知道 “该连哪个节点”—— 这就是 “服务治理” 的作用。这套系统基于Etcd实现服务注册发现,基于RR(轮询) 实现负载均衡。

4.1 Etcd 封装:Registry 与 Discovery

Etcd 是分布式键值存储,这里相当于服务的 “通讯录”:服务端把自己的地址注册到 Etcd(Registry),客户端从 Etcd 获取服务地址(Discovery)。

4.1.1 Registry:服务 “注册者”

将服务节点地址注册到 Etcd,并通过 “租约(Lease)” 机制保持心跳 —— 如果服务节点宕机,租约过期,Etcd 会自动删除该节点信息,避免客户端连接无效节点。

关键代码:

class Registry {
public:using ptr = std::shared_ptr<Registry>;// 构造函数:连接Etcd,创建3秒租约(心跳间隔3秒)Registry(const std::string &host): _client(std::make_shared<etcd::Client>(host)), _keep_alive(_client->leasekeepalive(3).get()), _lease_id(_keep_alive->Lease()) {}// 注册服务:key=服务名(如/service/file_service),val=服务地址(如127.0.0.1:8000)bool registry(const std::string &key, const std::string &val) {auto resp = _client->put(key, val, _lease_id).get(); // 绑定租约if (!resp.is_ok()) {LOG_ERROR("注册数据失败:{}", resp.error_message());return false;}return true;}
};
4.1.2 Discovery:服务 “发现者”

客户端用Discovery从 Etcd 获取服务地址,分两步:

  1. 启动时 “拉取” 已有服务节点(避免错过已上线的节点);

  2. 实时 “监控” Etcd 变化(服务上线 / 下线时收到通知)。

关键逻辑:

  • 通过etcd::Watcher监控 Etcd 的键值变化;

  • 服务上线(PUT 事件)时,调用_put_cb通知;

  • 服务下线(DELETE 事件)时,调用_del_cb通知。

4.2 信道管理:ServiceChannel 与 ServiceManager

客户端拿到服务地址后,需要管理 “连接”(即 BRPC 的 Channel),并实现负载均衡 ——ServiceChannel管理单个服务的所有信道,ServiceManager管理所有服务的信道。

4.2.1 ServiceChannel:单个服务的 “信道池”

为单个服务(如 file_service)维护多个节点的信道,用RR 轮询选择信道,实现负载均衡:

  • append:新增服务节点的信道;

  • remove:删除下线节点的信道;

  • choose:轮询选择一个信道(线程安全,用 mutex 保护)。

关键代码(choose 方法):

ChannelPtr choose() {std::unique_lock<std::mutex> lock(_mutex); // 线程安全if (_channels.empty()) {LOG_ERROR("当前没有能够提供 {} 服务的节点!", _service_name);return ChannelPtr();}// RR轮询:取当前下标,然后自增(取模避免越界)int32_t idx = _index++ % _channels.size();return _channels[idx];
}
4.2.2 ServiceManager:全局服务的 “信道管家”

管理所有需要关注的服务(通过declared声明),接收Discovery的服务上下线通知,更新对应服务的信道:

  • declared:声明 “我关心哪个服务”(如只关心 file_service);

  • onServiceOnline:服务上线时,添加信道到对应ServiceChannel

  • onServiceOffline:服务下线时,删除对应信道;

  • choose:获取指定服务的一个信道(供客户端调用)。

五、客户端测试:用 GTest 验证服务可用性

客户端测试是保障服务正确运行的关键,这里用GTest框架编写测试用例,覆盖所有 4 个 RPC 接口,验证 “上传→下载→数据一致性” 全流程。

5.1 测试用例设计:覆盖全场景

测试用例按 “功能 + 操作” 分类,顺序执行(单文件上传→单文件下载→多文件上传→多文件下载),用全局变量传递file_id(下载需要依赖上传生成的 ID)。

5.1.1 单文件上传测试(put_test, single_file)

步骤:

  1. 读取本地文件(如./Makefile);

  2. 构造PutSingleFileReq,填充文件名称、大小、内容;

  3. 发起 RPC 调用,断言响应成功(success=true);

  4. 保存返回的file_id,供后续下载测试使用。

关键断言:

ASSERT_TRUE(zrt::readFile("./Makefile", body)); // 断言本地文件读取成功
ASSERT_FALSE(cntl->Failed()); // 断言RPC调用无网络错误
ASSERT_TRUE(rsp->success()); // 断言服务端处理成功
ASSERT_EQ(rsp->file_info().file_size(), body.size()); // 断言文件大小一致
single_file_id = rsp->file_info().file_id(); // 保存file_id
5.1.2 单文件下载测试(get_test, single_file)

步骤:

  1. 用上传保存的single_file_id构造GetSingleFileReq

  2. 发起 RPC 调用,断言响应成功;

  3. 将下载的文件内容写入本地(如 make_file_download);

  4. (隐含验证)对比本地原文件和下载文件,确认数据一致。

5.2 客户端初始化:连接服务的 “准备工作”

main 函数中完成测试前的初始化,核心是 “获取服务信道”:

  1. 初始化日志(配置日志文件、级别);

  2. 创建ServiceManager,声明关注 file_service;

  3. 创建Discovery,连接 Etcd,监听服务上下线;

  4. ServiceManager获取 file_service 的信道;

  5. 执行所有测试用例。

六、关键技术点解析:为什么这么设计?

这套系统用到了多个工业级技术和设计思想,理解这些技术点能更深入掌握分布式服务开发。

6.1 BRPC:高性能 RPC 框架

BRPC 是百度开源的 RPC 框架,优势在于高性能、易用性强:

  • Service注册:通过AddService将业务逻辑绑定到服务器;

  • Controller:管理 RPC 调用的上下文(如错误信息、超时);

  • ClosureGuard:自动释放Closure资源,避免内存泄漏;

  • 多协议支持:默认使用 “baidu_std” 协议,也支持 HTTP、gRPC 等。

6.2 Protobuf:高效序列化

相比 JSON、XML,Protobuf 的优势是 “体积小、速度快”,适合 RPC 通信:

  • oneof:避免冗余字段(如消息类型只需要一种,不用传所有类型的字段);

  • optional:节省带宽(非必需字段可省略);

  • 二进制序列化:比文本格式(如 JSON)体积小 50% 以上,解析速度快 10 倍以上。

6.3 设计模式:解耦与复用

  • 构建者模式(FileServerBuilder):复杂对象(FileServer)的创建分步骤,支持不同配置(如不同端口、不同存储路径);

  • 智能指针(shared_ptr):自动管理内存,避免内存泄漏(如FileServer::ptrChannelPtr);

  • 观察者模式(Discovery+ServiceManager)Discovery监控 Etcd 变化,ServiceManager作为观察者接收通知,解耦 “监控” 和 “处理”。

6.4 线程安全:避免并发问题

服务端和客户端都是多线程运行,必须保证线程安全:

  • std::mutex:保护共享资源(如ServiceChannel_channels列表,避免同时读写);

  • brpc::ClosureGuard:BRPC 回调在多线程中执行,Guard 确保资源正确释放。

七、系统使用与扩展建议

7.1 实际使用流程

  1. 编译 Proto:用 protoc 编译 file.proto,生成 C++ 代码(.h 和.cc 文件);

  2. 编译服务端:链接 BRPC、Etcd、Protobuf 库,编译服务端程序;

  3. 启动服务端:指定存储路径、Etcd 地址、端口(如./file_server --storage_path ./data --etcd_host ``http://127.0.0.1:2379`` --port 8000);

  4. 编译客户端:链接 GTest、BRPC、Etcd 库,编译测试程序;

  5. 运行测试:启动客户端测试程序,验证服务可用性。

7.2 扩展建议

这套系统是基础版本,实际项目中可根据需求扩展:

  1. 文件分片上传:大文件(如 1GB 以上)分块上传,避免单次请求过大;

  2. 权限控制:利用user_id字段实现权限校验(如只有上传者能下载);

  3. 缓存优化:热门文件缓存到内存,减少磁盘 IO;

  4. 监控告警:添加 Prometheus 监控(服务 QPS、文件存储量),配置告警(服务下线、磁盘满);

  5. 数据备份:定期备份存储目录,避免数据丢失。

八、总结

这套分布式文件存储 RPC 服务,从 “协议定义” 到 “业务实现”,再到 “服务治理”,形成了完整的解决方案。核心优势在于:

  1. 易用性:通过构建者模式简化服务配置,客户端调用像本地函数一样简单;

  2. 可靠性:Etcd 服务治理确保客户端总能连接到可用节点,RR 负载均衡避免单点压力;

  3. 可扩展性:模块化设计,新增功能(如分片上传)只需扩展对应模块,不影响其他逻辑。

对于需要分布式文件存储的场景(如聊天系统的附件传输、云存储服务),这套系统可以作为基础框架,快速迭代开发。

http://www.dtcms.com/a/469242.html

相关文章:

  • 在哪些软件上建设网站市场营销方案怎么做
  • 《小小梦魇3》今日发售!用UU远程手机躺玩通关
  • Jenkins Pipeline post指令详解
  • 系列文章<一>(从LED显示问题到非LED领域影像画质优化:揭秘跨领域的核心技术):从LED冬奥会、奥运会及春晚等大屏,到手机小屏,快来挖一挖里面都有什么
  • 泊松分布解题步骤
  • Postman API 测试使用指南:从入门到精通
  • VisualSVN-Server-2.5.26 TortoiseSVN-1.7.21
  • SVN 检出操作
  • 深度相机初探:立体视觉(Stereo Vision)、结构光(Structured Light)、TOF(Time of Flight,飞行时间)
  • SVN 生命周期
  • 武昌做网站公司互联网营销顾问是做什么的
  • 辉视融合服务器:强劲驱动电视信息发布,直播点播流畅运行,赋能高效传播
  • MyBatis-Spring-Boot快速上手指南
  • Linux运维实战:系统及服务管理(视频教程)
  • 服务器运维(四)服务器漏洞扫描工具与审查——东方仙化神期
  • SolidWorks服务器多人使用方案
  • 安卓手机app开发软件下载网站关键词优化效果
  • Redis中的RPOP、BRPOP、LPOP 和 BLPOP
  • R语言学习
  • 【C++】C++11 新特性详解(下)
  • 成都市公园城市建设管理局网站济南百度推广开户
  • 网站的技术建设公司网站建设 wordpress
  • 联想小新平板Pro GT/Y700四代平板无需解锁BL获取root权限方法
  • Linux系统安装PGSQL实现向量存储
  • 跨语言协作新范式:阿里云Qwen-MT与DooTask的翻译技术突破
  • LLM 笔记 —— 04 为什么语言模型用文字接龙,图片模型不用像素接龙呢?
  • ubuntu-20.04.6升级OpenSSH_10.2p1
  • redis lua脚本(go)调用教程以及debug调试
  • shopnc本地生活o2o网站源码有声小说网站开发
  • OpenHarmony 之Telephony电话服务技术详解:架构设计与Modem厂商库集成机制