首页 > 技术文章 > stsdb开发指南

foreverme 2013-09-26 16:48 原文

摘自:http://www.iopenworks.com/Products/ProductDetails/DevelopmentGuide?proID=387

多线程问题,请参见线程安全小结

1 STSdb存储引擎

STSdb存储引擎是一个基于瀑布树结构实现的NoSQL数据库和虚拟文件系统。该存储引擎提供两种数据结构——XIndex和XFile,一个存储引擎可以包含多个XIndex表和XFile文件。下面我们看一个简单示例。

 

2 简单示例

(1)打开数据库,创建表

using (StorageEngine engine =newStorageEngine("stsdb4.sys","stsdb4.dat")){XIndex<int,string> table = engine.OpenXIndex<int,string>("table");for(int i =0; i <1000000; i++){
        table[i]= i.ToString();}
 
    table.Flush();
    engine.Commit();}

(2)打开数据库,从表中读取数据

using (StorageEngine engine =newStorageEngine("stsdb4.sys","stsdb4.dat")){XIndex<int,string> table = engine.OpenXIndex<int,string>("table");foreach(var row in table)//table.Forward(), table.Backward(){Console.WriteLine("{0} {1}", row.Key, row.Value);}}

 

3 XIndex

XIndex是一个按序的键/值对存储(表)。我们可以在同一个引擎使用不同的键/记录类型来读取多个普通的表。每一个表并没有列数量的特殊限制。

需要注意的是:在当前版本提交操作是在存储引擎这一级别的——engine.Commit(),它将提交对所有表的更新。在官方的Release版本,每一个表的Commit()方法也将可用。

 

3.1 XIndex<TKey, TRecord>泛型类支持的泛型类型

我们可以使用XIndex<TKey, TRecord> table来定义一个表。这里TKey和TRecord支持的类型有:

● 基本类型——Boolean、Char、SByte、Byte、Int16、UInt16、Int32、UInt32、Int64、UInt64、Single、Double、Decimal、DateTime、String、byte[];

● 包含默认构造器的类型和结构体,他们使用基本类型定义了公开的读/写属性。

● 包含默认构造器的类型和结构体,他们使用基本类型和上述使用基本类型定义的类/结构体定义了公开的读/写属性。

● 可以转换成IData接口的类型。

 

下面我们来看一个示例,我们定义了两个类型,对应于TKey和TRecord。

publicclassKey{publicstringSymbol{ get;set;}publicDateTimeTimestamp{ get;set;}}publicclassTick{publicdoubleBid{ get;set;}publicdoubleAsk{ get;set;}publiclongVolume{ get;set;}publicstringProvider{ get;set;}}

我们可以以以下方式来打开表。

XIndex<long,Tick> table1 = engine.OpenXIndex<long,Tick>("table1");XIndex<DateTime,Tick> table2 = engine.OpenXIndex<DateTime,Tick>("table2");XIndex<Key,Tick> table3 = engine.OpenXIndex<Key,Tick>("table3");

或者,甚至可以用以下方式。

XIndex<Tick,Tick> table4 = engine.OpenXIndex<Tick,Tick>("table4");

在使用组合键的情况下,存储引擎将会使用子键进行比较。

 

3.2 XIndex<TKey, TRecord> 类型定义的方法

XIndex<TKey,TRecord>实现了IIndex<TKey,TRecord>接口,它定义了如下的方法:

 

publicinterface IIndex<TKey,TRecord>:IIndex,IEnumerable<KeyValuePair<TKey,TRecord>>{TRecordthis[TKey key]{ get;set;}voidReplace(TKey key,TRecord record);voidInsertOrIgnore(TKey key,TRecord record);voidDelete(TKey key);voidDelete(TKey fromKey,TKey toKey);voidClear();
 
    bool Exists(TKey key);
    bool TryGet(TKey key,outTRecord record);TRecordFind(TKey key);TRecordTryGetOrDefault(TKey key,TRecord defaultRecord);KeyValuePair<TKey,TRecord>?FindNext(TKey key);KeyValuePair<TKey,TRecord>?FindAfter(TKey key);KeyValuePair<TKey,TRecord>?FindPrev(TKey key);KeyValuePair<TKey,TRecord>?FindBefore(TKey key);IEnumerable<KeyValuePair<TKey,TRecord>>Forward();IEnumerable<KeyValuePair<TKey,TRecord>>Forward(TKeyfrom, bool hasFrom,TKey to, bool hasTo);IEnumerable<KeyValuePair<TKey,TRecord>>Backward();IEnumerable<KeyValuePair<TKey,TRecord>>Backward(TKey to, bool hasTo,TKeyfrom, bool hasFrom);KeyValuePair<TKey,TRecord>FirstRow{ get;}KeyValuePair<TKey,TRecord>LastRow{ get;}longCount();}

默认的XIndex迭代器使用升序方式遍历了表中的每一行。
Forward()方法以升序遍历表的每一行。
Backward()方法以降序遍历表的每一行。
FindNext()返回大于等于指定Key的第一行。
FindAfter()返回大于指定Key的第一行。
FindPrev()返回小于等于指定Key的第一行。
FindBefore()返回小于指定Key的第一行。
FirstRow返回最小Key的行。
LastRow返回最大Key的行。

以下方法用于异步更改XIndex的内容,他们在瀑布树中执行相应的操作。

this[TKey key]set;Replace(TKey key,TRecord record);InsertOrIgnore(TKey key,TRecord record);Delete(TKey key);Clear().

3.3 XIndex<TKey, TRecord>基础知识

前面已经提过,每一个存储引擎可以使用不同的类型处理多个XIndex<TKey,TRecord>表。每一个TKey和TRecord可以是基本类型或者复杂结构体或者类型。然而,在所有情况,每一个泛型的XIndex都使用一个非泛型的XIndex表来存储它的数据,如下所示。


public class XIndex : IIndex<IData, IData>

当一个XIndex被创建,它使用.NET表达式自动生成和编译能够将每一个TKey和TRecord实例转换成一个合适的IData实例的转换代码。因此,从一个数据库的角度来看,XIndex<TKey, TRecord>好像是在XIndex中实现了应用层数据和内部数据的转换。这确保了内部存储的独立,而依赖于应用定义的类型,同时为以更自然的方式向开发者提供了使用自定义类型的存储。

 

 

3.4 XIndex和IData实现

XInde使用如下由STSdb实现的IData类型。

 

publicinterface IData{}publicclassData<TSlot0>:IData{publicTSlot0Slot0;}publicclassData<TSlot0,TSlot1>:IData{publicTSlot0Slot0;publicTSlot1Slot1;}publicclassData<TSlot0,TSlot1,TSlot2>:IData{publicTSlot0Slot0;publicTSlot1Slot1;publicTSlot2Slot2;}...

当前版本支持最多64个Slot的Key和Record的Data类型。每一个Slot可以是任何基本类型。

 

以下代码演示了如何直接使用非泛型XIndex。

 

//writing
using (StorageEngine engine =newStorageEngine("stsdb4.sys","stsdb4.dat")){XIndex table = engine.OpenXIndex(typeof(Data<int>),typeof(Data<string>),"table");for(int i =0; i <1000000; i++){
        table[newData<int>(i)]=newData<string>(i.ToString());}
 
    table.Flush();
    engine.Commit();}
//reading
using (StorageEngine engine =newStorageEngine("stsdb4.sys","stsdb4.dat")){XIndex table = engine.OpenXIndex(typeof(Data<int>),typeof(Data<string>),"table");foreach(var row in table)//table.Forward(), table.Backward(){Data<int> key =(Data<int>)row.Key;Data<string> record =(Data<string>)row.Value;Console.WriteLine("{0} {1}", key.Slot0, record.Slot0);}}

3.5 数据转换

 

数据转换用于实现将应用定义的类型与IData类型的转换。他们构建了XIndex<TKey, TRecord>和XIndex表的转换层。所有的转换都实现了如下接口。

 

publicinterface IDataTransformer<T>{IDataToIData(T item);
    T FromIData(IData data);DataDescriptorDataDescriptor{ get;}}

每一个XIndex<TKey, TRecord>示例自动生成两个数据转换——一个用于实现Key的转换,另一个实现Record的转换。也就是说,每一个输入的TKey/TRecord实例将转换成合适的IData实例。同样的,来自于XIndex的每一个IData实例也将转换到TKey/TRecord实例(在一些很复杂的情况,可以由自己来定义转换)。

 

 

假设我们定义了以下类型。

 

publicclassTick{publicstringSymbol{ get;set;}publicDateTimeTimestamp{ get;set;}publicdoubleBid{ get;set;}publicdoubleAsk{ get;set;}publiclongVolume{ get;set;}publicstringProvider{ get;set;}}

并且我们定义了XIndex<long, Tick>。

 

XIndex<long,Tick> table = engine.OpenXIndex<long,Tick>("table");

那么,在后端的XIndex表将使用以下两个对应于键和记录的类型。

 

Data<long>Data<string,DateTime,double,double,long,string>

我们可以用以下代码来模拟打开后端的XIndex表。

 

XIndex table2 = engine.OpenXIndex(typeof(Data<long>),typeof(Data<string,DateTime,double,double,long,string>),"table");

在上述的例子,table和table2将指向相同的数据。

 

 

如果我们希望使用非基本类型来扩展Tick类,比如。

 

publicclassProvider{publicstringName{ get;set;}publicstringWebsite{ get;set;}}publicclassTick{publicstringSymbol{ get;set;}publicDateTimeTimestamp{ get;set;}publicdoubleBid{ get;set;}publicdoubleAsk{ get;set;}publiclongVolume{ get;set;}publicProviderProvider{ get;set;}}

那么,此时后端的XIndex可以使用以下类型来打开。

Data<long>Data<string,DateTime,double,double,long, bool,string,string>

我们还可以使用Symbol和Timestamp并使用它作为组合键。

publicclassKey{publicstringSymbol{ get;set;}publicDateTimeTimestamp{ get;set;}}publicclassProvider{publicstringName{ get;set;}publicstringWebsite{ get;set;}}publicclassTick{//public string Symbol { get; set; }//public DateTime Timestamp { get; set; }publicdoubleBid{ get;set;}publicdoubleAsk{ get;set;}publiclongVolume{ get;set;}publicProviderProvider{ get;set;}}
XIndex<Key,Tick> table = engine.OpenXIndex<Key,Tick>("table");

此时后端的XIndex可以使用以下类型来打开。

Data<string,DateTime>Data<double,double,long, bool,string,string>

需要注意的是,因为每一个数据Slot只能由基本类型来定义,因此,需要使用一个额外的Boolean Slot来指定引用类型的对象的属性是否为null。这样,我们可以拆分自定义类型。

 

如果一个XIndex使用了组合Key,这些Key使用了不止一个Slot,那么数据引擎将使用BigEndian按照从小到大的Slot的顺序来比较子键。

 

转换成IData后,用户的数据非常适合于压缩。该引擎能够为每一个XIndex类型自动生成压缩类。这些压缩类依赖于Slot的基本类型为每一个Slot使用并行垂直方法的进行压缩。因此,压缩会很快并保持很高的压缩比。默认的,XIndex<TKey,TRecord>和XIndex以压缩的方式来保存数据。

 

4 XFile

STSdb 4.0支持稀疏文件存储,称为XFile。我们可以基于标准的.NET流来使用XFile。

 

using (StorageEngine engine =newStorageEngine("stsdb4.sys","stsdb4.dat")){XFile file = engine.OpenXFile("file");Random random =newRandom();byte[] buffer =newbyte[]{1,2,3,4,5,6,7,8,9,10};for(int i =0; i <100; i++){long position = random.Next();//writes some data on random positions
        file.Seek(position,SeekOrigin.Begin);
        file.Write(buffer,0, buffer.Length);}
 
    file.Flush();
    engine.Commit();}

XFile使用特殊的XIndex<long, byte[]>实现且提供了更为高效的稀疏文件功能。这样,在一个存储引擎里,开发人员可以组合使用XIndex表和XFile文件。

 

5 线程安全

存储引擎是线程安全的,在一个存储引擎中使用不同线程创建XIndex和XFile实例是线程安全的。不过,XIndex和XFile本身不是线程安全的。在同一个存储引擎,不同的线程来处理由其创建的XIndex和XFile是线程安全的。

推荐阅读