数据存取

基础:html

GE的核心是一个处理原始二进制文件的键值存储。您能够将一个二进制数保存到由指定的键标识的GE键-值存储中,而后将其加载回程序中。密钥是64位整数;而引用GE数据对象(或GE术语中的单元格)的惟一本地方法就是经过这样一个键。可是,其余类型的键(如字符串键)能够经过将键散列到64位整数来轻松实现。注意,每一个键最多只能与一个二进制数值关联;用同一个键保存两个二进制数值会致使一个被另外一个覆盖。咱们还将把键的概念称为id。node

GE支持具备原子性的单元上的高性能并发键值运算符。内置的原子单元操做符包括:AddCell、SaveCell、LoadCell和RemoveCell。使用特定键访问单元格是序列化的;每一个人都按照必定的顺序观察一个做家所作的改变。c#

GE的耐久性是可选的。在编写单元格以前,咱们能够选择是否向本地持久存储提交预写日志(WAL)WAL保证了耐久性,但也带来了一些性能损失。明智的使用它。缓存

数据存取模式:安全

GE提供了多种数据访问模式。在决定使用这里介绍的一种数据访问方法以前,咱们应该权衡便利和性能之间的权衡。服务器

内置键值存储接口:网络

最方便的方法是使用内置的键-值存储接口,好比SaveCell和LoadCell。在GE中,这些接口是在Trinity Global。用于分别在本地或集群上访问数据的CloudStorage。并发

内置接口将单元格视为二进制数。在TSL中定义了本身的单元格类型以后,其余类型的键-值存储接口将绑定在Trinity.Global.LocalStorage Triity.Global.CloudStorage.TSL生成的接口采用LoadMyType和SaveMyType的形式。例如,若是咱们在TSL中定义一种单元格类型以下:ide

cell GraphNode
{
[Index]
   string       Name;
   float        Value;
   List<CellId> Neighbors;
}
性能

两个新的接口LoadGraphNode和SaveGraphNode将绑定到LocalStorage和CloudStorage。为这些接口提供了多个方法重载。咱们能够这样写:

var root = Global.CloudStorage.LoadGraphNode(123); //123 as the cell id
var sum  = 0.0f;

foreach(var neighbor in root.
Neighbors)

{
    sum += Global.CloudStorage.LoadGraphNode(neighbor).Value;
}

远程选择性数据访问:

GE支持服务器端计算。这意味着咱们不须要将全部相关的单元放到网络上,而后在本地对他们应用操做。咱们 经过网络传递咱们感兴趣的信息。为此,咱们为客户机-服务器通讯定义了一个自定义协议。如今,咱们能够向服务器发送用户定义的请求,以得到所需的结果,而不是发送单元格加载/保存操做。例如,我了得到一组GraphNode值的和,咱们能够定义这样的协议,而不是将他们加载到客户端:

struct RequestMessage
{
    List<CellId> NodeSet;
}

struct ResponseMessage
{
    float Result;
}

protocol CalculateSum
{
    Type     : Syn;
    Request  : RequestMessage;
    Response : ResponseMessage;
}

在客户端,咱们能够经过:

var sum = 0.0f;

for(int serverId = 0; serverId < Global.ServerCount; serverId++)
{
   using(var request  = new RequestMessageWriter(new List<long>(){1,2,3}))
   {
      using(var response = Global.CloudStorage.CalculateSumToMyServer(serverId, request))
      {
         var sum += response.Result;
      }
   }
}

服务器端,逻辑实现以下:

public override void CalculateSumHandler(
    RequestMessageReader  request,
    ResponseMessageWriter response)
{
    response.Result = .0f;
    foreach(var nodeId in request.NodeSet)
    {
        response.Result += Global.LocalStorage.LoadGraphNode(nodeId).Value;
    }
}

单元访问器:

实际上,咱们能够经过键-值存储接口执行任何数据访问任务。但很快咱们会注意到,即便咱们只想访问单个数据字段,整个单元格也须要加载。在上面显示的代码片断中,对某个字段访问单元格。对于每一个单元,GE首先在内存存储中肯定其内存位置。而后它调用运行时来分配单元格对象,并将单元格内容从存储复制到对象。而后,从对象中读出该字段病将其输入到外部计算循环中。

单元格修饰是一个棘手的问题。只需修改单元格的一小部分就须要三个单元格操做:加载单元格、修改单元格和保存 单元格。在这个过程当中产生的内存 副本浪费了大量的内存和网络带宽。并且,即便每一个单元操做都是原子操做,单元修改做为一个总体也不是原子操做,由于不能保证三个单独的单元操做做为一个总体执行。

在了解了使用键-值存储接口形成的问题以后,咱们如今给出了解决方法。在GE中,咱们经过一种称为数据访问器的机制来解决上述问题。对于TSL脚本中定义的任何单元结构,TSL编译器将自动生成单元访问器。访问器不拥有任何数据。相反,全部字段都做为c#属性提供;对这些属性的操做将转换为对底层单元格二进制的就低内存操做。使服务器逻辑重写为:

public override void ComputeAverageHandler(
    RequestMessageReader  request,
    ResponseMessageWriter response)
{
    response.Result = .0f;
    foreach(var nodeId in request.NodeSet)
    {
        using( var neighbor = Global.LocalStorage.UseGraphNode(nodeId) )
        {
            response.Result += neighbor.Value;
        }
    }
}

在这个新版本中,访问相邻节点的值,将访问4个字节的内存。有关单元格访问器的更多信息,请参阅:个人博客-TSL 访问器

只要可能,使用单元格访问器而不是键-值存储接口来访问数据。

单元访问设置:

咱们能够为大多数单元格访问接口提供单元格访问选项。根据单元格接口,能够应用下面列出的一个或多个选项。

public enum CellAccessOptions
{
   // Throws an exception when a cell is not found.
   ThrowExceptionOnCellNotFound,

   // Returns null when a cell is not found.
   ReturnNullOnCellNotFound,

   // Creates a new cell when a cell is not found.
   CreateNewOnCellNotFound,

// Specifies that write-ahead-log should be performed with strong durability.
   StrongLogAhead,

   // Specifies that write-ahead-log should be performed with weak durability.
   // This option brings better performance, but under certain circumstances
   // the log may fail to be persistent, for example, during a power outage
   // or equipment failure.
   WeakLogAhead
}

单元访问选项用于控制单元操做的行为。例如,咱们能够为可能改变单元格状态的单元格访问接口制定write-ahead-log级别。

 long cellId = 1;

// MyCell is a cell type defined in a TSL project
Global.LocalStorage.SaveMyCell(CellAccessOptions.StrongLogAhead, cellId, ...);

Global.LocalStorage.RemoveCell(CellAccessOptions.WeakLogAhead, cellId);

using (var cell = Global.LocalStorage.UseMyCell(cellId, CellAccessOptions.ReturnNullOnCellNotFound))

 {
    // Do something here
}

using (var cell = Global.LocalStorage.UseMyCell(3, CellAccessOptions.CreateNewOnCellNotFound))
{
    // Do something here
}

单元选择器:

GE提供了用于迭代存储在本地内存存储中的单元格的枚举数。注意,目前不支持云内存存储上的枚举。

对于TSL中定义的每一个单元格类型,TSL编译器在本地内存存储上生成一个选择器接口:Global.LocalStorage.MyCellType_Selector。顾名思义,选择器接口选择给定类型的全部单元,并返回IEnumerable<MyCellType>集合。而后,咱们可使用foreach便利集合:

 foreach( var node in Global.LocalStorage.GraphNode_Selector() )
{
    //Do something...
}

支持单元对象和单元访问器。这里是访问器对等物:

foreach( var accessor in Global.LocalStorage.GraphNode_Accessor_Selector() )
{
    //Do something...
}

经过选择器枚举单元格是线程安全的;多个枚举能够同时执行。可是,不容许缓存访问器,由于访问器对象将在枚举期间被重用泳衣指向其余单元。所以,如下代码将致使数据损坏或系统崩溃:

// Attempting to cache cell accessors
List<GraphNode_Accessor> accessor_cache = new List<GraphNode_Accessor>();
foreach( var accessor in Global.LocalStorage.GraphNode_Accessor_Selector() )
{
    accessor_cache.Add(accessor);
}
// It will crash on visiting the cached accessors!
var f = accessor_cache.Sum(accessor.AdditionalData);

LINQ:

选择器实现了IEnumerable接口,所以他们支持LINQ/PLINQ查询。若是为某些单元格字段指定了子字符串索引属性,那么一些查询能够直接利用反向索引来加速查询处理。使用LINQ实现通用数据查询逻辑很方便,例如:

 var results = from node in Global.LocalStorage.GraphNode_Accessor_Selector()
               where node.name.Contains("Alice")
               select node.Value;
var min     = results.Min();

在本例中,node.name.Contains子句被转换为子字符串查询。而后结果投影到一个浮点数列表中,而后使用内置的LINQ接口Min()进行聚合。

详细介绍参考:Language-Integrated Query

子串查询:

若是在TSL中的单元格字段中指定了子字符串索引属性,将为指定的单元格字段生成一组子字符串查询接口。子字符串查询接口接受一个或多个查询字符串,并返回匹配的单元格id列表。

详细介绍参考:substring query

相关文章
相关标签/搜索