GateServer

GateServer

本节进行网关服务器的线程模型的分析

基本地位

​ 该网关服务器的逻辑目标是实现一个服务器集群中的HTTP服务器,用于接受在用户登录时候发来的对应的请求,为了保证对应的登录服务能够正确的被提供,所以需要保证对应的服务的请求不会被丢失。但是由于我本身对于QUIC这种协议不是很熟悉,而且C++中不存在这种的框架,所以不考虑使用这种协议来进行对应的实现。最终,我们敲定的是基于HTTP协议提供可靠登录服务验证的服务器。

处理流程

​ 对于GateServer,其对应的每次HTTP请求都会触发一次监听套接字的监听,对应的会存在一个封装的HttpConnection结构来进行本次连接的处理。下面直接给出一个实际的简单的流程图。

image-20250625231520092

​ 在程序的一开始,我们开辟出一个独立的线程进行对应的监听,该线程实际上即为我们的主线程,主线程上运行我们的acceptor来进行对应的连接请求的监听

​ 为了提高我们的并发能力,对于每个HTTP请求处理,我们都会存在一个IO_Context进行管理,避免所有的处理都跑在主线程上导致性能不高。而这个IO_Context是由一个池子来统一提供的,这种避免对应的创建销毁开销的池化思想很常见了,不再赘诉。主要来关注一下我们对应的下面的逻辑处理与整个的线程模型。

逻辑处理

​ 对于每个IO_Context,其跑在一个独立的线程上,其管理着对应的一系列的建立起来的HTTP短连接,对应的所有的连接都是在本个上下文中串行的执行的。通过这中串行的逻辑,我们省去了在IO_Context内部去调度对应的多个服务的并发的复杂度,同时,由于多个IO_Context本身线程本身物理隔离。同时,我们在设计上避免了对应的多个IO_Context的通信,这样能够避免我们需要关注对应的多个IO_Context间的任务调度竞态问题。

​ 接下来考虑一下在一个服务内部的一个任务的一个实际处理情况。首先,我们确认一下一个任务在外面的这里的地位是什么,这里的一个任务是实现一个功能的验证的一个单位。就比如,存在对应于**/getVerifyCide的回调,其中会涉及数据库的数据的查询,Redis职工共享数据的查询,通过RPC向其他的服务器通信的服务。总总这些,再组合一些业务逻辑,实现了一个完整的任务,提供起了一个验证码服务的提供,这个即使一个基本的任务**。

​ 在一个任务中,目前的设计是一个串行处理的,这是基于目前我的能力和对应的业务一致性决定的。即,要想实现一个注册服务,我需要先拉取对应的数据库信息,判断是否存在等信息,在操作完成之后(我们此处推演一次全部的正确流程,错误流程自行源码分析),再来进行对应的Redis的操作,存在一个MySQLService->RedisService的先后依赖关系。在某种程度上,这种同步操作可能会导致一些性能问题,但是,怎么理解这种场景呢?

性能的陷阱

​ 在我一开始实现这个服务的时候,我其实存在一些很多的考虑。在之前的书上,我其实看到了对应的在同步操作中去进行对应的数据库,grpc等连接IO操作的代价,所以我会在潜意识里想要去优化掉对应的同步操作为一个异步的逻辑。但是在一个简单的思考过后(或许并不简单),我推翻了进行重构为异步的想法。其中有多方面因素,其中最重要的是俩个

1.将同步优化为异步的复杂

​ 异步操作确实能够带来很多的便捷性,但是与之同时到来的,是为了实现这种便捷性本身所需要的复杂性,对于现有的ASIO中的**async_等的逻辑。我们值所以倾向于在对应的网络中使用它,首先就是因为其给我们封装了一系列的异步处理逻辑,其能够保证大多数情况下对应的异步回调能够正常被执行,其所带来的心智负担*并不是很大。

​ 其中的重点是心智负担这一词。如果我们想要在业务层实现一个自己的异步逻辑,至少对于现在的我来说,我是无法想象的。如果以类似于Asio的异步逻辑的写法来实现的话,其会造成一种多层嵌套的局面,其对应的实际的逻辑会变得支离破碎。这是由于异步本身的性质决定的,异步想要正常执行,那么其就需要保留一个异步注册和调用时候的上下文状态,在框架级别的使用中,ASIO给我们封装了这些的操作,使得我们不用关心。但是这里,如果我们想要把一个任务中的一个数据库处理操作,一个grpc调用操作都给封装为一个异步的,其所引入的状态机模型将会指数级别的提升。这对于现在的我来说,这是无法接受的,无论是编码还是理解维护层面。

​ 就比如下面的伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
RegPost("/user_login", [=](auto conn) {
parse_json(conn, [=](Json::Value parsed_json){
// 封装数据、进入异步状态1
mysql_async_check_pwd(email, pwd, [=](bool valid, UserInfo userInfo) {
if (!valid) return send_error(conn, "pwd invalid");

// 异步状态2
grpc_async_get_chat_server(userInfo.uid, [=](Reply reply) {
if (reply.error()) return send_error(conn, "grpc fail");

// 回包
Json::Value root;
root["uid"] = userInfo.uid;
root["token"] = reply.token();
send_json(conn, root);
});
});
});
});

​ 如果按照ASIO的风格来进行对应的异步的优化,在我的个人理解中,其最终的形态大概率会导致一个回调地狱,这对于我之后回来熟悉这一段的逻辑,以及扩展对应的逻辑,都是相当不利的,更重要的是,我根本无法实现在异步条件下安全的该类异步回调函数,而且,即使能够实现,由于业务功能的多样性,我实现的一个异步函数也无法适配所有的场景,对应的复杂度是我当前无法想象的,所以我在这一点中放弃了对应的异步逻辑的优化。

2.优化本身的意义

-------------本文结束 感谢阅读-------------