基于ES的開源分布式SQL數(shù)據(jù)庫,CrateDB適用于哪些場景?
今天的分享主要包含以下幾個方面的內(nèi)容:
- CrateDB介紹
- CrateDB在攜程的實踐
- CrateDB在攜程的優(yōu)化
- 總結(jié)
一、CrateDB介紹
1、CrateDB
CrateDB是一款基于ElasticSearch的分布式數(shù)據(jù)庫,它與ElasticSearch最大的區(qū)別是提供了ANSI SQL查詢訪問接口。ElasticSearch在6.X版本以后,也開始提供SQL的查詢,但CrateDB與ElasticSearch相比,能夠支持多索引之間的關(guān)聯(lián)查詢,針對某些聚合函數(shù),它返回的是精確的查詢結(jié)果,而ElasticSearch返回的是近似值。
2、CrateDB的特性
- 適用于海量時序數(shù)據(jù)存儲
CrateDB適用于海量時序數(shù)據(jù)存儲,需要頻繁更改的數(shù)據(jù)使用CrateDB存儲效果較差。因為CrateDB基于ElasticSearch,頻繁的刪改操作會使它的性能大大受損。
- 高可靠水平可擴(kuò)
CrateDB繼承了ElasticSearch設(shè)計中高可靠的優(yōu)點,集群較方便實現(xiàn)擴(kuò)容,對于一些點查詢或復(fù)雜度中等的查詢均能夠較為實時地返回結(jié)果。
- 支持Dynamic Schema
CrateDB支持Dynamic Schema,其最新版本能夠支持json數(shù)據(jù)格式,寫入數(shù)據(jù)更加方便。
我認(rèn)為CrateDB的初衷是用SQL的方式查詢訪問基于ElasticSearch存儲的數(shù)據(jù)。基于這一概念,我們可以看到它大概的分層(如上圖所示),從外部訪問從下到上依次到達(dá)最終的存儲,其最外一層提供了PostgresSQL兼容的訪問協(xié)議和REST API的訪問協(xié)議,接下來對語句進(jìn)行解析,然后執(zhí)行,獲取存儲在各個節(jié)點上的數(shù)據(jù)。
3、海量數(shù)據(jù)存儲對比
因為類似技術(shù)較多,這里只對比幾個典型的技術(shù),CrateDB、ElasticSearch以及MongoDB,這三者都可以歸類于Nosql。下文將從7個維度對三者進(jìn)行對比。
1)Schema支持類型
這三個數(shù)據(jù)庫均支持Dynamic Schema。但在現(xiàn)實的生產(chǎn)環(huán)境下,我們推薦采用Struct Schema,因為Dynamic Schema可能會帶來種種問題。
僅代表個人觀點,并非適用于所有場景。
2)是否支持SQL訪問
SQL誕生四十多年,已成為非常成熟的語言,具有極強(qiáng)的表達(dá)能力。同時SQL具有通用性,被大家普遍接受。CrateDB基于SQL的通用性不斷發(fā)展,其支持ANSI SQL,并且采用了PostgreSQL協(xié)議。
ElasticSearch起初只支持類json格式的查詢語法,之后開始提供針對單索引的一些SQL語句支持函數(shù),并不斷豐富。MongoDB據(jù)我所知并未直接支持SQL,如果寫入SQL語句,需要通過第三方插件才能夠被MongoDB識別,這在一定程度上會影響查詢性能。
3)可擴(kuò)展性
從可擴(kuò)展性角度出發(fā),CrateDB和ElasticSearch采用gossip協(xié)議組建集群,簡單來說節(jié)點之間相應(yīng)對等。在一個ElasticSearch集群中,節(jié)點可分Master、Coordinator,以及承載數(shù)據(jù)的Data,一個節(jié)點可以同時扮演三個不同的角色,因此它們是對等的。
MongoDB則不同,如果用它來構(gòu)建一個分布式集群,最起碼有三個不同的Host,分別是Config Server、Mongos以及Data,為了實現(xiàn)高可靠,一個分片還需要分成相應(yīng)的Master或Slave。
綜上所述,從可擴(kuò)展角度來看,ElasticSearch和CrateDB更好。
4)對于關(guān)聯(lián)分析的支持程度
CrateDB支持跨索引之間的關(guān)聯(lián)分析,而ElasticSearch則使用一些變通的方式支持此類關(guān)聯(lián)查詢,這意味著在寫入數(shù)據(jù)時需要做相應(yīng)變更。MongoDB在4.X版本時不支持關(guān)聯(lián)查詢,之后的版本未及時關(guān)注,如描述有誤,歡迎大家指正。
5)聚合準(zhǔn)確度
CrateDB和MongoDB返回精確值,ElasticSearch則是返回近似值,雖然返回近似值執(zhí)行速度快,但其計算的準(zhǔn)確度會受到一定影響。
6)性能
在查詢性能方面, CrateDB和ElasticSearch都能夠較好地返回查詢結(jié)果,上圖中列出的耗時為100毫秒。對于較為簡單的查詢,100毫秒算是較高的消耗,事實上可以在更短的時間內(nèi)返回結(jié)果。后文中會提到我們自己質(zhì)量環(huán)境下的實際耗時。
7)運維
引入一項新技術(shù)后,其帶來的運維復(fù)雜度十分關(guān)鍵。CrateDB和ElasticSearch相較于MongoDB運維復(fù)雜度更低。
4、CrateDB系統(tǒng)架構(gòu)及節(jié)點類型
上文中提到在CrateDB和ElasticSearch中節(jié)點之間相互對等。以ElasticSearch舉例,由5個節(jié)點構(gòu)成的ElasticSearch集群中起碼有兩個不同的角色。
- Master
該角色需要負(fù)責(zé)兩個方面的工作,分別是管理節(jié)點和管理索引。節(jié)點加入集群,在集群中創(chuàng)建了多少個不同的索引,這些索引的分片分布在哪些機(jī)器上,這些信息都由 Master來管理。
- 數(shù)據(jù)節(jié)點
我們創(chuàng)建好的索引,數(shù)據(jù)最終要落到一個具體的ElasticSearch節(jié)點上,這些最終承載數(shù)據(jù)的就是數(shù)據(jù)節(jié)點。
上圖右半部分所示為在生產(chǎn)上部署一個CrateDB或ElasticSearch集群。最上方的負(fù)載均衡部分可有可無。除上文提到的兩種節(jié)點類型外,還有一種叫做Coordinator的節(jié)點類型,它既不承載具體的數(shù)據(jù),也不扮演Master的角色,只接受外部的請求,并將外部請求路由到數(shù)據(jù)節(jié)點上做具體查詢,然后在Coordinator節(jié)點做一些匯總,最后返回給應(yīng)用程序。除此之外,ElasticSearch中可能還會有一個叫Ingest的節(jié)點類型,這里不進(jìn)行過多闡述。
綜上所述,一個CrateDB的表類似于一個ElasticSearch的索引,ElasticSearch中索引由多個不同的分片組成,每一個分片可能會落到某一個數(shù)據(jù)節(jié)點上。為了實現(xiàn)高可靠,一個分片又分成主分片和副本分片,即圖中列出的Primary和Secondary。
5、CrateDB具體操作
1)表創(chuàng)建
這個操作和我們平時用PostgreSQL或MySQL創(chuàng)建一張表并無很大差別。
創(chuàng)建一張職工的表(如上圖所示),其中包括姓名、年齡、性別以及住址。這張表根據(jù)姓名來進(jìn)行哈希,哈希的結(jié)果分到4個不同的分片中,with后面跟著一些針對索引層面的配置,它的配置項多達(dá)幾十項。我們最主要關(guān)注以下幾點:
- 分片的副本數(shù)
如果只有主分片,replica數(shù)為0。如果在主分片之外,還有別的副本分片,增加相應(yīng)的replica數(shù)即可。
- refresh_interval
ElasticSearch進(jìn)行刷新數(shù)據(jù)會從內(nèi)存刷新到磁盤,不斷刷新會降低性能。為了保證更多數(shù)據(jù)留在內(nèi)存中,減少刷新的次數(shù),我們可以調(diào)節(jié)刷新間隔,具體調(diào)整根據(jù)對數(shù)據(jù)的新鮮度要求而定。數(shù)據(jù)只有被刷新后才能被搜索到。
- translog.sync_interval
ElasticSearch采用的是write ahead log的方式,這意味著有大量的translog。translog同樣將數(shù)據(jù)從內(nèi)存寫到磁盤,這當(dāng)中有一個sync的間隔,如果調(diào)高這一間隔,可能會加快寫入速度,但也有可能帶來容錯方面的問題。
2)樂觀并發(fā)控制
CrateDB是基于ElasticSearch的數(shù)據(jù)庫,其在ElasticSearch基礎(chǔ)上進(jìn)行了叫做樂觀并發(fā)控制的演變。我們將數(shù)據(jù)寫入到某一張表時,有兩個隱藏的列,一個是sequence_number,即這一列的版本號,另一個為primary_term,二者聯(lián)合使用可以實現(xiàn)某一版本的數(shù)據(jù)只更新一次,避免頻繁更新。
以上圖中的語句為例,對sequence_number等于0進(jìn)行更新,當(dāng)這條語句執(zhí)行成功后,它的sequence_number會自動跳到1,每更新一次,這個值就會遞增。如果有兩個不同的進(jìn)程或兩個不同的外部訪問,試圖來更新同一條語句,那么只有一條會被執(zhí)行成功,這就做到了樂觀并發(fā)控制。
3)Partitioned Table
CrateDB與ElasticSearch不同,它引入了Partitioned Table的概念,即所謂的分區(qū)表。
上文中講到一個表存在多個分片承載數(shù)據(jù),即ElasticSearch的一個索引有多個不同的分片,對應(yīng)到CrateDB中是分區(qū),CrateDB中的分區(qū)可以與ElasticSearch中的別名相對應(yīng)。
如果我們要查詢或?qū)懭氡淼臄?shù)據(jù)量達(dá)幾十億或上百億,將這些表都放到同樣一個索引當(dāng)中,可能會導(dǎo)致查詢與寫入的速度變慢,我們其實可以把這些數(shù)據(jù)分成多個不同的分區(qū)。
在我們實際的生產(chǎn)中有這樣一種情況,一些坐過飛機(jī)的用戶可能希望查看自己的飛行足跡,如果將所有用戶的歷史數(shù)據(jù)都放在同一個索引中,經(jīng)過查詢最后在前端展現(xiàn)的話,速度可能會較慢,因為這一操作對接口的要求較高。
例如要求在50毫秒內(nèi)返回結(jié)果,如果不把這些數(shù)據(jù)做分區(qū)的話,查詢會很慢。此處的慢是99%line的情況,在此情況下,我們要達(dá)到滿足性能指標(biāo),其中一個變通方法就是把它拆成多個不同的分區(qū),每個uid進(jìn)入后只需要到對應(yīng)的分區(qū)表查詢即可。
在做分區(qū)的時候有一點需要注意,如果表已經(jīng)創(chuàng)建了組件,分區(qū)的字段必須都屬于組件字段的列表,因為這個組件可以由一個列或多個列組成,也可能是一種復(fù)合的組件,分區(qū)的字段必須在組件的字段列表當(dāng)中。
二、CrateDB在攜程的實踐
1、實時聚合分析
上圖是我們使用CrateDB之后進(jìn)行的比較,圖中只比較了CrateDB和Presto,我們當(dāng)時的場景如下。
我們有不少的表,每張表的數(shù)據(jù)量都有幾千萬條,有的甚至上億條,需要對數(shù)據(jù)做比較復(fù)雜的聚合。原來是用Presto查詢,因為它是一個看板,每次刷新的間隔延遲較大,為了解決這個問題,我們嘗試了一些方法,后來發(fā)現(xiàn)用CrateDB效果較好,右側(cè)是性能對比,收益十分明顯。
1)具體分析場景
- 國內(nèi)產(chǎn)品/業(yè)務(wù)/收益數(shù)據(jù)分析;
- 主要對常用產(chǎn)量收益(多維度)進(jìn)行監(jiān)控;
- 進(jìn)行拆分下鉆分析;
- 進(jìn)行了sum、between、groupby、case when、left join、union all等操作。
在性能對比方面,采用CrateDB后,我們基本上能夠在1~2秒之內(nèi)返回結(jié)果。
2、海量數(shù)據(jù)存儲以及實時查詢
在我們實際的生產(chǎn)中有不少實時數(shù)據(jù)聚合分析的調(diào)用。
起初,我們是將數(shù)據(jù)放入Redis中,每收到一次取數(shù)請求,我們都會進(jìn)行相應(yīng)的代碼開發(fā),把取出的數(shù)據(jù)進(jìn)行相應(yīng)解析,處理之后返回給調(diào)用方。這個需求雖然不復(fù)雜,但是因為我們沒有辦法注入數(shù)據(jù)分析的邏輯,所以不得不進(jìn)行代碼工作。
引入CrateDB后,我們可以將分析工作采用SQL的方式來實現(xiàn),對于那些用SQL分析不能完全解決掉的剩余部分,則聯(lián)合一些Groovy腳本完成。
基于這樣的理念,我們開發(fā)了一個模板,我們將SQL寫入模板中,指定從哪個表中取數(shù),如何分析,決定取完數(shù)后是否需要進(jìn)行定制的后續(xù)處理,如果需要,則執(zhí)行相應(yīng)的Groovy的腳本,最后返回結(jié)果。這一套流程大大節(jié)省了開發(fā)的周期,提升了開發(fā)的效率。
除開發(fā)周期對比外,存儲方面的對比也十分顯著。例如數(shù)據(jù)放入到Redis中,需要200g內(nèi)存,用CrateDB來存,可能只需要50g,這不僅是數(shù)據(jù)量上的減少,同時意味著成本的大大縮減。在攜程,有基于RocksDB的存儲,它開發(fā)有Redis兼容協(xié)議,可以做到把數(shù)據(jù)存儲到磁盤上,同時可以用Redis的接口訪問。
我們將數(shù)據(jù)存入了磁盤,分別從均線、95%line、99%line三方面對比性能。均線方面還在可以忍受的范圍內(nèi),當(dāng)然CrateDB不可能比Redis更快。從上圖中可以看出,除99.9%line的時候差距大一點,其他均在可接受的范圍內(nèi)。在數(shù)據(jù)導(dǎo)入耗時方面,我們運用Spark將數(shù)據(jù)導(dǎo)入CrateDB,兩者差距不是特別大。
三、CrateDB在攜程的優(yōu)化
1、落地時的調(diào)優(yōu)
當(dāng)我們將CrateDB引入整體的技術(shù)方案中時,還需要進(jìn)行一些調(diào)優(yōu)。
1)磁盤空間調(diào)優(yōu)
為了避免大量磁盤空間的消耗,需要對索引層面進(jìn)行優(yōu)化。除此之外,還可以進(jìn)行聚合優(yōu)化,關(guān)閉列存儲。
2)update操作優(yōu)化
為了提升 update操作的性能,我們建議先insert,然后再刪除已有的數(shù)據(jù)。為了達(dá)到目的,可以加上相應(yīng)的版本號,每次只取最新版本的數(shù)據(jù)。對于在線更新的需求需要做轉(zhuǎn)換,這也意味著采用CrateDB所能夠支持的場景是有受限的,對于嚴(yán)格要求一致,或更新頻繁的場景,CrateDB不是很好的選擇。
3)查詢優(yōu)化
上文中提到采用分區(qū)加多個分片的方式優(yōu)化表結(jié)構(gòu)的存儲,使得每一次查詢只需要去查盡可能少的分區(qū)或分片,查的數(shù)據(jù)越少、越精準(zhǔn),時間消耗就越短。
4)過期數(shù)據(jù)刪除優(yōu)化
2、Spark數(shù)據(jù)導(dǎo)入
在數(shù)據(jù)導(dǎo)入CrateDB時,我們可能會用 Spark進(jìn)行操作,此處向大家分享這一過程中的一個細(xì)節(jié)點。
此處用分區(qū)舉例,如果有一個十幾億或幾億的用戶ID,還有一些關(guān)聯(lián)數(shù)據(jù),要把它均勻地落到每個分區(qū)上,有一種比較簡單的方法。我們把 uid(一串字符)進(jìn)行相應(yīng)的MD5,MD5之后,取前兩位或后兩位,就可以得到256個分片。256分片顯然太多了,可以再除以一個系數(shù),減少分片數(shù),就可以讓這些數(shù)據(jù)均勻分布,這樣可以做到分片上承載的數(shù)據(jù)量是差不多的。
這樣做的挑戰(zhàn)是在寫Spark程序時,怎樣讓每一個partition當(dāng)中的數(shù)據(jù)都是落入同一個分片的內(nèi)容,大家可能會想到repartition函數(shù),但repetition是對某個字段進(jìn)行哈希,并不能保證落到同一個 partition的數(shù)據(jù),這時我們就需要去制定 partition。上圖右側(cè)寫出了一些偽碼,我們在spark中定義一個repartition,然后重載,顯示這里可能會有多少個不同的分片。
假設(shè)我們剛才取前兩位或取后兩位,然后除以4得到64個分片的話,那么我們把傳進(jìn)來的數(shù)字跟64取模就對應(yīng)到某一個具體的partition的位置。在Spark中有partitionBy,partitionBy只支持rdd算子,DataFrame中沒有partitionBy的算子,所以我們需要先把DataFrame或者DataSet轉(zhuǎn)成rdd,通過組成一個 key鍵值對的方式進(jìn)行partitionBy操作。之后還需要將相應(yīng)的rdd轉(zhuǎn)換回DataFrame,這樣就可以得到一個分布很均勻的 DataFrame,再將其寫入CrateDB中,就能達(dá)到很快的寫入速度。
3、運維自動化嘗試
我當(dāng)時是用 Rancher、OpenEBS,以及Nginx Ingress實現(xiàn)了一個在K8S上的CrateDB集群,這使得我們在云環(huán)境去部署CrateDB成為一種可能,部署到云上,即便是私有云上,也可以提高硬件使用率,這也是我的初衷。
4、CrateDB admin UI
CrateDB安裝完成后,會打開上圖所示的操作界面,我們能夠直接寫入查詢語句,也可以方便地觀測到整個集群的狀況。
四、總結(jié)
1、CrateDB的適用場景
- 單點查詢
- 寫入少,查詢多
- 時序數(shù)據(jù)存儲
- 全文本查詢
2、CrateDB的不足
- Upsert性能較低
- 僅支持NRT查詢
- 高階SQL函數(shù)有待實現(xiàn)
- 不支持事務(wù)
Q&A
Q1:CrateDB有解決ES字段類型無法修改、寫入性能較低和高硬件資源消耗等痛點嗎?
A1:首先,CrateDB支持修改字段類型,這個字段類型的修改和PostgreSQL中相同,可以將varchar改成text,但將varchar類型直接改成time stamp可能就會有問題,這時就不得不從重寫或者是進(jìn)行轉(zhuǎn)換。其次,寫入性能高低分場景,如果只是單獨insert的話,它的性能還是很高的,如果是upsert,或delete與insert摻雜在一起的話,這種混雜這種模式的話,寫入性能就會有一些問題,需要進(jìn)行相應(yīng)的變通。變通的方式有兩種,第一種是先把新數(shù)據(jù)insert,再把老數(shù)據(jù)delete。第二種方式是新數(shù)據(jù)較小的話,可以寫入一張另外的臨時表中,臨時表和新的表進(jìn)行關(guān)聯(lián),再做相應(yīng)的update。
Q2:CrateDB 相比于 Elasticsearch 和 MongoDB ,備份和恢復(fù)能力如何?
A2:CrateDB和Elasticsearch在備份和恢復(fù)能力層面一樣,但是和MongoDB相比,可能更加直觀和容易,這是我個人的理解。恢復(fù)方面,如果你要求寫入時所有數(shù)據(jù)都吐到磁盤之后才返回,那么所有數(shù)據(jù)應(yīng)該都是全部無丟失的。
Q3:CrateDB運行一段時間性能會明顯降低,除了重啟還有什么方案?
A3:CrateDB在實際運維中確實會碰到一些問題,但是我沒有碰到性能明顯下降的情況。如果有的話,你可以進(jìn)行索引級別的重建,而不是整個集群的重啟,因為集群重啟帶來的成本較高。
Q4:CrateDB日志分析能力如何,有繼承ES的ELK能力嗎?
A4:在與Logstash和Kibana搭配這一層面,還是ES能力更強(qiáng)。從整個生態(tài)圈的角度來看,CrateDB還是不能和Elasticsearch相比的,因為Elasticsearch的發(fā)展時間久,然后有Logstash和Kibana的加持,在數(shù)據(jù)的可視化還有分析展現(xiàn)層面確實很強(qiáng),但是CrateDB可以和另外幾個開源的產(chǎn)品搭配使用,比如說Apache Superset但是肯定沒有Kibana那種原生定制的強(qiáng)大。
Q5:如果把CrateDB部署在k8s上,數(shù)據(jù)存儲應(yīng)該怎么存放,是分布存儲,本地存儲,還是集中存儲?
A5:上文中提到需要和OpenEBS或Rancher結(jié)合,它是分布式處理的,你的節(jié)點要附著于相應(yīng)的存儲機(jī)器上面,即使Docker掛了,數(shù)據(jù)是不會丟失掉的。
Q6:CrateDB貴司用在TP場景多還是AP場景多?
A6:我們用到的是 AP場景,實時數(shù)據(jù)的聚合返回結(jié)果的,當(dāng)然每一次查詢所命中的數(shù)據(jù)集并不是特別大,我們要查詢的數(shù)據(jù)集可能是很大的,但是真正被查詢條件所命中的還是比較少的,可能是幾十萬。
Q7:CrateDB 的對標(biāo)競品是什么,和大數(shù)據(jù)生態(tài)圈比如hadoop有互補嗎 ?
A7:CrateDB不是跟Hadoop相競爭,它們兩個應(yīng)該在不同的層面,因為Hadoop是進(jìn)行離線數(shù)據(jù)存儲的,而CrateDB是做數(shù)據(jù)分析的。如果要尋找對標(biāo)競品的話,我個人認(rèn)為TimescaleDB是一個很強(qiáng)的競品,因為它們都號稱是時序數(shù)據(jù)庫,同時也提供ANSI SQL的查詢標(biāo)準(zhǔn)。從現(xiàn)在的態(tài)勢來看,可能TimescaleDB獲得的用戶群更多一點。