跳到主要内容
C++微服务架构下的好友管理子服务设计与实现 | 极客日志
C++
C++微服务架构下的好友管理子服务设计与实现 一、服务定位与技术栈 在即时通讯(IM)系统中,**好友管理子服务**是连接'用户社交关系'与'聊天会话'的核心枢纽。它既要处理好友申请与关系维护,也要管理单聊/群聊会话的创建与成员维护。基于实际项目代码(C++/brpc/Protobuf/ODB),从接口设计、数据模型、核心逻辑、高可用部署四个维度,完整拆解好友管理子服务的实现细节。 核心业务范围 **好友关系**:申请、同意/拒绝、删除、…
ByteFlow 发布于 2026/3/30 更新于 2026/5/23 33K 浏览一、服务定位与技术栈
在即时通讯(IM)系统中,好友管理子服务 是连接'用户社交关系'与'聊天会话'的核心枢纽。它既要处理好友申请与关系维护,也要管理单聊/群聊会话的创建与成员维护。本文基于实际项目代码(C++/brpc/Protobuf/ODB),从接口设计、数据模型、核心逻辑、高可用部署四个维度,完整拆解好友管理子服务的实现细节。
1. 核心业务范围
好友关系 :申请、同意/拒绝、删除、查询好友列表;
会话管理 :创建单聊/群聊会话、查询会话列表、获取群成员;
用户搜索 :基于 ES 实现昵称/手机号模糊搜索(过滤已加好友);
申请事件 :管理好友申请的生命周期(待处理→已处理)。
2. 技术栈选型
技术组件 作用说明 项目中的封装/使用场景 brpc 高性能 RPC 框架 搭建 RPC 服务器,提供 Protobuf 接口 Protobuf 数据序列化/接口定义 定义服务接口(如 FriendAdd/GetChatSessionList) ODB MySQL ORM 框架 映射数据库表到 C++ 对象(如 RelationTable) MySQL 关系型数据库 存储好友关系、会话信息、申请事件 Elasticsearch 全文搜索引擎 实现用户模糊搜索,过滤已加好友和当前用户 Etcd 服务注册与发现 注册自身服务,发现用户服务、消息服务地址 gflags 命令行参数解析 配置服务端口、数据库地址、日志等级等 spdlog 日志框架 输出调试/错误日志,支持文件/控制台双输出
二、Protobuf 接口设计:定义服务的通信契约
微服务的核心是接口化协作,好友管理子服务的对外交互全靠 Protobuf 定义的接口。以下结合项目代码,拆解核心接口的设计逻辑。
1. 核心接口概览
好友服务共提供 10 个核心接口,覆盖'好友关系''会话管理''搜索'三大场景,接口设计遵循单一职责原则:
接口名称 业务场景 核心作用 GetFriendList查看好友列表 返回当前用户的所有好友信息(昵称、头像等) FriendAdd发送好友申请 校验关系后创建申请事件,返回事件 ID FriendAddProcess处理好友申请
FriendRemove删除好友 移除好友关系+单聊会话+会话成员
FriendSearch搜索用户 ES 模糊搜索,过滤已加好友和当前用户
GetPendingFriendEventList查看待处理申请 返回当前用户收到的所有未处理好友申请
GetChatSessionList查看聊天会话列表 返回单聊/群聊会话,包含最新消息
ChatSessionCreate创建群聊会话 生成群聊 ID,添加会话信息和成员
GetChatSessionMember查看群成员 返回指定群聊的所有成员信息
2. 关键接口字段解析(以好友申请为例)
(1)FriendAdd:发送好友申请 message FriendAddReq {
string request_id = 1; // 链路追踪ID(分布式调用唯一标识)
string user_id = 2; // 申请人ID(当前登录用户)
string respondent_id = 3; // 被申请人ID(目标用户)
}
message FriendAddRsp {
string request_id = 1; // 对应请求ID
bool success = 2; // 申请是否成功
string errmsg = 3; // 错误信息(失败时填充)
string notify_event_id = 4; // 申请事件ID(用于后续处理)
}
业务逻辑 :
用户 A 申请加 B 为好友时,网关会先鉴权并填充 user_id(A 的 ID),服务端需校验:
A 和 B 是否已为好友(查 relation 表);
A 是否已向 B 发送过申请(查 friend_apply 表);
校验通过后生成 notify_event_id(UUID),存入 friend_apply 表。
(2)FriendAddProcess:处理好友申请 message FriendAddProcessReq {
string request_id = 1; // 链路追踪ID
string user_id = 2; // 被申请人ID(当前登录用户)
string apply_user_id = 3; // 申请人ID(A的ID)
bool agree = 4; // 是否同意(true=同意,false=拒绝)
string notify_event_id = 5; // 申请事件ID(对应FriendAdd返回的ID)
}
message FriendAddProcessRsp {
string request_id = 1; // 对应请求ID
bool success = 2; // 处理是否成功
string errmsg = 3; // 错误信息
string new_session_id = 4; // 同意时生成的单聊会话ID(网关推送用)
}
业务逻辑 :
用户 B 处理 A 的申请时,服务端需:
校验 notify_event_id 对应的申请是否存在;
无论同意/拒绝,先删除申请事件(避免重复处理);
同意则创建双向好友关系 (relation 表插两条记录)、单聊会话 (chat_session 表)、会话成员 (chat_session_member 表插 A 和 B 的记录)。
三、ODB 数据模型:映射业务对象与数据库表 好友服务的核心数据存储在 MySQL 中,通过 ODB(Object-Relational Mapping) 框架将 C++ 对象与数据库表关联,避免直接编写 SQL 语句,提升代码可维护性。
1. 核心表结构与 ODB 映射 数据库表名 ODB 映射类 核心字段(C++/MySQL) 业务作用 relation(好友关系表)RelationTable_user_id(varchar64)/_peer_id(varchar64)存储双向好友关系(A→B 和 B→A) friend_apply(申请事件表)FriendApplyTable_event_id(varchar64)/_user_id/_peer_id存储好友申请的生命周期(待处理/已处理) chat_session(会话表)ChatSessionTable_chat_session_id(varchar64)/_session_type(tinyint)存储单聊/群聊会话元信息(类型、名称) chat_session_member(会话成员表)ChatSessionMemberTable_chat_session_id/_user_id存储'会话-用户'关联(群聊成员、单聊双方)
2. ODB 映射类实现(以好友关系表为例)
#pragma once
#include <odb/core.hxx>
#include <string>
namespace zrt {
#pragma db object table("relation" )
class Relation {
public :
Relation () {}
Relation (const std::string& user_id, const std::string& peer_id)
: _user_id(user_id), _peer_id(peer_id) {}
std::string user_id () const { return _user_id; }
void user_id (const std::string& v) { _user_id = v; }
std::string peer_id () const { return _peer_id; }
void peer_id (const std::string& v) { _peer_id = v; }
private :
friend class odb ::access;
#pragma db id auto
unsigned long _id;
#pragma db type("varchar(64)" ) index
std::string _user_id;
#pragma db type("varchar(64)" )
std::string _peer_id;
};
class RelationTable {
public :
using ptr = std::shared_ptr<RelationTable>;
RelationTable (const std::shared_ptr<odb::core::database>& db) : _db(db) {}
bool insert (const std::string& user_id, const std::string& peer_id) {
try {
odb::transaction t (_db->begin()) ;
_db->persist (Relation (user_id, peer_id));
_db->persist (Relation (peer_id, user_id));
t.commit ();
return true ;
} catch (const odb::exception& e) {
LOG_ERROR ("Insert relation failed: {}" , e.what ());
return false ;
}
}
bool exists (const std::string& user_id, const std::string& peer_id) {
try {
odb::transaction t (_db->begin()) ;
auto count = _db->query_value <Relation>(
odb::query<Relation>::_user_id == user_id &&
odb::query<Relation>::_peer_id == peer_id
).count ();
t.commit ();
return count > 0 ;
} catch (const odb::exception& e) {
LOG_ERROR ("Check relation exists failed: {}" , e.what ());
return false ;
}
}
private :
std::shared_ptr<odb::core::database> _db;
};
}
双向关系 :插入好友时必须存两条记录(A→B 和 B→A),确保双方查询好友列表时都能找到对方;
事务保障 :通过 ODB 的 transaction 确保'插入两条记录'要么全成功,要么全失败,避免数据不一致;
索引优化 :_user_id 加索引,查询'用户的所有好友'时性能显著提升。
四、核心服务实现:从接口逻辑到服务协作 好友服务的核心逻辑封装在 FriendServiceImpl 类中,通过'接口实现+私有方法'的方式实现业务解耦与代码复用。
1. 服务初始化:依赖注入与解耦设计 FriendServiceImpl 的构造函数通过依赖注入 (DI)传入所有外部依赖,避免硬编码,便于测试与扩展:
FriendServiceImpl (
const std::shared_ptr<elasticlient::Client>& es_client,
const std::shared_ptr<odb::core::database>& mysql_client,
const ServiceManager::ptr& channel_manager,
const std::string& user_service_name,
const std::string& message_service_name
) : _es_user(std::make_shared <ESUser>(es_client)),
_mysql_apply(std::make_shared <FriendApplyTable>(mysql_client)),
_mysql_chat_session(std::make_shared <ChatSessionTable>(mysql_client)),
_mysql_chat_session_member(std::make_shared <ChatSessionMemberTable>(mysql_client)),
_mysql_relation(std::make_shared <RelationTable>(mysql_client)),
_user_service_name(user_service_name),
_message_service_name(message_service_name),
_mm_channels(channel_manager) {}
数据层解耦 :通过 RelationTable/FriendApplyTable 封装 MySQL 操作,业务逻辑不直接依赖 ODB;
服务间解耦 :通过 ServiceManager 管理其他服务的 RPC 信道,避免直接硬编码服务地址(服务地址变化时,只需更新 Etcd,无需改代码)。
2. 关键接口实现:以'处理好友申请'为例 FriendAddProcess 涉及'申请事件删除、好友关系创建、会话创建'三个关键步骤,需确保数据一致性:
void FriendServiceImpl::FriendAddProcess (
::google::protobuf::RpcController* controller,
const ::zrt::FriendAddProcessReq* request,
::zrt::FriendAddProcessRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard (done) ;
auto err_response = [this , response](const std::string& rid, const std::string& errmsg) {
response->set_request_id (rid);
response->set_success (false );
response->set_errmsg (errmsg);
};
std::string rid = request->request_id ();
std::string eid = request->notify_event_id ();
std::string uid = request->user_id ();
std::string pid = request->apply_user_id ();
bool agree = request->agree ();
if (!_mysql_apply->exists (pid, uid)) {
LOG_ERROR ("{}-未找到{}-{}的好友申请事件" , rid, pid, uid);
return err_response (rid, "申请事件不存在" );
}
if (!_mysql_apply->remove (pid, uid)) {
LOG_ERROR ("{}-删除申请事件{}-{}失败" , rid, pid, uid);
return err_response (rid, "处理申请失败" );
}
std::string session_id;
if (agree) {
if (!_mysql_relation->insert (uid, pid)) {
LOG_ERROR ("{}-创建好友关系{}-{}失败" , rid, uid, pid);
return err_response (rid, "添加好友失败" );
}
session_id = uuid ();
ChatSession session (session_id, "" , ChatSessionType::SINGLE) ;
if (!_mysql_chat_session->insert (session)) {
LOG_ERROR ("{}-创建单聊会话{}失败" , rid, session_id);
return err_response (rid, "创建会话失败" );
}
std::vector<ChatSessionMember> members = {
ChatSessionMember (session_id, uid),
ChatSessionMember (session_id, pid)
};
if (!_mysql_chat_session_member->append (members)) {
LOG_ERROR ("{}-添加会话成员{}-{}失败" , rid, uid, pid);
return err_response (rid, "添加会话成员失败" );
}
}
response->set_request_id (rid);
response->set_success (true );
response->set_new_session_id (session_id);
LOG_INFO ("{}-处理好友申请成功:{}→{},agree={}" , rid, pid, uid, agree);
}
错误统一处理 :通过 err_response 回调函数,确保所有错误场景的响应格式一致;
数据一致性 :删除申请事件后再创建关系,避免'申请事件残留';若创建会话失败,直接返回错误,后续可通过重试机制补全;
会话 ID 生成 :使用 UUID 确保全局唯一,避免分布式环境下的 ID 冲突。
3. 服务间协作:调用用户服务与消息服务 好友服务本身不存储用户信息和消息数据,需通过 RPC 调用其他服务获取。
(1)调用用户服务批量获取用户信息 bool FriendServiceImpl::GetUserInfo (
const std::string& rid,
const std::unordered_set<std::string>& uid_list,
std::unordered_map<std::string, UserInfo>& user_list) {
auto channel = _mm_channels->choose (_user_service_name);
if (!channel) {
LOG_ERROR ("{}-获取用户服务信道失败" , rid);
return false ;
}
GetMultiUserInfoReq req;
GetMultiUserInfoRsp rsp;
req.set_request_id (rid);
for (const auto & uid : uid_list) {
req.add_users_id (uid);
}
brpc::Controller cntl;
zrt::UserService_Stub stub (channel.get()) ;
stub.GetMultiUserInfo (&cntl, &req, &rsp, nullptr );
if (cntl.Failed () || !rsp.success ()) {
LOG_ERROR ("{}-批量获取用户信息失败" , rid);
return false ;
}
for (const auto & item : rsp.users_info ()) {
user_list.emplace (item.first, item.second);
}
return true ;
}
优化点 :批量调用减少网络开销;ServiceManager 自动管理用户服务地址(从 Etcd 发现),服务扩缩容时无需重启。
(2)调用消息服务获取会话最新消息 bool FriendServiceImpl::GetRecentMsg (
const std::string& rid,
const std::string& session_id,
MessageInfo& msg) {
auto channel = _mm_channels->choose (_message_service_name);
if (!channel) {
LOG_ERROR ("{}-获取消息服务信道失败" , rid);
return false ;
}
GetRecentMsgReq req;
GetRecentMsgRsp rsp;
req.set_request_id (rid);
req.set_chat_session_id (session_id);
req.set_msg_count (1 );
brpc::Controller cntl;
zrt::MsgStorageService_Stub stub (channel.get()) ;
stub.GetRecentMsg (&cntl, &req, &rsp, nullptr );
if (cntl.Failed () || !rsp.success ()) {
LOG_WARN ("{}-获取会话{}最新消息失败" , rid, session_id);
return false ;
}
if (rsp.msg_list_size () > 0 ) {
msg.CopyFrom (rsp.msg_list (0 ));
return true ;
}
return false ;
}
业务价值 :查询会话列表时显示'每条会话的最新消息',符合微服务'数据私有'原则。
五、服务搭建与高可用部署 好友服务通过 FriendServerBuilder 模式封装初始化流程,支持'配置解析→依赖构建→服务启动'的一站式部署。
1. Builder 模式:简化服务初始化 class FriendServerBuilder {
public :
void make_es_object (const std::vector<std::string>& host_list) {
_es_client = ESClientFactory::create (host_list);
}
void make_mysql_object (const std::string& user, const std::string& pswd,
const std::string& host, const std::string& db,
const std::string& cset, int port, int conn_pool_count) {
_mysql_client = ODBFactory::create (user, pswd, host, db, cset, port, conn_pool_count);
}
void make_discovery_object (const std::string& reg_host, const std::string& base_service,
const std::string& user_service_name, const std::string& message_service_name) {
_mm_channels = std::make_shared <ServiceManager>();
_mm_channels->declared (user_service_name);
_mm_channels->declared (message_service_name);
auto on_service_online = std::bind (&ServiceManager::onServiceOnline, _mm_channels.get (), std::placeholders::_1, std::placeholders::_2);
auto on_service_offline = std::bind (&ServiceManager::onServiceOffline, _mm_channels.get (), std::placeholders::_1, std::placeholders::_2);
_service_discoverer = std::make_shared <Discovery>(reg_host, base_service, on_service_online, on_service_offline);
}
void make_rpc_server (uint16_t port, int32_t timeout, uint8_t num_threads) {
_rpc_server = std::make_shared <brpc::Server>();
FriendServiceImpl* service = new FriendServiceImpl (_es_client, _mysql_client, _mm_channels, _user_service_name, _message_service_name);
if (_rpc_server->AddService (service, brpc::ServiceOwnership::SERVER_OWNS_SERVICE) != 0 ) {
LOG_ERROR ("添加好友服务到 RPC 服务器失败" );
abort ();
}
brpc::ServerOptions options;
options.idle_timeout_sec = timeout;
options.num_threads = num_threads;
if (_rpc_server->Start (port, &options) != 0 ) {
LOG_ERROR ("RPC 服务器启动失败(端口:{})" , port);
abort ();
}
}
void make_registry_object (const std::string& reg_host, const std::string& service_name, const std::string& access_host) {
_registry_client = std::make_shared <Registry>(reg_host);
_registry_client->registry (service_name, access_host);
}
FriendServer::ptr build () {
if (!_es_client || !_mysql_client || !_rpc_server) {
LOG_ERROR ("服务依赖未初始化完成" );
abort ();
}
return std::make_shared <FriendServer>(_service_discoverer, _registry_client, _es_client, _mysql_client, _rpc_server);
}
private :
Registry::ptr _registry_client;
std::shared_ptr<elasticlient::Client> _es_client;
std::shared_ptr<odb::core::database> _mysql_client;
ServiceManager::ptr _mm_channels;
Discovery::ptr _service_discoverer;
std::shared_ptr<brpc::Server> _rpc_server;
};
2. 部署入口:main 函数解析配置与启动 int main (int argc, char * argv[]) {
google::ParseCommandLineFlags (&argc, &argv, true );
zrt::init_logger (FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level);
zrt::FriendServerBuilder fsb;
fsb.make_es_object ({FLAGS_es_host});
fsb.make_mysql_object (FLAGS_mysql_user, FLAGS_mysql_pswd, FLAGS_mysql_host,
FLAGS_mysql_db, FLAGS_mysql_cset, FLAGS_mysql_port, FLAGS_mysql_pool_count);
fsb.make_discovery_object (FLAGS_registry_host, FLAGS_base_service, FLAGS_user_service, FLAGS_message_service);
fsb.make_rpc_server (FLAGS_listen_port, FLAGS_rpc_timeout, FLAGS_rpc_threads);
fsb.make_registry_object (FLAGS_registry_host, FLAGS_base_service + FLAGS_instance_name, FLAGS_access_host);
auto server = fsb.build ();
server->start ();
return 0 ;
}
FLAGS_mysql_pool_count:MySQL 连接池大小(建议设为 CPU 核心数的 2~4 倍);
FLAGS_rpc_threads:brpc IO 线程数(通常设为 CPU 核心数);
FLAGS_access_host:服务对外访问地址(需与网关在同一网络)。
六、技术亮点与扩展方向
1. 核心技术亮点
解耦设计 :通过 ServiceManager 管理 RPC 信道,服务地址变化只需更新 Etcd;数据层通过 ODB 封装,更换数据库不影响业务逻辑。
数据一致性保障 :ODB 事务确保关键操作的原子性;UUID 确保分布式环境下的 ID 唯一性。
性能优化 :批量 RPC 调用减少网络开销;ES 搜索过滤无效结果;MySQL 索引优化查询性能。
高可用设计 :MySQL 连接池避免频繁连接;Etcd 实现服务动态发现与故障切换;spdlog 提供详细日志监控。
2. 后续扩展方向
好友备注功能 :在 relation 表中添加 remark 字段;
黑名单功能 :新增 blacklist 表,支持屏蔽非好友消息;
会话权限管理 :群聊添加'管理员''禁言'功能,在 chat_session_member 表中添加 role 字段;
数据分片 :用户量增大时,按 user_id 对 MySQL 表进行分片,避免单表数据量过大。
七、总结 好友管理子服务作为 IM 系统的核心组件,通过'Proto 接口定义规范通信、ODB 映射隔离数据层、ServiceManager 解耦服务协作、Builder 模式简化部署',实现了一个高可用、可扩展的微服务。其设计思路不仅适用于 IM 系统,也可复用在社交、电商等需要'关系管理'的业务场景中。该服务在满足业务需求的同时,通过解耦与高可用设计确保了系统的稳定性与可维护性,为后续扩容与迭代奠定了坚实基础。
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online