ET服务器框架学习笔记(十八)
文章目录
前言
本篇主要梳理ET服务器框架中的数据库相关组件。ET服务器框架所用的数据库为MongoDB,内部库包含了很多好用的东西,比如Bson序列化,自动排序等,非常适合作为游戏数据库使用。
一、DBTaskQueue
这个类需要用于管理所有DB任务相关的队列。
1.DBTask
抽象类,所有DB任务的基类,具有一个Run抽象方法,带有的返回值是ETTASK,可以异步执行。
2.DBTaskQueue
- Queue queue:用于管理DBTask的队列,先进先出。由于ET框架使用的是单线程,所以需要使用这种方式对DB任务进行处理。
- Add(DBTask task):往队列里面增加一个task
- ETTask Get():从队列里面获取一个task.
备注:
1.当使用ADD时,会先判断一下this.tcs是否为空,如果不为空,那么说明有其他地方在queue队列为空时get一个任务,get为一个异步操作,如果队列为空,那么返回一个本实例tcs的ETTASK,让get的函数异步等待。
2.当后续有任务add时,会判断本实例tcs是否为空,不为空则有地方在等待get一个任务。那么直接将新增的task设置为tcs的结果,这样异步get会被唤醒,获取最新的Task。
这里不得不说一句,ETTASK真好用,如果按照往常的写法,要写好几个回调函数,而且需要传来传去。
3.DBTaskQueue的扩展StartAsync方法
在DBTaskQueue组件添加时,awake方法中调用。主要逻辑就是开启循环,然后内部使用Get方法获取DBTask,然后调用task的Run进行处理。这里并没有使用定时器进行定时调用,那么意味着,如果有多条数据库任务,他将优先执行。
while (true)
{
if (self.InstanceId != instanceId)
{
return;
}
DBTask task = await self.Get();
try
{
await task.Run();
}
catch (Exception e)
{
Log.Error(e);
}
task.Dispose();
}
二、DBComponent
DBComponent用于处理所有任务队列。
1.MongoClient
这个类是由Mongo库提供的一个连接MongoDB的类,使用起来非常方便,具体使用方式请看:
https://www.cnblogs.com/axel10/p/8459996.html
2.IMongoDatabase database
用于获取当前在Mongo中正在使用的数据库,后续的数据库操作基本上都在这个类上执行。MongoClient.GetDatabase(config.DBName)中获取这个类的引用。
3.List tasks
任务队列的列表,ET中一共定有32条DBTaskQueue,如果数量不够的话,有些数据库操作耗时很长,这样其他数据库任务会等待太久才能执行。而数据库本身是多线程的操作,所以可以将任务分为32条,防止全部等待某一条操作耗时太久。
4.DBComponent中的方法
- IMongoCollection GetCollection(string name):用于在数据中,通过名字获取某一类集合的所有数据操作对象。
- Add(ComponentWithId component, string collectionName = “”):将某个component实例,存入到数据库集合中。如果没有集合名,则以实例类型名作为集合名
- AddBatch(List components, string collectionName),批量存入实例。
- Get(string collectionName, long id):获取某个集合中某个id的component实例
- GetBatch(string collectionName, List idList):批量获取
- GetJson(string collectionName, string json):通过JSON字串查询条件获取实例集合。
上面所有方式,内部都是通过构造一个对应的DBTask并将其根据ID取余的方式分配到不同的DBQueue中,下面对这些不同的DBTASK分别熟悉。
5.DBSaveTask
保存一个实例数据到数据库,对应了上面的Add方法
核心方法:await dbComponent.GetCollection(this.CollectionName).ReplaceOneAsync(s => s.Id == this.Component.Id, this.Component, new UpdateOptions {IsUpsert = true});
获取DBComponent获取集合实例,然后调用API:ReplaceOneAsync,设置好参数与内容即可。
DBSaveBatchTask与之类似,多了一个For循环
6.DBQueryTask
查询数据库一个实例。
核心方法:IAsyncCursor<ComponentWithId> cursor = await dbComponent.GetCollection(this.CollectionName).FindAsync((s) => s.Id == this.Id); ComponentWithId component = await cursor.FirstOrDefaultAsync();
首先调用API-FindAsync获取一个数据游标,然后调用第二个API-FirstOrDefaultAsync获取真正的数据类。
DBQueryBatchTask与之类似
7.DBQueryJsonTask
通过JSON字符串,传递一个带条件的查询,并返回结果。
核心方法:
FilterDefinition<ComponentWithId> filterDefinition = new JsonFilterDefinition<ComponentWithId>(this.Json);
IAsyncCursor<ComponentWithId> cursor = await dbComponent.GetCollection(this.CollectionName).FindAsync(filterDefinition);
List<ComponentWithId> components = await cursor.ToListAsync();
先通过JSon字符串构造一个FilterDefinition过滤定义,然后调用API-FindAsync将过滤定义传进去,获取数据集合游标,再通过游标的.ToListAsync()获取到一个数据实例List。
以上就是所有DBComponent的内容,核心就是对数据库的增删改查,有人可能疑问怎么没有改和删除。其实都可以包含在Add中,调用的ReplaceOneAsync即可保存和修改一个数据库中的值。
三、DBProxyComponent
上面的都是关于DBComponent怎么操作数据库的操作,下面熟悉下DBProxyComponent,这类是封装了所有数据库操作的类,便于其他服务模块直接调用,内部原理是给DB服务模块,发送Session,DB服务模块收到对应的协议,进行各种Handle中,调用DBComponent进行处理实现。
1.保存
- DBProxyComponent的Save方法
方法比较简单,就是生成一个DBSaveRequest,通过DB服务模块的地址,获取一个与之连接Session,然后发送出去。
Session session = Game.Scene.GetComponent<NetInnerComponent>().Get(self.dbAddress);
await session.Call(new DBSaveRequest { Component = component });
- DBSaveRequestHandler:对应收到协议的处理方法,
DBComponent dbComponent = Game.Scene.GetComponent<DBComponent>();
if (string.IsNullOrEmpty(request.CollectionName))
{
request.CollectionName = request.Component.GetType().Name;
}
await dbComponent.Add(request.Component, request.CollectionName);
reply();
SaveBatch就不过多分析了,与上面类似,增加一个循环处理。
还封装了带Session取消的Save,也不过多解释了,详细可以看Session相关的方法。
- SaveLog:这个方法比较特别,是存入日志的方法,使用了自己定义的Log数据集合名,那么所有相关的东西都会存入到日志集合中。
2.查询
- Query
string key = typeof (T).Name + id;
ETTaskCompletionSource<T> tcs = new ETTaskCompletionSource<T>();
if (self.TcsQueue.ContainsKey(key))
{
self.TcsQueue.Add(key, tcs);
return tcs.Task;
}
self.TcsQueue.Add(key, tcs);
self.QueryInner<T>(id, key).Coroutine();
return tcs.Task;
QueryInner方法内容:
try
{
Session session = Game.Scene.GetComponent<NetInnerComponent>().Get(self.dbAddress);
DBQueryResponse dbQueryResponse = (DBQueryResponse)await session.Call(new DBQueryRequest { CollectionName = typeof(T).Name, Id = id });
T result = (T)dbQueryResponse.Component;
object[] tcss = self.TcsQueue.GetAll(key);
self.TcsQueue.Remove(key);
foreach (ETTaskCompletionSource<T> tcs in tcss)
{
tcs.SetResult(result);
}
}
catch (Exception e)
{
object[] tcss = self.TcsQueue.GetAll(key);
self.TcsQueue.Remove(key);
foreach (ETTaskCompletionSource<T> tcs in tcss)
{
tcs.SetException(e);
}
}
查询使用了一点节省性能的技巧,那就是保存了一个查询的MultiMap<string, object> TcsQueue。当发起一个对某个ID的查询时,又发起了另外一个相同的查询(当结果还没返回时),这时会进入这个TcsQueue,如果服务器返回结果,那么可以同时返回给这两个查询,而不用再去找服务器查询一次。
查询多个,以及带表达式查询,也比较类似,也不详细说明了。
总结
这篇简单梳理了ET中有关服务器框架的内容,下一篇将梳理下ET中HTTP相关内容,最后会使用一个实例贯穿这两块内容的实际用法。