TSL 訪問器

設計原理:GE有一個分佈式內存基礎設施,成爲內存雲。內存雲由一組內存主幹組成。集羣中的每臺機器承載256個內存中繼。咱們將一臺機器的本地內存空間劃分爲多個內存中繼的緣由有兩方面:1)中繼級別的並行性能夠沒有任何鎖定開銷的狀況下實現;2)內存中繼使用散列機制進行內存尋址。因爲哈希衝突發生的機率較高,單個大哈希表的性能不夠理想。java

GE內存雲提供鍵值訪問接口,鍵是64位全局惟一標識符。只是任意長度的二進制數。因爲內存雲分佈在多臺機器上,咱們沒法使用其物理內存地址來處理鍵值對。爲了定位給定鍵的值,咱們首先肯定存儲鍵值對的機器,而後在該機器上的一個內存中定位鍵值對。c++

對於不一樣的GE應用程序,鍵-值對中的值組件具備不一樣的數據結構或數據模式。咱們使用單元格來表示值組件。例如,讓咱們考慮具備如下內容的單元格:1)32位整數Id;2)64位整數的列表。換句話說,咱們但願實現一個可變長度列表(帶有Id)做爲值組件。在c#中,這樣的結構能夠定義爲:編程

struct CellA
{
  int Id;
  List<long> Links;
}
c#

如何有效的存儲結構化類型化數據並支持高效而優雅的操做是一個挑戰。緩存

咱們能夠直接使用大多數面嚮對象語言(如c++或c#)支持的對象來建模用戶數據。這提供了一種方便直觀的數據操做方式。咱們能夠經過對象的接口來操做對象,例如int Id = cell.Id 或者 cell.Links[0] = 1000001其中cell是類型的對象。這種方法雖然簡單而優雅,但有明顯的缺點。首先,在內存中保存對象的存儲開銷很是高。其次,語言運行時一般不是爲處理大量對象而設計的。隨着對象的增長,系統性能幾句降低。第三,加載和存儲數據須要大量的時間,由於序列化/反序列化對象很是耗時,尤爲是數據量大。安全

咱們能夠將值組件視爲二進制數並經過指針訪問數據。將數據存儲爲二進制能夠最小化內存開銷。它還能夠提升數據操做性能,由於數據操做不涉及數據反序列化。可是系統不知道數據的模式。在實際操做二進制數中的數據以前,咱們須要知道確切的內存佈局。話句話說,咱們須要使用指針和地址偏移量來訪問二進制數中的數據元素。這使得編程變得困難和容易出錯。數據結構

按照上面描述的數據結構,咱們須要知道字段Id存儲在偏移量0處,鏈接的第一個元素位於二進制數中的偏移量8處。要獲取字段Id的值,並設置Links[0]的值,他須要編寫一下代碼:多線程

byte* ptr = GetBlobPtr(123); // Get the memory pointer
int Id = *(int*)ptr; // Get Id
ptr += sizeof(int);
int listLen = *(int*)ptr;
ptr+=sizeof(int);
*(long*)ptr = 1000001; // Set Links[0] to 1000001
編程語言

 注意,咱們不能天真的將二進制數轉換爲使用c#等編程語言提供的內置關鍵字結構定義的結構。也就是說,下面顯示的代碼將會失敗:分佈式

// C# code snippet
struct
{
  int Id;
  List<long> Links;
}
.... 

struct * cell_p = (struct*) GetBlobPtr();
int id = *cell_p.Id;
*cell_p.Links[0] = 100001;

這是由於上面示例中的連接等數據字段是引用數據字段。這樣一個結構體的數據域病沒有被平鋪在內存中。咱們不能使用結構化數據指針操做平面內存區域。

咱們還能夠將值組件視爲二進制數,並經過高級語言聲明和訪問數據。在第三種方法中,咱們經過聲明行語言(如SQL)定義和操做數據。然而,一般狀況下,聲明性語言要麼表達能力很是有限,要麼沒有有效的實現。但對GE來講,表達能力和效率極其重要。

GE將用戶數據存儲爲二進制數而不是運行時對象,這樣能夠最小化存儲開銷。同時,GE使咱們可以以面向對象的方式訪問數據,就像咱們在c#或java中所作的那樣。例如,在GE中,咱們能夠執行如下操做,即便咱們操做的數據是一個二進制數。

CellA cell = new CellA();
int Id = cell.Id;
cell.Links[0] = 1000001;

換句話說,咱們仍然能夠以一種優雅的、面向對象的方式操做二進制數。GE經過單元訪問機制實現了這一點。具體來講,咱們首先使用TSL腳本聲明數據模式。GE編譯腳本併爲TSL腳本中定義的單元結構生成單元訪問器。而後,咱們能夠經過單元訪問器訪問二進制數據,就好像數據是運行時c#對象同樣,但實際上,是單元訪問器將單元結構中聲明的字段映射到正確的內存位置。全部數據訪問操做將被正確映射到正確的內存位置,而不會產生任何內存複製開銷。

讓咱們用一個例子來演示單元訪問器是如何工做的。要使用單元訪問器,咱們必須首先指定其單元結構。這是使用TSL完成的。對於前面的例子,咱們定義TSL中的數據結構以下:

cell struct CellA
{
  int Id;
  List<long> Links;
}

注意CellA不是c#中的struct定義,儘管它看起來很類似。這個代碼片斷將由TSL編譯器編譯成CellA_Accessor。編譯器生成用於將CellA字段上的操做轉換爲底層二進制數上的內存操做的代碼。編譯後,咱們可使用面向對象的數據訪問接口訪問二進制中的數據,以下圖所示。

 

 除了直觀的數據操做接口外,單元訪問器還提供了線程安全的數據操做保證。GE被設計成在一個高度多線程的環境中運行,在這個環境中,大量的單元以很是複雜的模式相互做用。爲了簡化應用程序開發人員的工做,GE經過單元訪問提供了線程安全的單元操做接口。

用法:

在使用訪問器時,必須應用一些使用規則。

訪問器沒法緩存:

  訪問器工做起來就像一個數據指針,因爲訪問器指向的內存塊可能會被其餘訪問器操做移動,所以沒法緩存以供未來使用。

例如,若是咱們有一個被定義爲:

cell struct MyCell
{
  List<string> list;
}

 TrinityConfig.CurrentRunningMode = RunningMode.Embedded;

Global.LocalStorage.SaveMyCell(0, new List<string> { "aaa", "bbb", "ccc", "ddd"});

using (var cell = Global.LocalStorage.UseMyCell(0))

{

   Console.WriteLine("Example of non-cached accessors:");

  IEnumerable<StringAccessor> enumerable_accessor_collection = cell.list.Where(element => element.Length >= 3);

  foreach(var accessor in enumerable_accessor_collection)

  {

    Console.WriteLine(accessor);

  }

  Console.WriteLine("Example of cached accessors:");

   List<StringAccessor> cached_accessor_list = cell.list.Where(element => element.Length >= 3).ToList();

  // Note the ToList() at the end

   foreach (var accessor in cached_accessor_list)

  {

    Console.WriteLine(accessor);

  }

}

 上面顯示的代碼片斷將輸出:

Example of non-cached accessors:

aaa

bbb

ccc

ddd

Example of cached accessors:

ddd

ddd

ddd

ddd

對於非緩存訪問器的示例,將在使用訪問器時計算訪問器的值。對於緩存訪問器的示例,由cel.list返回的訪問器列表。(=>元素。Length>=3).ToList()實際上只是指向同一個訪問器的引用,這個訪問器指向cell.list的最後一個元素。

單元格訪問器和消息訪問器在使用後必須進行處理:

單元格訪問器是一次性對象。單元格訪問器使用後必須進行處理。在c#中,一次性對象能夠 經過使用構造來處理。

long cellId = 314;
using (var myAccessor = Global.LocalStorage.UseMyCell(cellId))
{
  // Do something with myAccessor
}

單元格訪問器或消息訪問器必須正確處理。若是訪問器在使用後沒有被處理,就會發生非託管資源泄露。

TSL協議的請求/響應閱讀器和寫入器成爲消息訪問器。他們也是一次性物品。使用後必須妥善處理。

using (var request = new MyRequestWriter(...))
{
  using (var response = Global.CloudStorage.MyProtocolToMyServer(0, request))
  {
    // Do something with the response
  }
}

單元格訪問器不能以嵌套方式使用

每一個單元格訪問器都有一個自旋鎖。單元格訪問器的嵌套使用可能致使死鎖。下面代碼片斷中顯示的單元格訪問器使用不當。

using (var accessorA = Global.LocalStorage.UseMyCellA(cellIdA))
{
  using(var accessorB = Global.LocalStorage.UseMyCellB(cellIdB))
  {
    // Nested cell accessors may cause deadlocks
  }
}

單元格訪問器不該嵌套在另外一個單元格中。兩個或多個嵌套單元格訪問器可能致使死鎖。

注意,單元格訪問器和一個或多個請求/響應閱讀器/寫入器能夠以嵌套的方式使用。

相關文章
相關標籤/搜索