200 行代碼解讀螞蟻金服世界第一 數據庫OceanBase

10 月 2 日,國際事務處理性能委員會公布了數據庫最新性能測試結果,在 TPC-C 基準測試中,由阿里巴巴集團螞蟻金服自主研發的分布式關系數據庫 OceanBase 打破了由 Oracle 保持了 9 年的 TPC-C 基準性能測試世界紀錄,不僅成為首個登榜的中國數據庫,更一舉拿下世界第一,對此,中國工程院院士、計算機專家李國杰評價道:「這是中國基礎軟件取得的重大突破!」

本文作者 CSDN 博客專家馬超對 OceanBase 的物理架構、數據及控制流程進行了分析,并解讀開源部分的源碼來剖析 OceanBase 是如何勇奪 TPC-C 冠軍的。

作者 | 馬超,CSDN 博客專家

責編 | 唐小引

出品 | CSDN 博客

封圖 | CSDN 付費下載自東方 IC

我國高科技基礎平臺又有重大突破,繼阿里和騰訊以及眾多國內老牌嵌入式廠商相繼宣傳開源 IoT 操作系統之后,今天據權威機構國際事務處理性能委員會(Transaction Processing Performance Council)官網披露,由阿里巴巴集團螞蟻金服自主研發的關系數據庫 OceanBase,在 TPC-C 基準測試中,打破了由美國公司 Oracle(甲骨文)保持了 9 年之久的世界記錄,成為首個登頂該榜單的中國數據庫產品。

不過經筆者剛剛多次嘗試,目前 TPC 的官網(www.tpc.org)并無法在國內訪問,所以全部的信息只能出自這個截圖,我們可以看到如下兩個信息,一是這個排行榜是以 tpmC 為基準進行排名的,這里解釋一下 tpmC 其實是 TPC-C 三標準的一種度量方式,tpmC 中的 tpm 是 transactions per minute 的簡寫,即每分鐘的交易量,C 代表 C 基準程序。它的定義是每分鐘內系統處理的新訂單個數。在這方面阿里的 OceanBase 的確是遙遙領先。

TPC-C 還經常以系統性能價格比的方式體現,單位是$/tpmC,即以系統的總價格(單位是美元)/tpmC 數值得出。這個筆者也已經在上圖中標紅,可見這個參數按現行匯率來說阿里的優勢不明顯。而且考慮到甲骨文的測試提交時間是 2011 年,而阿里的測試時間則是今年的 10 月 2 日。因此由于筆者也沒拿到測試報告的原文,所以甲骨文是否還是以 11 年的硬件價格來做為分母計算性價比指標也尚不得而知。

因而綜上所述,阿里在性能指標上的確是做到了遙遙領先,但是可能在硬件價格以及測試時間的方面占了一些先機。

OceanBase 在哪些方面做對了?

根據 OceanBase 的官網[1]介紹,其總體物理架構如下:

同時,從官網信息我們可以看到,OceanBase 在金融行業的應用案例為南京銀行,因此筆者第一時間翻了一下南京銀行朋友的票圈,了解了一下相關信息,目前南京分行在 OceanBase 上線的系統以互聯網應用為主,而且根據其分享 OceanBase 在 Github 對自己的 0.4 版本進行了開源[2],目前雖然版本有更新,但是其基礎設計沒有根本改變,根據 GitHub 上的資源顯示,其數據及控制流程總體如下:

OceanBase 將表的數據動態切分為 tablet,tablet 的數據分為動態和靜態兩部分。靜態的數據存放在 chunkserver 上,所有對數據的修改都存儲在 updateserver 中。updateserver 的修改定期同步到 chunkserver,chunkserver 將 updateserver 的更新和本地的靜態數據合并,生成合并后的新數據。

tablet 的信息由 rootserver 維護,客戶端在初始化時會請求 rootserver,獲取 updateserver 的地址信息。客戶端的更新請求(包括新增、修改和刪除)都直接訪問 updateserver。查詢請求時客戶端根據相應的 rowkey 向 rootserver 查詢其對應的 tablet 信息,rootserver 返回相應的 mergeserver 地址,客戶端根據返回的信息請求相應的 mergeserver 獲取數據。

Mergeserver 收到請求時,根據 rowkey 從 rootserver 獲取相應的 tablet 信息,該信息中包括負責該 tablet 的 chunkserver 列表,mergeserver 請求相應的 chunkserver,獲取靜態數據(如果有的話),然后根據返回的數據,請求 updateserver 獲取相應的更新數據,將更新數據和靜態的數據合并,將合并后的結果返回給客戶端。

為了提高讀取的性能,chunkserver 對部分數據結構進行了緩存。一個 SSTable 由多個 block 組成,為了加快定位需要請求的數據位于 SSTable 的哪個 block 中,chunkserver 包含一個 block index 的功能,block index 由該 block 負責的數據的最后一個 key 和該 block 在 SSTable 文件中的位置組成。為了提高 block 的讀取性能,chunkserver 還將 block 緩存在內存中。Block index 和 block 的 cache 都采用 LRU 的策略淘汰。

筆者之前的博文中也介紹過,想提高效率必須做做減法,針對專門的場景做特定的優化。在筆者看到 OceanBase 的設計時,最令我印象深刻的是其 chunkserver 自帶的緩存功能,而且根據之前的最佳實踐分享,與甲骨文等傳統數據庫不同,OceanBase 與應用之間是不需要加 redius 緩存的,所以這點應該是阿里做的比傳統數據庫廠商厲害的地方。阿里的數據庫之所以快基本上可以歸功于這個 chunkserver 的功勞,下面我們就來進行一下代碼解讀。

速度秘訣:chunkserver 的設計與相關代碼

其 chunkserver 的基礎流程圖如下:

可以看到負責數據查找和讀取的部分是 SSTable 所以下面我們再對這部分做一下詳細的代碼解讀,為便于查找,SSTable 會建立一些信息來索引數據,比如相關 key 在 SSTable 中的偏移,IndexBuilder 即用來建立這些信息。這部分的相關代碼在:

https://github.com/alibaba/oceanbase/tree/master/oceanbase_0.4/src/sstable/ob_sstable_block_index_builder.cpp

其中重點函數是這個生成入口的函數 add_entry,這些都寫得比較清楚,大家可以看一下。

int ObSSTableBlockIndexBuilder::add_entry(const uint64_t table_id,
                                              const uint64_t column_group_id,
                                              const ObRowkey &key, 
                                              const int32_t record_size) 
    {
      int ret     = OB_SUCCESS;
      ObSSTableBlockIndexItem index_item;
      //合法性校驗
      if (record_size < 0 || key.get_obj_cnt() <= 0 || NULL == key.get_obj_ptr()
          || table_id == OB_INVALID_ID || table_id == 0 || OB_INVALID_ID == column_group_id)
      {
        TBSYS_LOG(WARN, "invalid param, table_id=%lu, key_len=%ld,"
                        "key_ptr=%p, record_size=%d, column_group_id=%lu"
                  table_id, key.get_obj_cnt(), key.get_obj_ptr(), record_size, column_group_id);
        ret = OB_ERROR;
      }
      if (OB_SUCCESS == ret)
      {
        //初始化,后面也是用以下的參數進行序列化的
        index_item.rowkey_column_count_ = static_cast<int16_t>(key.get_obj_cnt());
        index_item.column_group_id_ = static_cast<uint16_t>(column_group_id);
        index_item.table_id_ = static_cast<uint32_t>(table_id);
        index_item.block_record_size_ = record_size;
        index_item.block_end_key_size_ = static_cast<int16_t>(key.get_serialize_objs_size());
        index_item.reserved_ = 0
        ret = index_items_buf_.add_index_item(index_item);
        if (OB_SUCCESS == ret) 
        {
          ret = end_keys_buf_.add_key(key);
          if (OB_ERROR == ret)
          {
            TBSYS_LOG(WARN, "failed to add end key");
          }
          else
          {
            index_block_header_.sstable_block_count_++;
          }
        }
        else
        {
          TBSYS_LOG(WARN, "failed to add index item");
          ret = OB_ERROR;
        }
      }
      return ret;
    }

和這個函數生成 index 的函數:

int ObSSTableBlockIndexBuilder::build_block_index(const bool use_binary_rowkey, 
        char* index_block, const int64_t buffer_size, int64_t& index_size)
    {
      int ret                   = OB_SUCCESS;
      int64_t index_block_size  = get_index_block_size();
      int64_t index_items_size  = get_index_items_size();
      int64_t header_size       = index_block_header_.get_serialize_size();
      int64_t pos               = 0;
      if (NULL == index_block)
      {
        TBSYS_LOG(WARN, "invalid param, index_block=%p", index_block); 
        ret = OB_ERROR;
      }
      else if (index_block_size == header_size)
      {
        //no data in index block
        ret = OB_ERROR;
      }
      if (OB_SUCCESS == ret)
      {
        index_block_header_.end_key_char_stream_offset_ 
            = static_cast<int32_t>(header_size + index_items_size);
        // new rowkey obj array format, force set to 1.
        index_block_header_.rowkey_flag_ = use_binary_rowkey ? 0 : 1;
        if (OB_SUCCESS == index_block_header_.serialize(index_block,
                                                        header_size, pos))
        {
          char* ptr = index_block + pos;
          ret = index_items_buf_.get_data(ptr, buffer_size - header_size);
          if (OB_SUCCESS == ret)
          {
            ptr += index_items_size;
            ret = end_keys_buf_.get_data(ptr, buffer_size - header_size - index_items_size);
            if (OB_SUCCESS == ret)
            {
              index_size = index_block_size;
            }
          }
        }
        else
        {
          TBSYS_LOG(WARN, "failed to serialize index block header");
          ret = OB_ERROR;
        }
      }
      return ret;
    }

并且,除了索引以外,OceanBase 還在讀取之前添加了一個布隆過濾器,這個設計也比較有意思,Bloom Filter(布隆過濾器)用來判定某一個 key 是否屬于某個集合,它有一定誤判概率。如果判定在集合內,不一定在;但是如果判定不在集合內,那么一定不在,這樣的操作可以優化很多 not in 的查詢時間。這部分的代碼在:

https://github.com/alibaba/oceanbase/tree/master/oceanbase_0.4/src/common/bloom_filter.h

https://github.com/alibaba/oceanbase/tree/master/oceanbase_0.4/src/common/bloom_filter.cpp

這里就不再貼代碼了,大家有興趣可以去上面的鏈接讀一下相關代碼。

后記

隨著美國在高科技領域的不斷施壓,反而倒逼我國之前相對冷清的操作系統、數據庫等基礎平臺領域重新熱鬧起來,筆者這個 C 語言的 IT 老兵感覺目前又煥發第二春,所以筆者還會繼續關注相關領域的進展,后續繼續帶來代碼級的解讀,歡迎持續關注。

[1] https://oceanbase.alipay.com/product/oceanbase

[2] https://github.com/alibaba/oceanbase

本文來源: CSDN 文章作者: 馬超 我要糾錯
聲明:本文由入駐金色財經的作者撰寫,觀點僅代表作者本人,絕不代表金色財經贊同其觀點或證實其描述。
提示:投資有風險,入市須謹慎。本資訊不作為投資理財建議。

金色財經 > 區塊鏈 > 200 行代碼解讀螞蟻金服世界第一 數據庫OceanBase

精准三肖六码3肖6码免费