摘自: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是线程安全的。