ET框架---DBCacheComponent学习笔记

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的一个队列。
不过我们看后面的AddGet方法,还是能看出点东西的。首先,在试图从这里获取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

之前我们提到的AddToCacheGetFromCache,都只是针对缓存的操作,那我们怎么对数据库操作呢?着就要用到我们的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,为什么要这么做呢?我的理解是,可能数据库的操作量比较大,如果放在一个线程里面执行,可能比较耗时,所以这里准备了32DBTaskQueue来执行。

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教程W3C

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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值