数据存取
基础:
GE的核心是一个处理原始二进制文件的键值存储。您可以将一个二进制数保存到由指定的键标识的GE键-值存储中,然后将其加载回程序中。密钥是64位整数;而引用GE数据对象(或GE术语中的单元格)的唯一本地方法就是通过这样一个键。但是,其他类型的键(如字符串键)可以通过将键散列到64位整数来轻松实现。注意,每个键最多只能与一个二进制数值关联;用同一个键保存两个二进制数值会导致一个被另一个覆盖。我们还将把键的概念称为id。
GE支持具有原子性的单元上的高性能并发键值运算符。内置的原子单元操作符包括:AddCell、SaveCell、LoadCell和RemoveCell。使用特定键访问单元格是序列化的;每个人都按照一定的顺序观察一个作家所做的改变。
GE的耐久性是可选的。在编写单元格之前,我们可以选择是否向本地持久存储提交预写日志(WAL)WAL保证了耐久性,但也带来了一些性能损失。明智的使用它。
数据存取模式:
GE提供了多种数据访问模式。在决定使用这里介绍的一种数据访问方法之前,我们应该权衡便利和性能之间的权衡。
内置键值存储接口:
最方便的方法是使用内置的键-值存储接口,比如SaveCell和LoadCell。在GE中,这些接口是在Trinity Global。用于分别在本地或集群上访问数据的CloudStorage。
内置接口将单元格视为二进制数。在TSL中定义了自己的单元格类型之后,其他类型的键-值存储接口将绑定在Trinity.Global.LocalStorage Triity.Global.CloudStorage.TSL生成的接口采用LoadMyType和SaveMyType的形式。例如,如果我们在TSL中定义一种单元格类型如下:
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
{