服务器框架 https://blog.csdn.net/nie2314550441/article/details/105981967
一、DB服介绍
整个服务器(除网站后台)与数据库交互都是通过DB服进行。DB服用于数据库交互和数据缓存,使用sqlserver做为存储,用来保存用户的所有相关数据。
二、服务器启动流程
1、启动准备
- 初始化网络库
- 监听事件注册
2、开始启动
- 创建日志、定时器
- 用户代理服务启动和监听(监听网关服和数据交互)
- 创建DB代理服务(用于存储过程与数据库交互)
- 挂接逻辑事件(用户线程读取网络数据)
- 创建守护中心(与守护中心服连接,用于服务器拉起)
说明:DB代理服务器,创建的时候会创建DBEngine服务
3、启动流程图
三、DB服与其他服务器交互
参考前面
四、类图总览
DB服按功能分可以分为两大块,连接对象和db代理。
五、探DB代理服务器
db代理服务是DB服最重要的模块,用于处理db请求和数据缓存。DB代理服务功能可以分为两部分,一、db请求分发和数据缓存;二、与db交互。下图为DB代理服务类图。
5.1 db请求分发和数据缓存
存储过程功能包含:玩家连接登陆服进行登陆、玩家进入广场服、玩家私有属性加载、玩家登录游戏服、活动数据等等。DB代理在将请求转发给dbengine前加了一层分类处理,用于缓存和校验。请求分类处理分为五类:直通、用户登录、登录广场、登录房间、用户数据。
- 直通:数据直接转发给DBEngine
- 用户登录:进行校验,校验通过之后转发给DBEngine。玩家登录包含登出,登录/登出的是登录服务器。
- 登录广场:进行校验,登录成功之后会创建用户数据,用于记录缓存数据,玩家登出,会执行所有的缓存请求。
- 登录房间:进行校验,交互通过之后转发给DBEngine。
- 用户数据:判断数据是否需要缓存,若配置为缓存先进行缓存。缓存的数据会在定时存盘或者限制存盘时执行缓存中的存储过程;若配置为不用缓存,转发给DBEngine。一般数据的存储过程是不用进行缓存,一般是需要定期存盘的数据需要缓存,例如玩家私有数据。
5.2 DBEngine与db进行交互
DB代理服务器将db请求处理完毕之后,转发给DBEngine进行执行。执行完毕之后通过接口IDBRetSink::OnRet()返回给DB代理服务器分类对象,再转发给对应连接对象,连接对象通过tcp将返回信息发生给对应请求服务器。
六、探DBEngine
db请求最终是通过DBEngine来与数据库进行交互。DBEngine收到db请求,根据请求所在的配置,将其投递到对应的异步队列中,一个异步队列为一个线程,该线程与数据库相连,当队列中用请求按顺序执行请求,执行完毕向上层返回执行结果。
6.1 DBEgine创建
DB代理服务在创建过程中会加载并创建DBEgine,DBEgine创建流程:配置解析,异步队列创建。
6.1.1 配置解析
需要解析的配置有三个《DBDefine.scp》、《DBEngine.scp》、《DBRequest.scp》。
DBDefine.scp:数据类型定义
DBEngine.scp:包含多个数据库列表配置信息,一个数据库列表配置包含连接数据库需要的信息(ip,断开,数据库名,账号、密码)和异步队列配置。
DBRequest.scp:服务器请求执行的存储过程配置
6.1.2 异步队列创建
一个数据库队列里面可以配置多个异步队列类型,一个异步队列类型对应多个队列,每个队列会创建一个线程,这个线程会和数据库进行连接并处理投入到该队列中的db请求。
6.1.3 DBEngine执行db请求
DBEngine执行db请求流程:
1. DBEngine主线程收到db请求
2. 根据db请求的requestID,查找该请求配置的异步队列类型。
3. 在该异步队列类型中选中一个线程,将db请求投递给它
4. 异步队列执行完db请求,将结果返回给DBEngine主线程
5. DBEngine主线程将db请求返回发送给上层
七、探连接对象
DB服收到客户端(中心服、登陆服、广场服、游戏服)连接之后会创建一个DBConnection,用于管理连接的消息接收和发送,DBConnection会创建一个DBDataProxy用于处理db请求转发(发送给DB代理服务)和接收db请求返回并发送给客户端。
DBConnection有多个,一个客户端对应一个。一个DBConnection有一个DBDataProxy。
八、一个存储过程执行流程
以在广场服发起执行存储过程为例,省略了一些中间过程。
九、线程同步问题
9.1 DBEngine主线程和异步队列线程之间同步
6.1.3中涉及到需要同步的地方有四处:
1. 主线程将请求投递给异步队列线程
2. 异步队列线程取出请求,用于执行
3. 异步队列将执行完的结果发送给主线程
4. 主线程将db请求结果发送给上层
这里需要有两处数据需要维持同步,步骤1、2同步的是异步队列中请求对象,步骤 3、4是主线程中返回结果队列。
9.2 同步方法
1.旧版本:在请求对象队列中添加或取出请求对象时会进入临界区域(EnterCriticalSection和LeaveCriticalSection)。
2.新版本:将请求对象队列改成支持多线程添加或取出,向队列中添加或取出数据不是用EnterCriticalSection和LeaveCriticalSection,而是用Interlocked系列函数。
两种方法比较:
当线程试图进入一个关键段,但这个关键段正被另一个线程占用的时候,函数会立即把调用线程切到等待状态。这意味着线程必须从用户模式切到内核模式(大约1000个CPU周期),这个切换的开销非常大。——来源《Windows核心编程》
Interlocked系列函数是锁内存,速度非常快,只需要占用几个CPU周期。
3.进一步优化效率
异步队列中新加两个队列,一个是【主线程调用队列】用于主线程调用,一个是【当前插入队列】用于当前线程添加返回结果。异步队列线程将返回结果都添加到【当前插入队列】,不需要考虑同步问题。当主线程需要获取异步队列返回结果是,将【主线程调用队列】和【当前插入队列】进行翻转,即:【主线程调用队列】变成了【当前插入队列】,【当前插入队列】变成了【主线程调用队列】,并将翻转后的结果发送给主线程,只需要在进行翻转队列的时候进行加锁。