DBCacheComponent学习笔记
请大家关注我的微博:@NormanLin_BadPixel坏像素
首先它订阅了Awake事件,会在添加该组件的时候调用Awake方法。
我们来看看作者对这个组件的定义是什么。
/// <summary>
/// 用来缓存数据
/// </summary>
public class DBCacheComponent : Component
缓存什么数据呢?
public Dictionary<string, Dictionary<long, Component>> cache = new Dictionary<string, Dictionary<long, Component>>();
从这里我们大概能猜到,缓存的是组件信息。这个字典有点复杂,字典内又套了一个字典,我们看看到底是怎么一个存法。
public void AddToCache(Component disposer, string collectionName = "")
{
if (string.IsNullOrEmpty(collectionName))
{
collectionName = disposer.GetType().Name;
}
Dictionary<long, Component> collection;
if (!this.cache.TryGetValue(collectionName, out collection))
{
collection = new Dictionary<long, Component>();
this.cache.Add(collectionName, collection);
}
collection[disposer.Id] = disposer;
}
这是唯一对cache进行添加操作的方法。我们看到,传进来的参数是一个Component disposer跟字符串 collectionName。这个collectionName到底是什么我们暂且不知,我们可以猜,可能是对组件的分类,如果传入的collectionName为空,则默认以组件的类名来分类。然后会根据组件的Id储存入内字典。
我们来举一个例子更形象的解释这一结构。
我们把每一种Component看成不同书名的书,同样书名的书会有好几本。所以我们用Dictionary
public Component GetFromCache(string collectionName, long id)
{
Dictionary<long, Component> d;
if (!this.cache.TryGetValue(collectionName, out d))
{
return null;
}
Component result;
if (!d.TryGetValue(id, out result))
{
return null;
}
return result;
}
除了cache这个属性,我们还有tasks。
public const int taskCount = 32;
public List<DBTaskQueue> tasks = new List<DBTaskQueue>(taskCount);
并且,在Awake方法里就对tasks进行了实例化。我们来瞅瞅DBTaskQueue是什么。
DBTaskQueue
在类声明中使用 sealed 修饰符可防止继承此类
public Queue<DBTask> queue = new Queue<DBTask>();
简直不要太直白了,就是管理DBTask的一个队列。
不过我们看后面的Add跟Get方法,还是能看出点东西的。首先,在试图从这里获取DBTask的时候,如果缓存队列有,则返回出队列的对象,否则,则创建一个TaskCompletionSource,等待缓存队列有对象进入。在等待状态,如果我们往缓存队列里堆新的对象的时候,则会直接通过TaskCompletionSource.SetResult把想入队的DBTask直接返回。如果不在等待状态,则直接把DBTask入队。
我们看看订阅的Start方法里面又是什么。
public override async void Start(DBTaskQueue self)
{
while (true)
{
if (self.IsDisposed)
{
return;
}
DBTask task = await self.Get();
try
{
await task.Run();
task.Dispose();
}
catch (Exception e)
{
Log.Error(e.ToString());
}
}
}
一个异步的方法。我们看到,当没有新的DBTask进队的时候,DBTask task = await self.Get(); 会一直处于等待状态。如果有新的DBTask了,则执行DBTask的任务,并且释放DBTask。
DBTask
public abstract class DBTask : Component
{
public abstract Task Run();
}
DBCacheComponent
之前我们提到的AddToCache跟GetFromCache,都只是针对缓存的操作,那我们怎么对数据库操作呢?着就要用到我们的DBTask了。
public Task<bool> Add(Component disposer, string collectionName = "")
{
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
this.AddToCache(disposer, collectionName);
if (string.IsNullOrEmpty(collectionName))
{
collectionName = disposer.GetType().Name;
}
DBSaveTask task = ComponentFactory.CreateWithId<DBSaveTask, Component, string, TaskCompletionSource<bool>>(disposer.Id, disposer, collectionName, tcs);
this.tasks[(int)((ulong)task.Id % taskCount)].Add(task);
return tcs.Task;
}
前面的都很好理解,需要注意的是,在存入数据库时会在缓存中也存一份。我们看到,DBTask会根据Id分配给不同的DBTaskQueue,为什么要这么做呢?我的理解是,可能数据库的操作量比较大,如果放在一个线程里面执行,可能比较耗时,所以这里准备了32个DBTaskQueue来执行。
public Task<bool> AddBatch(List<Component> disposers, string collectionName)
{
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
DBSaveBatchTask task = ComponentFactory.Create<DBSaveBatchTask, List<Component>, string, TaskCompletionSource<bool>>(disposers, collectionName, tcs);
this.tasks[(int)((ulong)task.Id % taskCount)].Add(task);
return tcs.Task;
}
这是添加一批数据的方法,需要注意的是,这里并没有存入缓存。
public Task<Component> Get(string collectionName, long id)
{
Component disposer = GetFromCache(collectionName, id);
if (disposer != null)
{
return Task.FromResult(disposer);
}
TaskCompletionSource<Component> tcs = new TaskCompletionSource<Component>();
DBQueryTask dbQueryTask = ComponentFactory.CreateWithId<DBQueryTask, string, TaskCompletionSource<Component>>(id, collectionName, tcs);
this.tasks[(int)((ulong)id % taskCount)].Add(dbQueryTask);
return tcs.Task;
}
如果缓存中有我们想要的数据,则直接从缓存中获取,否则,尝试从数据库中获取。
public Task<List<Component>> GetBatch(string collectionName, List<long> idList)
{
List <Component> disposers = new List<Component>();
bool isAllInCache = true;
foreach (long id in idList)
{
Component disposer = this.GetFromCache(collectionName, id);
if (disposer == null)
{
isAllInCache = false;
break;
}
disposers.Add(disposer);
}
if (isAllInCache)
{
return Task.FromResult(disposers);
}
TaskCompletionSource<List<Component>> tcs = new TaskCompletionSource<List<Component>>();
DBQueryBatchTask dbQueryBatchTask = ComponentFactory.Create<DBQueryBatchTask, List<long>, string, TaskCompletionSource<List<Component>>>(idList, collectionName, tcs);
this.tasks[(int)((ulong)dbQueryBatchTask.Id % taskCount)].Add(dbQueryBatchTask);
return tcs.Task;
}
这个方法,是获取一批数据,传入的参数是索要获取的所有数据的ID。如果所有的数据都在缓存中,则直接从缓存中收集并返回,否则,从数据库中获取。关于这个方法,我有一个想法,为什么不先统计缓存中已经有的,再从数据库中加载缓存中没有的,最后加在一起呢?个人理解,可能获取的数据有顺序的要求?
这里,我们看一下那些DBTask具体是怎么操作的。
DBSaveTask
我们主要看Run方法里面的代码。
await dbComponent.GetCollection(this.CollectionName).ReplaceOneAsync(s => s.Id == this.Disposer.Id, this.Disposer, new UpdateOptions {IsUpsert = true});
这里有很多是MongoDB C# .Net库里面的方法。
MongoDB官方文档(可以查找自己想看的)
中文教程(别人的博客)
这里说明一下UpdateOptions
BypassDocumentValidation:Gets or sets a value indicating whether to bypass document validation.(是否绕过文档验证获取或设置一个值。)
IsUpsert:Gets or sets a value indicating whether to insert the document if it doesn’t already exist.(获取或设置一个值,该值指示在文档不存在该值时是否插入该文档(创建值)。)
翻译来自谷歌翻译~
DBSaveBatchTask
我们看到,其实它只是遍历了List,然后一个一个储存。同样是用了IMongoCollection.ReplaceOneAsync。
DBQueryTask
在Run方法里,又对DBCacheComponent查询了一次是否存在在缓存中。大家应该记得,我们在创建这个DBTask之前,就已经查询了一遍了。这里是不是重复代码呢?
disposer = await dbComponent.GetCollection(this.CollectionName).FindAsync((s) => s.Id == this.Id).Result.FirstOrDefaultAsync();
IMongoCollection.FindAsync官方文档
这就是一个数据库查询的方法。
DBQueryBatchTask
try
{
// 执行查询数据库任务
foreach (long id in IdList)
{
Component disposer = dbCacheComponent.GetFromCache(this.CollectionName, id);
if (disposer == null)
{
disposer = await dbComponent.GetCollection(this.CollectionName).FindAsync((s) => s.Id == id).Result.FirstOrDefaultAsync();
dbCacheComponent.AddToCache(disposer);
}
if (disposer == null)
{
continue;
}
result.Add(disposer);
}
this.Tcs.SetResult(result);
}
我们看到,在批量查询的时候,其实也是遍历一个一个查询,首先会尝试从缓存中获取,如果没有,则尝试从数据库里获取,如果还是没有,则跳过。
DBQueryJsonTask
我们看到,除了以名称和Id为参数的数据库操作方法,还有一个是以Json为参数的操作方法。我们之前貌似没有讲过Json吧?
Json是一个很有用的东西,如果不了解的一定要去了解一下。
// 执行查询数据库任务
FilterDefinition<Component> filterDefinition = new JsonFilterDefinition<Component>(this.Json);
List<Component> disposers = await dbComponent.GetCollection(this.CollectionName).FindAsync(filterDefinition).Result.ToListAsync();
FilterDefinition: defines a filter. It is implicity convertible from both a JSON string as well as a BsonDocument.