MatrixOne:HTAP數(shù)據(jù)庫中的OLAP設(shè)計(jì)
一、MatrixOne整體架構(gòu)
MatrixOne早期的架構(gòu)是一個(gè)典型的share nothing架構(gòu),數(shù)據(jù)存放在一個(gè)Multi Raft集群上面,數(shù)據(jù)的每一個(gè)切片存在一個(gè)Raft上面,不同的Raft Group之間的數(shù)據(jù)是完全沒有重疊的。
早期架構(gòu)存在著一些無法解決的問題,比如在擴(kuò)展性上,每擴(kuò)展一個(gè)節(jié)點(diǎn),就需要同時(shí)擴(kuò)展存算的資源,因?yàn)橛?jì)算和存儲(chǔ)沒有完全分開。而且每擴(kuò)展一個(gè)節(jié)點(diǎn),需要大量的數(shù)據(jù)遷移工作。另外因?yàn)槊恳环輸?shù)據(jù)都要保存至少 3個(gè)副本,從擴(kuò)展節(jié)點(diǎn)到完成的時(shí)間會(huì)非常久。在性能方面,Raft協(xié)議所包含的leader角色,容易造成熱點(diǎn);在性能較差的存儲(chǔ)下,數(shù)據(jù)庫整體性能下降會(huì)超過預(yù)期;多種引擎各自用途不同,性能各異,無法有效應(yīng)對(duì)HTAP場(chǎng)景。成本方面,數(shù)據(jù)保存3副本,隨節(jié)點(diǎn)規(guī)模增長(zhǎng),成本不斷攀升,云上版本更甚;只有高配存儲(chǔ)才能發(fā)揮數(shù)據(jù)庫的預(yù)期性能。
為了更好地滿足客戶的需求,我們升級(jí)了新的架構(gòu)。
升級(jí)后的架構(gòu)從整體上看,分為三個(gè)部分:
- 最上面是計(jì)算層,計(jì)算層里面的每一個(gè)單位是一個(gè)Compute Node (CN) 計(jì)算節(jié)點(diǎn)。
- 在計(jì)算層下面是數(shù)據(jù)層,由Data Node (DN) 組成。
- 再下面是File service,支持各種不同的文件系統(tǒng)。
下面分別詳細(xì)介紹一下每一部分。
- 先從最底下的File service說起。File service支持各種不同的文件系統(tǒng),比如用戶自己的本地的磁盤,NFS、HDFS、或者對(duì)象存儲(chǔ)。File service對(duì)上層提供一個(gè)統(tǒng)一的接口,對(duì)用戶來說,他并不需要關(guān)心最底下的數(shù)據(jù)本身是存在什么樣的介質(zhì)上面。
- 這邊還會(huì)有個(gè)log service,因?yàn)槲覀冎涝趂ile service插入數(shù)據(jù)的時(shí)候,只能一整塊一整塊地寫,尤其像S3,一個(gè)object非常大,不可能一行一行地寫,一般是積累到一定的量再往里面寫一個(gè)整塊,那么這些還沒形成整塊的部分,會(huì)放在一個(gè)單獨(dú)的log service里面。log service是一個(gè)Multi Raft的集群。已經(jīng)形成整塊的部分的數(shù)據(jù)完整性和一致性是用S3或者HDFS的功能來保證。還沒有形成單個(gè)block的數(shù)據(jù)的一致性完整性則是通過Multi Raft group來保證。
- 再上面是存儲(chǔ)節(jié)點(diǎn)DN,存儲(chǔ)節(jié)點(diǎn)里只存放的是元數(shù)據(jù)信息,比如每個(gè)表會(huì)分成大的segment,每個(gè)segment又會(huì)分成很多小的block。比如用S3來存放數(shù)據(jù),元數(shù)據(jù)里會(huì)存放每個(gè)block,存放對(duì)應(yīng)的具體S3 object。還有其他元數(shù)據(jù)信息比如row map,或者是次級(jí)索引bloom filter這些信息也會(huì)存在DN上。這里可以看到有多個(gè)DN,它們是怎么分布的呢?比如一張表有一個(gè)主鍵,那么我們可以根據(jù)主鍵來做一個(gè)分區(qū),目前是按hash來做分區(qū),也可以按range來做都是沒有問題的。對(duì)于數(shù)據(jù)規(guī)模較小的客戶來說,存放元數(shù)據(jù)其實(shí)只需要一個(gè)DN就足夠了。
- 再上面就是計(jì)算節(jié)點(diǎn)CN,具體執(zhí)行計(jì)算任務(wù)的節(jié)點(diǎn)。CN可以分成各種不同類型,比如專門做TP查詢用的,專門做AP查詢用的,專門做streaming用的,還有一些對(duì)用戶不可見的數(shù)據(jù)庫自己后臺(tái)的一些任務(wù)。
- 還有一個(gè)組件叫HA Keeper,跟ZooKeeper功能類似,在節(jié)點(diǎn)之間互相通知上線下線這些信息,維護(hù)整個(gè)集群的可用信息。
新的share storage的架構(gòu)優(yōu)勢(shì)在于:
- DN節(jié)點(diǎn)不保存任何數(shù)據(jù),只保存元數(shù)據(jù)信息。這樣就不會(huì)讓單個(gè)DN成為瓶頸,如果需要做彈性擴(kuò)縮容,比如DN要新增或者刪除一個(gè)節(jié)點(diǎn)。做數(shù)據(jù)遷移只需要交換一些元數(shù)據(jù)的信息,不需要把所有數(shù)據(jù)都做搬遷的操作,這大大簡(jiǎn)化了數(shù)據(jù)遷移量,并提高了擴(kuò)縮容的效率。
- 完整性和一致性主要是通過S3來保證,S3本身就具備這一功能,并且從成本上來看也是非常低的,比用戶自己去搭建Multi Raft集群的成本會(huì)低很多。
- 計(jì)算任務(wù)也可以很好地分離開,比如TP和AP的計(jì)算,可以放在不同的CN節(jié)點(diǎn)上去做。因?yàn)楝F(xiàn)在數(shù)據(jù)已經(jīng)不跟DN綁定在一起,所以計(jì)算的節(jié)點(diǎn)也可以完全解耦。
我們來看一個(gè)典型的查詢里面讀數(shù)據(jù)的操作是怎樣的。數(shù)據(jù)查詢從一個(gè)CN節(jié)點(diǎn)開始,首先去訪問DN的信息,讀取元信息,判斷某張表在哪些block,甚至?xí)韧ㄟ^過濾條件用row map來做過濾,剩下的是實(shí)際上需要去讀的那些block信息,拿到之后直接去那個(gè)file service下面去讀取數(shù)據(jù)。
在這里DN節(jié)點(diǎn)只提供了元數(shù)據(jù)信息,基本上不會(huì)成為性能瓶頸。因?yàn)楦嗟臄?shù)據(jù)是CN節(jié)點(diǎn)去直接向File service讀取的,CN節(jié)點(diǎn)上面還會(huì)維護(hù)一個(gè)metadata cache。假設(shè)這個(gè)數(shù)據(jù)新鮮度還沒有過期,甚至可以不需要訪問DN,直接去File service下面拉取數(shù)據(jù)。
最后是存儲(chǔ)引擎,TAE,T和A分別是指TP和AP,T和A表示它既有事務(wù)的能力也可以很好的處理分析性查詢,用同一套引擎同時(shí)支持AP和TP。
在結(jié)構(gòu)上,我們實(shí)際上還是可以把它看成是基于列存,不同的列之間,數(shù)據(jù)仍然是單獨(dú)分開存放的,每一列會(huì)按照8192行分成一個(gè)小的block。對(duì)于大多數(shù)的數(shù)字類型的block,8192行數(shù)據(jù)可以在L1 cache里面直接裝下,在后面的批量計(jì)算的時(shí)候會(huì)對(duì)計(jì)算引擎比較友好。多個(gè)block會(huì)組成一個(gè)segment,segment的作用是假設(shè)這個(gè)表它有主鍵或者排序鍵的情況下,一個(gè)segment內(nèi)部會(huì)通過排序鍵和主鍵去做排序,這樣數(shù)據(jù)存儲(chǔ)在每一個(gè)segment內(nèi)部是保持有序的,但segment之間可能會(huì)有重疊。這跟LSM的存儲(chǔ)有些相似的地方。但如果我們之后做了partition功能,可能會(huì)把一個(gè)partition內(nèi)的所有segment也去做一次compaction操作。即把它們重新拿出來,做一個(gè)歸變排序再放進(jìn)去。
以上就是MatrixOne最新的架構(gòu)。
二、MatrixOne OLAP引擎
MatrixOne的計(jì)算引擎分為四個(gè)部分:
- 第一個(gè)是parser:把一個(gè)SQL語句解析成一個(gè)AST樹。
- 第二個(gè)是planner:把AST樹轉(zhuǎn)化成一個(gè)邏輯計(jì)劃。
- 第三個(gè)是optimizer:把邏輯計(jì)劃通過各種優(yōu)化器規(guī)則或者是通過一些基于代價(jià)估算的方式轉(zhuǎn)化成更好的邏輯計(jì)劃。
- 最后是execution:把具體的邏輯計(jì)劃轉(zhuǎn)化成可執(zhí)行的pipeline,然后去具體的CPU上面執(zhí)行。
語法解析器(Parser)
各大開源數(shù)據(jù)庫大多都不會(huì)去手寫一個(gè)parser,至少是用MySQL或者PG的parser。比如DuckDB就是直接使用Postgres的parser代碼。即使我們不直照搬,也可以用一些YACC的工具去生成一個(gè)parser。測(cè)試之后發(fā)現(xiàn)用YACC生成的parser,并不會(huì)成為性能瓶頸,它的耗時(shí)非常少,所以我們沒有必要去手寫一個(gè)parser。(除了ClickHouse的parser是手寫的)。parser生成AST樹之后,會(huì)通過邏輯計(jì)劃器,把AST樹轉(zhuǎn)換成一個(gè)可以執(zhí)行的邏輯計(jì)劃。
邏輯計(jì)劃器(Planner)
邏輯計(jì)劃器主要包含兩個(gè)部分:Bind(Algebrizer) 和子查詢消除。因?yàn)槲覀儾⒉恢С窒馭QL Server一樣將子查詢轉(zhuǎn)換成apply join,或者像MySQL一樣完全從父查詢里面拿出一行,再帶入子查詢里面,把子查詢完整地執(zhí)行一次??紤]到在AP查詢的場(chǎng)景,這樣的一個(gè)執(zhí)行計(jì)劃是不可接受的,就干脆完全不支持這種apply join的方式,所以我們?cè)趐lanner這一步,把子查詢的消除給做掉。
優(yōu)化器(Optimizer)
優(yōu)化器部分,通常會(huì)有一個(gè)RBO基于規(guī)則的優(yōu)化,基于規(guī)則對(duì)大部分查詢已經(jīng)是夠用的。因?yàn)閮?yōu)化器通常分為兩種,一種是減少數(shù)據(jù)IO,它會(huì)減少實(shí)際從磁盤文件系統(tǒng)讀取的數(shù)據(jù)量;還有一種是對(duì)于CPU,在計(jì)算的過程中減少計(jì)算的代價(jià)。下面會(huì)具體舉一些例子來說明MatrixOne是如何設(shè)計(jì)的。
(1)第一部分是減少IO
包括以下部分:
- 列裁剪,讀一張表,有很多列,但我實(shí)際上只用到其中一列,那么其它列是不用讀取的。
- 謂詞下推,就是把一些過濾條件直接推到讀取數(shù)據(jù)這一部分,這樣可以盡量少的讀取數(shù)據(jù)。
- 謂詞推斷,主要會(huì)影響TPC-H里面的Q7和Q19,在后面會(huì)再舉例詳細(xì)介紹。
- Runtime filter ,比如大表跟小表做join操作,在小表構(gòu)建完hash表之后,可能hash表的計(jì)數(shù)非常小,這樣我們可以直接通過hash表里面不同的詞去大表里面通過runtime或者元數(shù)據(jù)信息進(jìn)行過濾,這樣在運(yùn)行時(shí)就大大減少了需要讀取的大表的block數(shù)量。
(2)第二部分是減少計(jì)算
它并不能減少實(shí)際從磁盤里面讀取的數(shù)據(jù),但是會(huì)在計(jì)算過程中減少計(jì)算量,和減少中間結(jié)果的數(shù)據(jù)量:
join order的join定序。通常使用TPC-H做OLAP 的benchmark,join order會(huì)影響很多不同的查詢,如果join order做的不好,這些查詢都可能會(huì)以數(shù)量級(jí)的變慢。
聚合函數(shù)下推和上拉的操作。假設(shè)聚合函數(shù)是在一個(gè)join上面,如果是先做join,之后再去做聚合,那么在join這里,數(shù)據(jù)可能會(huì)膨脹的非常多。但是如果可以把聚合函數(shù)推到j(luò)oin下面去做,即在join之前先聚合,數(shù)據(jù)已經(jīng)減少很多,這也可以大大的減少計(jì)算。
簡(jiǎn)單介紹謂詞推斷:
謂詞下推是已經(jīng)確定顯式的可以下推的一個(gè)位置。但謂詞推斷可能是需要做一些邏輯上的變化之后,才能得到一些新的謂詞,這些新的謂詞才可以下推下去。比如TPC-H Q19的過濾條件是三個(gè)很長(zhǎng)的謂詞用or連接起來,通過觀察實(shí)際上這三個(gè)or里面有很多共同的部分,可以把共同的部分提取出來,變成右圖的樣子。可以觀察到,首先part這張表上面有一個(gè)可以下推的謂詞,lineitem這張表上面也有兩個(gè)可以下推的謂詞。這樣可以先把這兩個(gè)謂詞下推到每個(gè)表的table scan上面去。然后還多出來一個(gè)在part和lineitem上面用主鍵做連接的謂詞。如果原來不對(duì)這個(gè)執(zhí)行計(jì)劃做優(yōu)化直接去執(zhí)行,可能會(huì)先做一個(gè)笛卡爾積,再去做過濾操作,這樣的效率會(huì)非常低?,F(xiàn)在我們可以把它變成一個(gè)join操作。
簡(jiǎn)單介紹一下MatrixOne使用的join order算法:
在各大開源數(shù)據(jù)庫上,join order的算法實(shí)現(xiàn)主要包括貪心法和動(dòng)態(tài)規(guī)劃。其中動(dòng)態(tài)規(guī)劃有很多不同方法,也有很多論文可以參考。但是動(dòng)態(tài)規(guī)劃存在一個(gè)問題,當(dāng)表的數(shù)量稍微多一些,狀態(tài)搜索空間就會(huì)以指數(shù)級(jí)膨脹。比如StarRocks的文檔里面提到過,10個(gè)表以上的計(jì)算就沒辦法使用動(dòng)態(tài)規(guī)劃來計(jì)算,只能使用貪心法來計(jì)算。
我們?cè)贛atrixOne里對(duì)這個(gè)問題做了思考,在大于10張表時(shí),可以先用貪心的方法來做一個(gè)剪枝操作,讓搜索空間大大減小,在貪心法之后再做動(dòng)態(tài)規(guī)劃。
貪心法分三步:
- 第一步是確定事實(shí)表和維度表,因?yàn)橐话愕腛LAP查詢的數(shù)據(jù)通常會(huì)把表分成事實(shí)表和維度表,之間用維度表的主鍵做join。因此拿到一個(gè)查詢之后,我們可以把事實(shí)表和維度表給找出來。
- 下一步事實(shí)表先與其維度表join成子樹,因?yàn)槭聦?shí)表維度表之間始終是通過維度表的主鍵去做join。做這樣的join的結(jié)果的函數(shù)始終是不大于他本來輸入的函數(shù),所以做這么一個(gè)join并不會(huì)造成很多OLAP開銷,不會(huì)造成數(shù)據(jù)膨脹。在做事實(shí)表維度表join的過程中,我們會(huì)考慮事實(shí)表先與過濾性好的維度表做join。就優(yōu)化器而言,越早減少數(shù)據(jù)量是越好的。
- 最后一步是在子樹之間,像TPC DS的表,會(huì)有多個(gè)維度表,維度表互相之間都不是以主鍵來做join。那么在子樹之間,我們?cè)偈褂媒?jīng)典的join order的算法,比如動(dòng)態(tài)規(guī)劃等等。
這樣我們把Join order的算法從之前只能10表以下做動(dòng)態(tài)規(guī)劃,擴(kuò)展到10個(gè)事實(shí)表之下都可以使用動(dòng)態(tài)規(guī)劃來做。
舉個(gè)例子,TPC-H Q5中有customer、 orders、lineitem、supplier、nation、region這么六張表,它是一個(gè)比較典型的星型結(jié)構(gòu)。
我們將orders和region這兩個(gè)表標(biāo)紅,因?yàn)樗鼈兩厦嬗羞^濾條件,需要單獨(dú)考慮。然后對(duì)于本身的join條件,標(biāo)上一些帶箭頭的線。這里從事實(shí)表到維度表,用維度表的主鍵做join的條件??梢钥闯?,一共有5個(gè)條件都是用主鍵來做join,其中還有比較特殊的一個(gè)條件,用畫的虛線標(biāo)記,它的兩邊互相都不是用主鍵來做的join。
這個(gè)join算法下一步還會(huì)有一個(gè)聯(lián)合優(yōu)化,最后可以跟謂詞推斷聯(lián)合使用,形成一個(gè)新的優(yōu)化。因?yàn)榭梢钥吹絪upplier和customer有一個(gè)join條件,supplier跟nation也有一個(gè)join條件,用的都是同一個(gè)類型T來做決定。我們可以推導(dǎo)出來nation和customer表之間是以類型key做為join的條件,因此我們用黃色的選項(xiàng)表示。
最后實(shí)際生成的最優(yōu)的join順序是從region開始,先跟nation表join,再跟customer表join,再跟orders 表join,再和lineitem表 join,最后跟supplier表 join。這是我們新推斷出來的條件。那么為什么是走這么一個(gè)路線,而不是先把上面這一條路join完成后再join下面這邊的表,是因?yàn)槲覀兛紤]到orders,region這兩張表都有過濾條件,放在一起過濾效果會(huì)更好。這樣會(huì)讓lineitem這張表的行數(shù)減少的速率更快。
執(zhí)行器(Execution)
最后是執(zhí)行器部分,從邏輯計(jì)劃轉(zhuǎn)化到實(shí)際可執(zhí)行的pipeline,執(zhí)行器的好壞對(duì)OLAP系統(tǒng)的執(zhí)行效率影響是非常大的,下面會(huì)做詳細(xì)介紹。
眾所周知,執(zhí)行器有一個(gè)經(jīng)典的火山模型。對(duì)于每一行它是一個(gè)典型的pull模型,從最上層的計(jì)算的operator開始,每次調(diào)用一個(gè)NEXT函數(shù)從下面的節(jié)點(diǎn)去拿一行新的數(shù)據(jù)出來,做完計(jì)算之后,再等待更上層的那個(gè)計(jì)算節(jié)點(diǎn)去調(diào)用next從它這里取走。
火山模型存在一些問題,首先它并沒有做并行化,而是一行一行地處理;而且每一行在不同層級(jí)之間做一次調(diào)用,實(shí)際上會(huì)產(chǎn)生虛函數(shù)的開銷。因?yàn)閚ext在不同編程語言肯定是要做函數(shù)重載,就算是虛函數(shù)開銷也很有可能是比實(shí)際計(jì)算的開銷還大,在整體開銷上會(huì)占相當(dāng)?shù)谋壤?;同時(shí)對(duì)緩存也是非常不友好的,因?yàn)橐恍袛?shù)據(jù)會(huì)跑多個(gè)不同的operation,可能在取下一行的時(shí)候,原來的緩存已經(jīng)被清洗掉了。
MatrixOne的執(zhí)行器是基于push模型,可以把幾個(gè)連續(xù)的operator組成一個(gè)流水線,而且流水線里面流動(dòng)的數(shù)據(jù),并不是一行一行的數(shù)據(jù),而是前面提到的TAE存儲(chǔ)引擎里面的一個(gè)block,包含8192行數(shù)據(jù),對(duì)于一般的數(shù)字類型是可以直接放進(jìn)L1 Cache里面的,對(duì)緩存非常友好。每一個(gè)operator每一次要處理完這8192行才會(huì)喂給下一個(gè)operator,再加上調(diào)度是從最下面的,實(shí)際讀取每一張表的table scan 那個(gè)節(jié)點(diǎn)開始,往上面push。
對(duì)于push模型,是以數(shù)據(jù)為中心而不是以operator為中心,它的生成過程是對(duì)上一步的planner和optimizer生成的邏輯計(jì)劃,作一個(gè)后續(xù)遍歷,后續(xù)遍歷之后就可以得到一個(gè)基礎(chǔ)的pipeline結(jié)構(gòu),這個(gè)基礎(chǔ)的pipeline結(jié)構(gòu)還沒有帶上比如每臺(tái)機(jī)器有多少個(gè)CPU或者需要在多少個(gè)CN節(jié)點(diǎn)上去執(zhí)行這樣的信息。在后面實(shí)際執(zhí)行的時(shí)候,再動(dòng)態(tài)地根據(jù)這些基礎(chǔ)信息去做擴(kuò)展。
舉個(gè)簡(jiǎn)單的例子,假設(shè)一個(gè)簡(jiǎn)單的查詢,有R、S、T三張表做join,其中R表是最大的一個(gè)表,S表和T表相對(duì)比較小,并且每個(gè)表都有過濾條件。對(duì)于一個(gè)典型的hash join,我們會(huì)把S和T這兩張表去構(gòu)建hash表,然后R表在這兩個(gè)hash表上面依次去做探測(cè)(Probe)操作,得到j(luò)oin之后的數(shù)據(jù)。這么一個(gè)邏輯計(jì)劃至少需要插上三個(gè)pipeline,S表的讀取數(shù)據(jù),做完過濾之后再建完hash表就在這里終止,T表也是在建完hash表之后就在join算子上面終結(jié)掉。但是最大的R表它始終是要做probe的,這張表的pipeline就可以往上走很多步。比如先做完過濾,再跟S表join之后,仍然以批量數(shù)量。然后這個(gè)批會(huì)繼續(xù)往下走,在下一個(gè)join中,仍然在同一個(gè)pipeline的下一個(gè)operator里面,再跟T表做一個(gè)join。所以一個(gè)3表join通常會(huì)拆成3個(gè)pipeline出來。
右圖還包括了數(shù)據(jù)并行的信息,比如S表可能會(huì)使用go語言里的三個(gè)協(xié)程并行的去讀數(shù)據(jù),再做合并操作,合并完之后構(gòu)建hash表,T表也是用三個(gè)協(xié)程去并行的讀,讀完之后,然后送到這里的構(gòu)建hash表。R表因?yàn)楸容^大,pipeline會(huì)展開出更多的實(shí)際pipeline出來。我們可以看到就是R表這個(gè)pipeline是不會(huì)被阻斷的,通過hash operator之后,會(huì)繼續(xù)進(jìn)到下一個(gè)join節(jié)點(diǎn)。
如上圖,我們的pipeline提供了這些算子,比較典型的有聚合、分組和各種join操作。這里把merge和connector、dispatch的顏色標(biāo)識(shí)成不一樣,因?yàn)樗鼈兒推渌黲perator的區(qū)別是其它算子都是只能在一個(gè)pipeline的中間,接受的數(shù)據(jù)是從上一個(gè)算子傳過來,發(fā)送的數(shù)據(jù)就直接發(fā)送給下一個(gè)算子去做后續(xù)的計(jì)算。而標(biāo)成灰色的這一部分是在一個(gè)pipeline的數(shù)據(jù)的source或者sink,即入口或者出口的地方,比如merge它會(huì)去其他的pipeline去接收數(shù)據(jù),把所有pipeline的數(shù)據(jù)合并成一個(gè),返回給用戶。同理group by或者order by算子,也會(huì)執(zhí)行merge操作。
發(fā)送也分為兩類:
- connector算子是一對(duì)一的發(fā)送。
- dispatch算子是一對(duì)多的發(fā)送。
dispatch會(huì)有很多不同的模式:
- 一種是廣播的模式,假設(shè)S表是一個(gè)很小的表,構(gòu)建完hash表之后,會(huì)把hash表廣播到不同的pipeline出來去做計(jì)算;
- 一種是做shuffle,假如S表和R表都比較大,因此要做shuffle join,那么直接會(huì)通過一個(gè)shuffle dispatch算子把數(shù)據(jù)發(fā)送到不同的對(duì)應(yīng)的一個(gè)pipeline上面去。
對(duì)于OLAP系統(tǒng),從語義上來說通常跟SQL本身沒什么關(guān)系。但是OLAP的分析性查詢會(huì)是比較復(fù)雜的計(jì)算任務(wù),有一些SQL能力是必須具備的,比如多表join、子查詢、窗口函數(shù),還有CTE 和Recursive CTE,以及用戶自定義函數(shù)等。MatrixOne目前已經(jīng)具備這些能力。