SequoiaDB 筆記,sequoiadb筆記
SequoiaDB 筆記
這幾天翻了翻SequoiaDB的代碼,記了點筆記。不保證下面內容的正確性(肯定有錯的地方)
個人觀感優點
- 代碼還不錯,設計也算簡潔。
- EDU和CB的使用讓整個系統變得簡單很多,讓代碼更關注邏輯。
- 從設計上應該就是一個分布式系統,麻雀雖小五髒俱全。
- 沒用什麼亂七八糟的東西改,基本是自己的代碼(雖然支援SQL但是基本可以認為是通過Postgresql支援的)。
八卦&吐槽
- 八卦一下Sequoia這個名字,本意是紅杉的意思,同時豐田有款車叫這個名字,還有那個著名的紅杉資本。不知道有什麼關係沒有。
- 掃了一眼他們的招聘列表,居然沒有招資料庫開發的,不知道是資料庫的人已經夠了還是不打算搞了。
- 吐槽一下縮寫,我實在無法從代碼的縮寫裡分辨出含義來,比如bar這個檔案夾,裡麵包含了barBackup和barRestore,難道bar = backup + restore? 問題是barrier情何以堪?ixm = index mananger?dps = Data Protection Service?PD=Problem Determination ? PMD = Process MoDel ? 又比如_extentInsertRecord的參數叫deletedRecordPtr, 而_extentRemoveRecord的參數叫做recordPtr
- 注釋極少,基本上還是沒有營養的重複一下類名什麼的,也沒有找到TODO之類,更關鍵的是注釋基本都是用縮寫表示,不排除開源版本特意把注釋都刪掉了。代碼顯得有點太乾淨了,乾淨的有點過了。
綜述
關於整體介紹,要看官方檔案(這份檔案看起來有些原始,應該是不同時期不同人的檔案堆積起來的,創業公司不能要求太高),如果只關注一個摘要的話,請參考這個ppt。
SequoiaDB 資料庫是一款新型企業級分布式非關係型資料庫,協助企業使用者降低 IT 成本,並對大資料的儲存與分析提供了一個堅實,可靠,高效與靈活的底層平台。
優勢
• 通過非結構化儲存與分散式處理,提供了近線性水平擴張能力,讓底層的儲存不再成為瓶頸
• 提供了精確到分區層級的高可用性,預防伺服器,機房故障以及人為錯誤,讓資料24x7永遠線上
• 提供了完善的企業級功能,讓使用者輕鬆管理高並發性任務,以及海量資料分析
• 增強非關係型資料模型,協助企業快速開發和部署應用程式,做到應用程式的隨需應變 • 提供了最終一致性的保障,從根本上杜絕資料缺失
• 提供了線上應用與大資料分析的後台資料庫的結合,通過讀寫分離機製做到同系統中資料分析與線上業 務互不干擾
系統架構
SequoiaDB 使用分布式架構,提供了對 SequoiaDB 體繫結構的一般概述。
在客戶機端(或應用程式端),本地或/和遠程應用程式都與 SequoiaDB 客戶機庫連結。本地與遠程客戶機 使用 TCP/IP 協議與協調節點進行通訊。
協調節點不儲存任何使用者資料,僅作為請求分發節點將使用者請求分發至相應的資料節點。 編目節點儲存系統的中繼資料資訊,協調節點通過與編目節點通訊從而瞭解資料在資料節點中的實際分布。一
個或多個編目節點可組成複製組叢集。
資料節點儲存使用者的資料資訊。一個或多個資料節點可以構成一個複製組(又稱分區組)。複製組中每個數 據節點都儲存該複製組的一份完整資料,又稱為複製組執行個體(或分區組執行個體);複製組中的資料節點之間采 用最終一致性同步資料,不同的複製組中儲存的資料無重複。
每個複製組中可以包含一個或多個資料節點。當存在多個資料節點時,節點間資料進行非同步複製。複製組中 可以存在最多一個主節點與若干從節點。其中主節點可以進行讀寫操作,從節點進行唯讀操作
從節點離線不影響主節點的正常工作。主節點離線後會在從節點中自動選舉出新的主節點處理寫請求
節點恢複後,或新的節點加入複製組後會進行自動同步,保障資料在同步完成時與主節點一致。
在單個資料節點中的體繫結構如下:
在資料節點,活動由引擎可調度單元(EDU)控制。每一個節點為作業系統中的一個進程。每個 EDU 在節點 中為一個線程。對於外部使用者請求其處理線程為代理線程,對於叢集內部請求則由同步代理線程處理分區內 同步事件;或分區代理線程處理分區間同步事件。
所有對資料的寫操作均會記錄入日誌緩衝區,通過日誌記錄器將其非同步寫入磁碟。 使用者資料會由代理線程直接寫入檔案系統緩衝池,然後由作業系統將其非同步寫入底層磁碟。
對外介面
基本上和MongoDB區別不大,用js搞得,介面粗看起來也差不多,努努力估計雙方能相容。SQL的支援通過postgresql實現,同時還實現了rest介面。
資料模型
SequoiaDB 資料庫使用 JSON 資料模型,而非傳統的關係型資料模型。
JSON 資料結構的全稱為 JavaScript Object Notation,是一種輕量級的資料交換格式,非常易於人閱讀和編 寫,同時也易於機器產生和解析。其底層儲存是BSON。The same as MongoDB
單個檔案的限制依然是16MB。
儲存模型
其具體實現也是經典的檔案--資料區段---資料頁結構
檔案可以跨頁(廢話,頁的大小最大64k),但是跨不了塊。
不知道是copy mongoDB還是處於系統簡化的考慮,底層實際上還是mmap,也就是說讀和換頁什麼的還是靠系統自己去搞得,相對於mongoDB的改進在於有背景工作刷髒頁到磁碟。
一致性和持久性事務模型
SequoiaDB 是 ACID 相容文檔層級,支援提交復原等事務操作。預設情況下,SequoiaDB為了保證效能關閉事務的支援,如果使用者需要則可以在啟動資料庫時指定參數開啟事務。
在關閉事務支援時,一個單一操作能夠寫一個或多個欄位,包括多個子文檔的更新和數組元素更新。SequoiaDB 的 ACID 保證了文檔更新的完整隔離性,任何錯誤都會引發復原操作,以及客戶能得到文檔的一致性視圖。
而當事務開啟時,任何在事務啟動到提交(復原)之間的操作都會在資料節點寫入交易記錄並跟蹤事務 ID。更改的記錄在事務提交(復原)前持有互斥鎖,不可被其他會話更改。當前 SequoiaDB 事務的隔離等級為 UR。
一致性
SequoiaDB 採用集合層級的可配置一致性策略,允許使用者在對記錄修改時,根據該記錄所在集合判斷是否需要等待備節點的確認。
譬如,對於一個對效能要求較高、對資料可靠性要求一般的集合來說,可以在建立集合時將 write concern 參數設定為 1,代表只要寫入主節點就可以返回成功資訊。而該操作會在後台非同步地發送給從節點執行。
而對於效能要求較低、對資料可靠性要求很高的集合來說,可以在建立集合時指定 write concern 參數為 3,代表該操作最少被三個節點確認執行成功後才能夠返回。
當 write concern 的數量與該集合所在每個複本集中包含節點數量相等時,系統可以被認為是強一致,否則為最終一致。
對比VS MongoDB
整體感覺,SequoiaDB就是一個refine版的mongoDB。修正或者說加強了mongoDB的很多問題,比如那個臭名昭著的鎖啊,頁管理了,有限的支援事務啥的。這個應該就是所謂的後發優勢了,前面有人給你趟過一遍坑了,問題簡化了很多。
至於說雙方誰的品質高,誰的分布式架構好,誰皮實耐用那就是一個仁者見仁智者見智的問題。
一切交給時間去考察吧。
個人理解的有點在於
- 實現了行級鎖,相對mongoDB極大的改善了插入效能,一些測試報告 也證明了這一點。
- 雖然依然是mmap但是似乎做了一些精細化管理。
- 有了最基礎的事務支援。
- 沒做複雜的mempool,緩衝池之類的事情;把有限的精力投入到了最需要的地方。(竊以為即使依賴系統實現總比自己山寨一個亂七八糟的東西強,寫核心的人是頂尖程式員,即使他們的程式不是為資料庫最佳化的)。
VS FounderXML
這裡的比較忽略XML和Json的巨大不同之處。
和FounderXML相比,SequoiaDB放棄了或者說不支援很多東西:
- 事務。換到的是相對簡單的設計(C:SequoiaDB支援到read uncommit,比完全不支援好那麼一點點),不用引入複雜的Postgresql或其的交易資料庫/引擎。當然可以自己開發一個,嗯,這麼天才的主意還是算了吧。
- 大檔案支援,有沒有,這是一個有點複雜的選擇,支援吧,確實給系統帶來更多的複雜性,效能也受到拖累;不支援吧,確實有實際需要。就快速出一個產品的角度說,還是不支援的好。
相對於FounderXML,SequoiaDB主要的進步點在於:
- 用簡化的設計(代價是犧牲某些功能)快速完成一個產品。
- 依賴相對較少,其核心代碼基本上都是自己開發的。只依賴boost,php,parser,crypto等少數東西。突然想起一個哥們吐槽某資料庫是學化學的人寫的,少依賴點離化學就遠了一點點。
其它和代碼相關的CB
CB的含義是control block。在SequoiaDB中這是很重要的介面:控制相關的邏輯就靠它了
/* _IControlBlock define */ class _IControlBlock : public SDBObject, public _ISDBRoot { public: _IControlBlock () {} virtual ~_IControlBlock () {} virtual SDB_CB_TYPE cbType() const = 0 ; virtual const CHAR* cbName() const = 0 ; virtual INT32 init () = 0 ; virtual INT32 active () = 0 ; virtual INT32 deactive () = 0 ; virtual INT32 fini () = 0 ; virtual void onConfigChange() {} } ; typedef _IControlBlock IControlBlock ;
代碼入口
整個資料庫的入口在pmdMain.cpp中,基本上是讀配置,初始化一堆mananger,恢複上次失敗的資料的。關鍵代碼是根據啟動類型的不同啟動不同的code block(CB):
void _pmdController::registerCB( SDB_ROLE dbrole ) { if ( SDB_ROLE_DATA == dbrole ) { PMD_REGISTER_CB( sdbGetDPSCB() ) ; // DPS PMD_REGISTER_CB( sdbGetTransCB() ) ; // TRANS PMD_REGISTER_CB( sdbGetClsCB() ) ; // CLS PMD_REGISTER_CB( sdbGetBPSCB() ) ; // BPS } else if ( SDB_ROLE_COORD == dbrole ) { PMD_REGISTER_CB( sdbGetTransCB() ) ; // TRANS PMD_REGISTER_CB( sdbGetCoordCB() ) ; // COORD PMD_REGISTER_CB( sdbGetFMPCB () ) ; // FMP } else if ( SDB_ROLE_CATALOG == dbrole ) { PMD_REGISTER_CB( sdbGetDPSCB() ) ; // DPS PMD_REGISTER_CB( sdbGetTransCB() ) ; // TRANS PMD_REGISTER_CB( sdbGetClsCB() ) ; // CLS PMD_REGISTER_CB( sdbGetCatalogueCB() ) ; // CATALOGUE PMD_REGISTER_CB( sdbGetBPSCB() ) ; // BPS PMD_REGISTER_CB( sdbGetAuthCB() ) ; // AUTH } else if ( SDB_ROLE_STANDALONE == dbrole ) { PMD_REGISTER_CB( sdbGetDPSCB() ) ; // DPS PMD_REGISTER_CB( sdbGetTransCB() ) ; // TRANS PMD_REGISTER_CB( sdbGetBPSCB() ) ; // BPS } else if ( SDB_ROLE_OM == dbrole ) { PMD_REGISTER_CB( sdbGetDPSCB() ) ; // DPS PMD_REGISTER_CB( sdbGetTransCB() ) ; // TRANS PMD_REGISTER_CB( sdbGetBPSCB() ) ; // BPS PMD_REGISTER_CB( sdbGetAuthCB() ) ; // AUTH PMD_REGISTER_CB( sdbGetOMManager() ) ; // OMSVC } //Data Management Service Control Block //This file contains code logic for data management control block, which is the metat// data information for DMS component. // 包括collection space等 PMD_REGISTER_CB( sdbGetDMSCB() ) ; // DMS // 和context相關,create和delete context PMD_REGISTER_CB( sdbGetRTNCB() ) ; // RTN // SQL PMD_REGISTER_CB( sdbGetSQLCB() ) ; // SQL // 集合 PMD_REGISTER_CB( sdbGetAggrCB() ) ; // AGGR //啟動伺服器/rest伺服器/管理sessionInfo PMD_REGISTER_CB( sdbGetPMDController() ) ; // CONTROLLER }
可調度單元(EDU)
對於不同的EDU有不同的入口函數,比如監聽一個連接埠的做法就是
rc = pEDUMgr->startEDU( EDU_TYPE_TCPLISTENER, (void*)_pTcpListener, &eduID ) ;
其定義為:
// 根據type,啟動一個線程(實際比這個複雜),把參數傳遞進去,並把eduid返回 INT32 _pmdEDUMgr::startEDU ( EDU_TYPES type, void* arg, EDUID *eduid )
系統會在啟動一個線程,並調用pmdTcpListenerEntryPoint函數。
static const _eduEntryInfo entry[] = { ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_SHARDAGENT, FALSE, pmdAsyncSessionAgentEntryPoint, "ShardAgent" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_COORDAGENT, FALSE, pmdAgentEntryPoint, "CoordAgent" ), // 最終調用_pmdDataProcessor::processMsg處理每條,順便提一句這裡有所有命令的列表 ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_AGENT, FALSE, pmdLocalAgentEntryPoint, "Agent" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_REPLAGENT, FALSE, pmdAsyncSessionAgentEntryPoint, "ReplAgent" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_HTTPAGENT, FALSE, pmdHTTPAgentEntryPoint, "HTTPAgent" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_RESTAGENT, FALSE, pmdRestAgentEntryPoint, "RestAgent" ), // 連接埠監聽 ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_TCPLISTENER, TRUE, pmdTcpListenerEntryPoint, "TCPListener" ), // rest監聽 ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_RESTLISTENER, TRUE, pmdRestSvcEntryPoint, "RestListener" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_CLUSTER, TRUE, pmdCBMgrEntryPoint, "Cluster" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_CLUSTERSHARD, TRUE, pmdCBMgrEntryPoint, "ClusterShard" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_CLSLOGNTY, TRUE, pmdClsNtyEntryPoint, "ClusterLogNotify" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_REPR, TRUE, pmdAsyncNetEntryPoint, "ReplReader" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_LOGGW, TRUE, pmdLoggWEntryPoint, "LogWriter" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_SHARDR, TRUE, pmdAsyncNetEntryPoint, "ShardReader" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_PIPESLISTENER, TRUE, pmdPipeListenerEntryPoint, "PipeListener" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_BACKGROUND_JOB, FALSE, pmdBackgroundJobEntryPoint, "Task" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_CATMAINCONTROLLER, TRUE, pmdCBMgrEntryPoint, "CatalogMC" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_CATNODEMANAGER, TRUE, pmdCBMgrEntryPoint, "CatalogNM" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_CATCATALOGUEMANAGER, TRUE, pmdCBMgrEntryPoint, "CatalogManager" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_CATNETWORK, TRUE, pmdAsyncNetEntryPoint, "CatalogNetwork" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_COORDNETWORK, TRUE, pmdCoordNetWorkEntryPoint, "CoordNetwork" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_DPSROLLBACK, TRUE, pmdDpsTransRollbackEntryPoint, "DpsRollback"), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_LOADWORKER, FALSE, pmdLoadWorkerEntryPoint, "MigLoadWork" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_PREFETCHER, FALSE, pmdPreLoaderEntryPoint, "PreLoader" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_OMMGR, TRUE, pmdCBMgrEntryPoint, "OMManager" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_OMNET, TRUE, pmdAsyncNetEntryPoint, "OMNet" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_SYNCCLOCK, TRUE, pmdSyncClockEntryPoint, "SyncClockWorker" ), ON_EDUTYPE_TO_ENTRY1 ( EDU_TYPE_MAXIMUM, FALSE, NULL, "Unknow" ) };
記憶體管理
雖然定義了SDBObject,很多類都是從這個類繼承而來,這個類重寫了new/delete,不過到底層還是使用了malloc解決問題,不排除以後會升級成mempool。目前還是只加了一些checking而已。
看起來查詢時用的記憶體都是臨時分配的,而系統的記憶體應該還是消耗在了mmap上。
查詢執行
一個基本的query執行方式:
- rtnQuery 完成查詢計劃的build(查詢最佳化似乎做了一些事情,還沒來得及細看),定位到第一個結果處。
- rtnGetMore 從第一個結果處繼續掃描結果。
從而避免了可能的查詢結果過大的問題。
作業系統封裝
在OSS中封裝了常見的系統調用,比如read,create,open,malloc之類的。btw,有的文檔中只列出了linux支援的版本,從代碼上看windows應該也是支援的。