文章目錄
- 服務器設計思路
- 服務器的設計
- 服務節點:UserServer
- UserServer 的業務流向
- UserServer的大體設計
- 抽象節點:UserService
- 參考文獻
郵箱:2107810343@qq.com
時間:2021/4/18 22:55
開發環境:Ubuntu VS Code
編譯器:g++
編程語言:C++
原始碼下載: GitHub
服務器設計思路
- 整個服務器應該具有高并發、高可用的特點,
- 服務器為了盡量保持一致性,并且由于登錄、注冊、注銷等業務不屬于高需求的業務,應直接與 mysql 互動,而不是 redis
- 為了加快與 mysql 互動的效率,可用引進資料庫連接池(也是自己寫的)
- 為了保持對外介面一致性和便捷性,可用提供一個抽象層
- 服務器與服務物件的通信協議采用 protobuf ,網路協議采用 TCP
服務器的設計
根據服務器的設計思路,我們可以向外提供一個抽象節點 UserService,所有關于登錄、注冊、注銷的業務需求全都引向這里,而真正提供服務的是服務節點 UserServer,它負責與持久層的 mysql 進行互動,并且將自己的資訊注冊到zookeeper 集群上,這個時候如果有需求到 UserService上,它的需求就會被UserService 從zookeeper 中拿出一個UserServer服務節點,向這個節點去分發需求,實作了一個反向代理的負載均衡
服務節點:UserServer
UserServer 的業務流向
UserServer 是整個服務集群中真正提供服務的節點,提供了登錄、注冊、注銷三個功能,當一個業務請求通過 抽象節點 UserService分發到UserServer上的時候,它會先去反序列化請求,得到這個資料是請求哪個服務,然后根據請求型別,先去資料庫連接池獲得一個連接,然后去選擇相應服務,并回傳相應結果,

其對應的代碼主要是在muduo庫的messagecallback函式:
//連接事件回呼函式
void UserServer::msg_callback(const muduo::net::TcpConnectionPtr &conn, muduo::net::Buffer *buffer, muduo::Timestamp stamp)
{
//反序列化由UserService傳過來的序列化資料
string message = buffer->retrieveAllAsString();
ik_UserServer::Request request;
request.ParseFromString(message);
//登錄業務
if (request.type() == "Login")
{
// 反序列化資料得到 id、password
ik_UserServer::LoginRequest login_request;
login_request.ParseFromString(request.request());
int id = login_request.id();
string password = login_request.password();
//執行具體的Login方法,執行成功is_success被設定為true
ik_UserServer::LoginReponse login_response;
if (Login(id, password) == true)
{
login_response.set_is_success(true);
}
else
{
login_response.set_is_success(false);
ik_UserServer::ErrorMsg *msg = login_response.mutable_msg();
msg->set_message("login error");
}
//序列化結果 并將結果回傳
string send = login_response.SerializeAsString();
conn->send(send.c_str(), send.size());
}
else if (request.type() == "Register") //注冊業務
{
ik_UserServer::RegisterRequest register_request;
register_request.ParseFromString(request.request());
string name = register_request.name();
string password = register_request.password();
//cout<<name<<" "<<password<<endl;
ik_UserServer::RegisterResponse register_response;
int id = Register(name, password);
if (id < 0)
{
register_response.set_is_success(false);
}
else
{
register_response.set_is_success(true);
register_response.set_id(id);
}
string send = register_response.SerializeAsString();
conn->send(send.c_str(), send.size());
}
else if (request.type() == "LoginOut") //注銷業務
{
ik_UserServer::LoginOutRequest out_request;
out_request.ParseFromString(request.request());
int id = out_request.id();
LoginOut(id);
}
else //其他業務,這里是沒有,所以向LogServer服務器列印一條日志資訊
{
ik::LogRequest request;
request.set_name("UserServer");
string message = "UserServer:";
message += ip_.c_str();
message += " not have this service!";
request.set_msg(message);
stub_.Log_ERROR(nullptr, &request, nullptr, nullptr);
}
}
UserServer的大體設計
UserServer 大體上還是分為兩層:網路層和業務層,網路層還是基于muduo網路庫構建,通過那老一套的流程很快的搭建一個高性能的網路層,業務層則是首要的就是向zookeeper中去注冊這個節點,一般是在/UserService目錄下,以便抽象節點能夠去發現服務,
注意:
這個注冊在zookeeper的節點應該是臨時節點,保證每個取得的節點都是可用的,但是有個bug,zookeeper那邊如果是上線基本秒更新,但是如果是下線一般會根據你設定的超時時間來判斷,只有超過超時時間才判定這個節點下線了,但是如果超時時間過短,會造成頻繁的與zookeeper-server連接,

具體是在UserServer的建構式和run方法中實作,
UserServer::UserServer(string ip, int port) : stub_(new RpcChannel())
{
ip_ = ip;
port_ = port;
pool_ = Connect_pool::get_instance();
if (nullptr == pool_)
{
ik::LogRequest request;
request.set_name("UserServer");
request.set_msg("create mysql pool error");
stub_.Log_ERROR(nullptr, &request, nullptr, nullptr);
}
}
void UserServer::run()
{
//初始化網路層
muduo::net::InetAddress address(ip_.c_str(), port_);
muduo::net::TcpServer server(&loop_, address, "UserServer");
server.setMessageCallback(bind(&UserServer::msg_callback, this, _1, _2, _3));
server.setConnectionCallback(bind(&UserServer::conn_callback, this, _1));
//注冊zookeeper節點
ZKClient zk_client;
zk_client.start();
string server_path = "/UserService/server";
char buffer[BUFF_SIZE] = {0};
sprintf(buffer, "%s:%d", ip_.c_str(), port_);
if (zk_client.create(server_path, buffer, strlen(buffer), ZOO_EPHEMERAL) == false)
{
ik::LogRequest request;
string name = "UserServer ";
name += buffer;
request.set_name(name);
request.set_msg("zookeeper connect error!");
google::protobuf::Empty response;
stub_.Log_ERROR(nullptr, &request, &response, nullptr);
}
else
{
ik::LogRequest request;
string name = "UserServer ";
name += buffer;
request.set_name(name);
request.set_msg("zookeeper connect!");
google::protobuf::Empty response;
stub_.Log_ERROR(nullptr, &request, &response, nullptr);
}
//開啟事件回圈
server.setThreadNum(4);
server.start();
loop_.loop();
}
抽象節點:UserService
抽象節點基于RPC框架,是整個ByteTalk 系統的提供服務的抽象節點,所有關于登錄、注冊、注銷的業務需求都會在這里得到中轉,可用看作是它管理了一群UserServer節點,每次都從這群節點沖抽取一個可用的,然后給它分派任務并等待它的回傳結果,

主要業務邏輯在各個RPC 方法中,拿一個 Login 出來看看:
void UserService::Login(::google::protobuf::RpcController *controller,
const ::ik_UserService::LoginRequest *request,
::ik_UserService::LoginReponse *response,
::google::protobuf::Closure *done)
{
master_.get_follow();
ik_UserServer::Request login_request;
login_request.set_type("Login");
login_request.set_request(request->SerializeAsString());
string send_str = login_request.SerializeAsString();
//獲取一個服務資訊
int client_fd;
while ((client_fd = master_.get_service()) == -1)
{
//重繪服務串列
master_.get_follow();
sleep(1);
}
//發送資訊
send(client_fd, send_str.c_str(), send_str.size(), 0);
//獲取資訊
char recv_buf[BUFF_SIZE] = {0};
recv(client_fd, recv_buf, BUFF_SIZE, 0);
ik_UserServer::LoginReponse login_response;
login_response.ParseFromString(recv_buf);
response->set_is_success(login_response.is_success());
close(client_fd);
done->Run();
}
參考文獻
[1] 無
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/280329.html
標籤:其他
上一篇:一氣之下,我搶過面試官電腦花10分鐘搭建了MySQL主從架構,面試官蒙了
下一篇:Spark大資料技術與應用
