精品国产亚洲一区二区三区|亚洲国产精彩中文乱码AV|久久久久亚洲AV综合波多野结衣|漂亮少妇各种调教玩弄在线

<blockquote id="ixlwe"><option id="ixlwe"></option></blockquote>
  • <span id="ixlwe"></span>

  • <abbr id="ixlwe"></abbr>

    MongoDB的水平擴(kuò)展,你做對了嗎?

    大數(shù)據(jù)

    作者:趙翼

    分布式數(shù)據(jù)庫的前世今生

    當(dāng)人們一開始使用數(shù)據(jù)庫系統(tǒng)的時候,所有數(shù)據(jù)都是跑在一臺服務(wù)器上,即所謂的單機(jī)數(shù)據(jù)庫服務(wù)器。在企業(yè)級應(yīng)用中,我們會搭建一臺應(yīng)用程序服務(wù)器,一般它會被運(yùn)行在一臺服務(wù)器或者工作站上,大多數(shù)情況下采用 Linux/Unix/Windows 操作系統(tǒng),也有人把這樣的服務(wù)器稱之為應(yīng)用程序服務(wù)器。顧名思義,他的作用是處理復(fù)雜的業(yè)務(wù)邏輯。但是一點(diǎn)需要注意的是,在這樣的構(gòu)架中,這臺應(yīng)用程序服務(wù)器不會存儲任何業(yè)務(wù)數(shù)據(jù),也就是說,他只負(fù)責(zé)邏輯運(yùn)算,處理用戶請求,真正存放數(shù)據(jù)的地方是前面提到的那臺數(shù)據(jù)庫服務(wù)器。應(yīng)用程序服務(wù)器將用戶的請求轉(zhuǎn)換成數(shù)據(jù)庫語言(通常是 SQL),運(yùn)行在數(shù)據(jù)庫中,從而進(jìn)行數(shù)據(jù)的增刪改查。數(shù)據(jù)庫服務(wù)器不會對外直接開放,管理人員也不允許直接在數(shù)據(jù)庫層面操作數(shù)據(jù)。所有的操作都會經(jīng)過應(yīng)用程序服務(wù)器來完成。應(yīng)用程序?qū)印?shù)據(jù)庫層再加上 UI 層,被稱為傳統(tǒng)的 Web 三層構(gòu)架。

    Replication

    隨著數(shù)據(jù)量的增大,技術(shù)的不斷進(jìn)步以及需求的增加,安全性、可靠性、容錯性、可恢復(fù)性等因素被人們考慮進(jìn)數(shù)據(jù)庫的設(shè)計中。于是出現(xiàn)了分布式數(shù)據(jù)庫系統(tǒng)。以前在存儲數(shù)據(jù)的時候,都是采用單體構(gòu)架模式,及數(shù)據(jù)全部存儲在一臺數(shù)據(jù)庫中,一旦數(shù)據(jù)庫出現(xiàn)問題,所有的應(yīng)用請求都會受到影響。數(shù)據(jù)庫的恢復(fù)也是一個令人頭疼的問題。有時一次數(shù)據(jù)庫的全恢復(fù)會運(yùn)行幾個小時甚至幾天的時間。在互聯(lián)網(wǎng)應(yīng)用不斷普及的今天,業(yè)務(wù)需求對構(gòu)架產(chǎn)生了嚴(yán)峻的挑戰(zhàn)。沒有哪個互聯(lián)網(wǎng)應(yīng)用會允許若干小時的宕機(jī)時間。分布式數(shù)據(jù)庫的產(chǎn)生,為我們提供了技術(shù)上的解決方案。在部署數(shù)據(jù)庫的時候,不用于以前的單體應(yīng)用,分布式下數(shù)據(jù)庫部署包括多點(diǎn)部署,一套業(yè)務(wù)應(yīng)用數(shù)據(jù)庫被分布在多臺數(shù)據(jù)庫服務(wù)器上,分主從服務(wù)器。主服務(wù)器處理日常業(yè)務(wù)請求,從服務(wù)器在運(yùn)行時不斷的對主服務(wù)器進(jìn)行備份,當(dāng)主服務(wù)器出現(xiàn)宕機(jī)、或者運(yùn)行不穩(wěn)定的情況時,從服務(wù)器會立刻替換成主服務(wù)器,繼續(xù)對外提供服務(wù)。此時,開發(fā)運(yùn)維人員會對出現(xiàn)問題的服務(wù)器進(jìn)行搶修、恢復(fù),之后再把它投入到生產(chǎn)環(huán)境中。這樣的構(gòu)架也被稱作為高可用構(gòu)架,它支持了災(zāi)難恢復(fù),為業(yè)務(wù)世界提供了可靠的支持,也是很多企業(yè)級應(yīng)用采用的主流構(gòu)架之一。需要指出的是,在這樣的主從設(shè)計中,從數(shù)據(jù)庫常常被設(shè)計成只讀,主數(shù)據(jù)庫支持讀寫操作。一般會有一臺主數(shù)據(jù)庫連接若干臺從數(shù)據(jù)庫。在互聯(lián)網(wǎng)產(chǎn)品的應(yīng)用中,人們大多數(shù)情況下會對應(yīng)用服務(wù)器請求讀操作,這樣應(yīng)用服務(wù)器可以把讀操作請求分發(fā)到若干個從數(shù)據(jù)庫中,這樣就避免了主數(shù)據(jù)庫的并發(fā)請求次數(shù)過高的問題。至于為什么大多數(shù)應(yīng)用都是讀操作,你可以想一下在你使用微信或者微博的時候,你是看別人發(fā)布的圖片多還是自己發(fā)布的時候多。當(dāng)你不斷下滑屏幕,刷新朋友圈,這些都是讀請求。只有當(dāng)評論、點(diǎn)贊、分享的時候才會進(jìn)行寫操作。

    我們的世界就是這樣,當(dāng)技術(shù)為人們解決了現(xiàn)實(shí)問題以后,新的需求會層出不窮。智能手機(jī),互聯(lián)網(wǎng) +,創(chuàng)業(yè)潮的不斷興起,點(diǎn)燃了這樣一個擁有幾千年文明歷史的民族的激情。各種新點(diǎn)子、新概念不斷的涌現(xiàn),誰的手機(jī)里沒有安裝幾十個互聯(lián)網(wǎng)應(yīng)用,從訂餐,快遞,到住房,旅游,再到教育,養(yǎng)老,那一個環(huán)節(jié)沒有互聯(lián)網(wǎng)的支持,沒有技術(shù)的成分。我們就是生存在這樣一個的平凡而又不乏豪情的社會中。許許多多的需求和數(shù)據(jù)充斥著我們的構(gòu)架,挑戰(zhàn)著我們的存儲。

    對此,你可能已經(jīng)想到,前面提到的分布式數(shù)據(jù)庫多點(diǎn)部署是不是會存在大量的瓶頸。比如,在主從數(shù)據(jù)庫結(jié)構(gòu)中,從數(shù)據(jù)庫的內(nèi)容基本上可以說是主數(shù)據(jù)庫的一份全拷貝,這樣的技術(shù)稱之為Replication。Replication在實(shí)現(xiàn)主從數(shù)據(jù)同步時,通常采用Transaction Log的方式,比如,當(dāng)一條數(shù)據(jù)插入到主數(shù)據(jù)庫的時候,主數(shù)據(jù)庫會像Trasaction Log中插入一條記錄來聲明這次數(shù)據(jù)庫寫紀(jì)錄的操作。之后,一個Replication Process會被觸發(fā),這個進(jìn)程會把Transaction Log中的內(nèi)容同步到從數(shù)據(jù)庫中。整個過程如下圖所示:

    大數(shù)據(jù)

    對于數(shù)據(jù)庫的擴(kuò)展來說,通常有兩種方法,水平擴(kuò)展和垂直擴(kuò)展。

    垂直擴(kuò)展:這種擴(kuò)展方式比較傳統(tǒng),是針對一臺服務(wù)器進(jìn)行硬件升級,比如添加強(qiáng)大的 CPU,內(nèi)存或者添加磁盤空間等等。這種方式的局限性是僅限于單臺服務(wù)器的擴(kuò)容,盡可能的增加單臺服務(wù)器的硬件配置。優(yōu)點(diǎn)是構(gòu)架簡單,只需要維護(hù)單臺服務(wù)器。水平擴(kuò)展:這種方式是目前構(gòu)架上的主流形式,指的是通過增加服務(wù)器數(shù)量來對系統(tǒng)擴(kuò)容。在這樣的構(gòu)架下,單臺服務(wù)器的配置并不會很高,可能是配置比較低、很廉價的 PC,每臺機(jī)器承載著系統(tǒng)的一個子集,所有機(jī)器服務(wù)器組成的集群會比單體服務(wù)器提供更強(qiáng)大、高效的系統(tǒng)容載量。這樣的問題是系統(tǒng)構(gòu)架會比單體服務(wù)器復(fù)雜,搭建、維護(hù)都要求更高的技術(shù)背景。MongoDB 中的 Sharding 正式為了水平擴(kuò)展而設(shè)計的,下面就來擠開 shard 面紗,探討一下 shard 中不同分片的技術(shù)區(qū)別以及對數(shù)據(jù)庫系統(tǒng)的影響。

    分片 (Shard)

    前面提到的 Replication 結(jié)構(gòu)可以保證數(shù)據(jù)庫中的全部數(shù)據(jù)都會有多分拷貝,數(shù)據(jù)庫的高可用可以保障。但是新的問題是如果要存儲大量的數(shù)據(jù),不論主從服務(wù)器,都需要存儲全部數(shù)據(jù),這樣檢索必然會出現(xiàn)性能問題。可以這樣講,Replication只能算是分布式數(shù)據(jù)庫的第一階段。主要解決的是數(shù)據(jù)庫高可用,讀數(shù)據(jù)可以水平擴(kuò)展,部分解決了主數(shù)據(jù)并發(fā)訪問量大的問題。但是它并沒有解決數(shù)據(jù)庫寫操作的分布式需求,此外在數(shù)據(jù)庫查詢時也只限制在一臺服務(wù)器上,并不能支持一次查詢多臺數(shù)據(jù)庫服務(wù)器。我們假設(shè),如果有一種構(gòu)架,可以實(shí)現(xiàn)數(shù)據(jù)庫水平切分,把切分的數(shù)據(jù)分布存儲在不同的服務(wù)器上,這樣當(dāng)查詢請求發(fā)送到數(shù)據(jù)庫時,可以在多臺數(shù)據(jù)庫中異步檢索符合查詢條件的語句,這樣不但可以利用多臺服務(wù)器的 CPU,而且還可以充分利用不同服務(wù)器上的 IO,顯而易見這樣的構(gòu)架會大大提高查詢語句的性能。但是這樣的實(shí)現(xiàn)卻給數(shù)據(jù)庫設(shè)計者代碼不少麻煩,首先要解決的就是事務(wù)(Transaction),我們知道在進(jìn)行一次數(shù)據(jù)庫寫操作的時候,需要定一個事務(wù)操作,這樣在操作失敗的時候可以回滾到原始狀態(tài),那當(dāng)在分布式數(shù)據(jù)庫的情況下,事務(wù)需要跨越多個數(shù)據(jù)庫節(jié)點(diǎn)以保持?jǐn)?shù)據(jù)的完整性,這給開發(fā)者帶來不少的麻煩。此外,在關(guān)系型數(shù)據(jù)庫中存在大量表關(guān)聯(lián)的情況,分布式的查詢操作就會牽扯到大量的數(shù)據(jù)遷移,顯然這必將降低數(shù)據(jù)庫性能。但是,在非關(guān)系型數(shù)據(jù)庫中,我們?nèi)趸踔寥コ耸聞?wù)和多表關(guān)聯(lián)操作,根據(jù) CAP 理論:在分布式數(shù)據(jù)庫環(huán)境中,為了保持構(gòu)架的擴(kuò)展性,在分區(qū)容錯性不變的前提下,我們必須從一致性和可用性中取其一,那么,從這一點(diǎn)上來理解“NoSQL 數(shù)據(jù)庫是為了保證 A 與 P,而犧牲 C”的說法,也是可以講得通的。同時,根據(jù)該理論,業(yè)界有一種非常流行的認(rèn)識,那就是:關(guān)系型數(shù)據(jù)庫設(shè)計選擇了一致性與可用性,NoSQL 數(shù)據(jù)庫設(shè)計則不同。其中,HBase選擇了一致性與分區(qū)可容忍性,Cassandra選擇了可用性與分區(qū)可容忍性。

    本文關(guān)注于非關(guān)系型數(shù)據(jù)庫中分區(qū)的技巧和性能,以 MongoDB 為例進(jìn)行說明,在下面的章節(jié)中就圍繞這一點(diǎn)展開討論。

    MongoDB 分片原理

    MongoDB 中通過 Shard 支持服務(wù)器水平擴(kuò)展,通過 Replication 支持高可用(HA)。這兩種技術(shù)可以分開來使用,但是在大數(shù)據(jù)庫企業(yè)級應(yīng)用中通常人們會把他們結(jié)合在一起使用。

    MongoDB Sharding

    首先我們簡要概述一下分片在 MongoDB 中的工作原理。通過分片這個單詞我們可以看出,他的意思是將數(shù)據(jù)庫表中的數(shù)據(jù)按照一定的邊界分成若干組,每一組放到一臺 MongoDB 服務(wù)器上。拿用戶數(shù)據(jù)舉例,比如你有一張數(shù)據(jù)表存放用戶基本信息,可能由于你的應(yīng)用很受歡迎,短時間內(nèi)就積攢了上億個用戶,這樣當(dāng)你在這張表上進(jìn)行查詢時通常會耗費(fèi)比較長的時間,這樣這個用戶表就稱為了你的應(yīng)用程序的性能瓶頸。很顯然的做法是對這張用戶表進(jìn)行拆分,假設(shè)用戶表中有一個age年齡字段,我們先做一個簡單的拆分操作,按照用戶的年齡段把數(shù)據(jù)放到不同的服務(wù)器上,以 20 為一個單位,20 歲以下的用戶放到 server1,20 到 40 歲的用戶放到 server2,40-60 歲的用戶放到 server3,60 歲以上放到 server4,后面我們會講這樣的拆分是否合理。在這個例子中,用戶年齡age就是我們進(jìn)行Sharding(切分)的Shard Key(關(guān)于 Shard Key 的選擇后面會詳細(xì)介紹),拆分出來的server1,?server2,?server3和server4就是這個集群中的 4 個Shard(分區(qū))服務(wù)器。好,Shard 集群已經(jīng)有了,并且數(shù)據(jù)已經(jīng)拆分完好,當(dāng)用戶進(jìn)行一次查詢請求的時候我們?nèi)绾蜗蜻@四個 Shard 服務(wù)器發(fā)送請求呢?例如:我的查詢條件是用戶年齡在 18 到 35 歲之間,這樣個查詢請求應(yīng)當(dāng)發(fā)送到server1和server2,因為他們存儲了用戶年齡在 40 以下的數(shù)據(jù),我們不希望這樣的請求發(fā)送到另外兩臺服務(wù)器中因為他們并不會返回任何數(shù)據(jù)結(jié)果。此時,另外一個成員就要登場了,mongos,它可以被稱為 Shard 集群中的路由器,就像我們網(wǎng)絡(luò)環(huán)境中使用的路由器一樣,它的作用就是講請求轉(zhuǎn)發(fā)到對應(yīng)的目標(biāo)服務(wù)器中,有了它我們剛才那條查詢語句就會正確的轉(zhuǎn)發(fā)給server和server2,而不會發(fā)送到server3和server4上。mongos根據(jù)用戶年齡(Shard Key)分析查詢語句,并把語句發(fā)送到相關(guān)的 shard 服務(wù)器中。除了mongos和shard之外,另一個必須的成員是配置服務(wù)器,config server,它存儲 Shard 集群中所有其他成員的配置信息,mongos會到這臺config server查看集群中其他服務(wù)器的地址,這是一臺不需要太高性能的服務(wù)器,因為它不會用來做復(fù)雜的查詢計算,值得注意的是,在 MongoDB3.4 以后,config server必須是一個replica set。理解了上面的例子以后,一個 Shard 集群就可以部署成下圖所示的結(jié)構(gòu):

    大數(shù)據(jù)

    其中:

    shard: 每一個 Shard 服務(wù)器存儲數(shù)據(jù)的一個子集,例如上面的用戶表,每一個 Shard 存儲一個年齡段的用戶數(shù)據(jù)。mongos: 處理來自應(yīng)用服務(wù)器的請求,它是在應(yīng)用服務(wù)器和Shard 集群之間的一個接口。config server: 存儲 shard 集群的配置信息,通常部署在一個 replica set 上。

    MongoDB Shard 性能分析

    環(huán)境準(zhǔn)備

    這樣的服務(wù)器構(gòu)架是否合理,或者說是否能夠滿足數(shù)據(jù)量不斷增長的需求。如果僅僅是通過理論解釋恐怕很難服眾,我已經(jīng)信奉理論結(jié)合實(shí)際的工作方式,所以在我的文章中除了闡述理論之外,一定會有一定的示例為大家驗證理論的結(jié)果。接下來我們就根據(jù)上面的例子做一套本地運(yùn)行環(huán)境。由于 MongoDB 的便捷性,我們可以在任何一臺 PC 上搭建這樣一個數(shù)據(jù)庫集群環(huán)境,并且不限制操作系統(tǒng)類型,任何 Windows/Linux/Mac 的主流版本都可以運(yùn)行這樣的環(huán)境。在本文中,我才用 MongoDB3.4 版本。

    對于如何創(chuàng)建一個 MongoDB Shard 環(huán)境,網(wǎng)上有很多教程和命令供大家選擇,創(chuàng)建一個有 3 個 Mongos,每個 Mongos 連接若干個 Shards,再加上 3 個 config server cluster,通常需要 20 幾臺 MongoDB 服務(wù)器。如果一行命令一行命令的打,即便是在非常熟練的情況下,沒有半個小時恐怕搭建不出來。不過幸運(yùn)的是有第三方庫幫我們做這個事情,大家可以查看一下mtools。他是用來創(chuàng)建各種 MongoDB 環(huán)境的命令行工具,代碼使用python寫的,可以通過pip install安裝到你的環(huán)境上。具體的使用方法可以參考https://github.com/rueckstiess/mtools/wiki/mlaunch。也可以通過https://github.com/zhaoyi0113/mongo-cluster-docker上面的腳本把環(huán)境搭載 Docker 上面。

    下面的命令用來在本地創(chuàng)建一個 MongoDB Shard 集群,包含 1 個mongos路由,3 個shardreplica,每個 replica 有 3 個shard服務(wù)器,3 個config服務(wù)器。這樣一共創(chuàng)建 13 個進(jìn)程。

    mlaunch init --replicaset --sharded 3 --nodes 3 --config 3 --hostname localhost --port 38017 --mongos 1

    服務(wù)器創(chuàng)建好以后我們可以連接到mongos上看一下 shard 狀態(tài),端口是上面制定的 38017。

    mongos> sh.status()--- Sharding Status ---  ?... ?shards: ? ?{ ?"_id" : "shard01", ?"host" : "shard01/localhost:38018,localhost:38019,localhost:38020", ?"state" : 1 } ? ?{ ?"_id" : "shard02", ?"host" : "shard02/localhost:38021,localhost:38022,localhost:38023", ?"state" : 1 } ? ?{ ?"_id" : "shard03", ?"host" : "shard03/localhost:38024,localhost:38025,localhost:38026", ?"state" : 1 } ?active mongoses: ? ?"3.4.0" : 1 ?...

    可以看到剛才創(chuàng)建的 shard 服務(wù)器已經(jīng)加入到這臺 mongos 中了,這里有 3 個 shard cluster,每個 cluster包含 3 個 shard 服務(wù)器。除此之外,我們并沒有看到關(guān)于 Shard 更多的信息。這是因為這臺服務(wù)器集群還沒有任何數(shù)據(jù),而且也沒有進(jìn)行數(shù)據(jù)切分。

    數(shù)據(jù)準(zhǔn)備

    首先是數(shù)據(jù)的錄入,為了分析我們服務(wù)器集群的性能,需要準(zhǔn)備大量的用戶數(shù)據(jù),幸運(yùn)的是mtools提供了mgenerate方法供我們使用。他可以根據(jù)一個數(shù)據(jù)模版向 MongoDB 中插入任意條 json 數(shù)據(jù)。下面的 json 結(jié)構(gòu)是我們在例子中需要使用的數(shù)據(jù)模版:

    { ? ?"user": { ? ? ? ?"name": { ? ? ? ? ? ?"first": {"$choose": ["Liam", "Aubrey", "Zoey", "Aria", "Ellie", "Natalie", "Zoe", "Audrey", "Claire", "Nora", "Riley", "Leah"] }, ? ? ? ? ? ?"last": {"$choose": ["Smith", "Patel", "Young", "Allen", "Mitchell", "James", "Anderson", "Phillips", "Lee", "Bell", "Parker", "Davis"] } ? ? ? ?},  ? ? ? ?"gender": {"$choose": ["female", "male"]}, ? ? ? ?"age": "$number",  ? ? ? ?"address": { ? ? ? ? ? ?"zip_code": {"$number": [10000, 99999]}, ? ? ? ? ? ?"city": {"$choose": ["Beijing", "ShangHai", "GuangZhou", "ShenZhen"]} ? ? ? ?}, ? ? ? ?"created_at": {"$date": ["2010-01-01", "2014-07-24"] } ? ?}}

    把它保存為一個叫user.json的文件中,然后使用mgenerate插入一百條隨機(jī)數(shù)據(jù)。隨機(jī)數(shù)據(jù)的格式就按照上面json文件的定義。你可以通過調(diào)整?–num的參數(shù)來插入不同數(shù)量的 Document。(Link to mgenerate wiki)

    mgenerate user.json --num 1000000 --database test --collection users --port 38017

    上面的命令會像test數(shù)據(jù)庫中users?collection 插入一百萬條數(shù)據(jù)。在有些機(jī)器上,運(yùn)行上面的語句可能需要等待一段時間,因為生成一百萬條數(shù)據(jù)是一個比較耗時的操作,之所以生成如此多的數(shù)據(jù)是方便后面我們分析性能時,可以看到性能的顯著差別。當(dāng)然你也可以只生成十萬條數(shù)據(jù)來進(jìn)行測試,只要能夠在你的機(jī)器上看到不同find語句的執(zhí)行時間差異就可以。

    插入完數(shù)據(jù)之后,我們想看一下剛剛插入的數(shù)據(jù)在服務(wù)器集群中是如何分配的。通常,可以通過sh.status()?MongoDB shell 命令查看。不過對于一套全新的集群服務(wù)器,再沒有切分任何 collection 之前,我們是看不到太多有用的信息。不過,可以通過 explain 一條查詢語句來看一下數(shù)據(jù)的分布情況。這里不得不強(qiáng)調(diào)一下在進(jìn)行數(shù)據(jù)性能分析時一個好的 IDE 對工作效率有多大的影響,我選擇 dbKoda 作為 MongoDB 的 IDE 主要原因是他是目前唯一一款對 MongoDB Shell 的完美演繹,對于 MongoDB Shell 命令不太熟悉的開發(fā)人員來說尤為重要,幸運(yùn)的是這款 IDE 還支持 Windows/Mac/Linux 三種平臺,基本上覆蓋了絕大多數(shù)操作系統(tǒng)版本。下面是對剛才建立的一百萬條 collection 的一次 find 的 explain 結(jié)果。(對于 Explain 的應(yīng)用,大家可以參考我的另外一片文章:如何通過 MongoDB 自帶的 Explain 功能提高檢索性能?)

    大數(shù)據(jù)

    從上圖中可以看到,我們插入的一百萬條數(shù)據(jù)全部被分配到了第一個 shard 服務(wù)器中,這并不是我們想看到的結(jié)果,不要著急,因為我還沒有進(jìn)行數(shù)據(jù)切分,MongoDB 并不會自動的分配這些數(shù)據(jù)。下面我們來一點(diǎn)一點(diǎn)分析如何利用 Shard 實(shí)現(xiàn)高效的數(shù)據(jù)查詢。

    配置 Shard 數(shù)據(jù)庫

    環(huán)境搭建好并且數(shù)據(jù)已經(jīng)準(zhǔn)備完畢以后,接下來的事情就是配置數(shù)據(jù)庫并切分?jǐn)?shù)據(jù)。方便起見,我們把用戶分為三組,20 歲以下(junior),20 到 40 歲(middle)和 40 歲以上(senior),為了節(jié)省篇幅,我在這里不過多的介紹如何使用 MongoDB 命令,按照下面的幾條命令執(zhí)行以后,我們的數(shù)據(jù)會按照用戶年齡段拆分成若干個 chunk,并分發(fā)到不同的 shard cluster 中。如果對下面的命令不熟悉,可以查看 MongoDB 官方文檔關(guān)于 Shard Zone/Chunk 的解釋。

    db.getSiblingDB('test').getCollection('users').createIndex({'user.age':1})sh.setBalancerState(false)sh.addShardTag('shard01', 'junior')sh.addShardTag('shard02', 'middle')sh.addShardTag('shard03', 'senior')sh.addTagRange('test.users', {'user.age': MinKey}, {'user.age':20}, 'junior')sh.addTagRange('test.users', {'user.age': 21}, {'user.age':40}, 'middle')sh.addTagRange('test.users', {'user.age': 41}, {'user.age': MaxKey}, 'senior')sh.enableSharding('test')sh.shardCollection('test.users', {'user.age':1})sh.setBalancerState(true)

    從上面的命令中可以看出,我們首先要為 Shard Key 創(chuàng)建索引,之后禁止 Balancer 的運(yùn)行,這么做的原因是不希望在 Shard Collection 的過程中還運(yùn)行 Balancer。之后將數(shù)據(jù)按照年齡分成三組,分別標(biāo)記為junior,?middle,senior并把這三組分別分配到三個 Shard 集群中。 之后對 test 庫中的 users collection 進(jìn)行按用戶年齡字段的切分操作,如果 Shard collection 成功返回,你會得到下面的輸出結(jié)果:{ “collectionsharded” : “test.users”, “ok” : 1 }。

    關(guān)于 Shard 需要注意的幾點(diǎn)

    一旦你對一個 Colleciton 進(jìn)行了 Shard 操作,你選擇的 Shard Key 和它對應(yīng)的值將成為不可變對象,所以:你無法在為這個 collection 重新選擇 Shard Key你不能更新 Shard key 的取值

    隨后不要忘記,我們還需要將 Balancer 打開:sh.setBalancerState(true)。剛打開以后運(yùn)行sh.isBalancerRunning()應(yīng)當(dāng)返回true,說明 Balancer 服務(wù)正在運(yùn)行,他會調(diào)整 Chunk 在不同 Shards 服務(wù)器中的分配。一般 Balancer 會運(yùn)行一段時間,因為他要對分組的數(shù)據(jù)重新分配到指定的 shard 服務(wù)器上,你可以通過sh.isBalancerRunning()命令查看 Balancer 是否正在運(yùn)行?,F(xiàn)在可以稍事休息一下喝杯咖啡或看看窗外的風(fēng)景。

    為了理解數(shù)據(jù)如何分布在 3 個 shard 集群中,我們有必要分析一下 chunk 和 zone 的劃分,下圖是在 dbKoda 上顯示 Shard Cluster 統(tǒng)計數(shù)據(jù),可以看到數(shù)據(jù)總共被分成 6 個 chunks,每個 shard 集群存儲 2 個 chunk。

    大數(shù)據(jù)

    對此有些同學(xué)會有疑問,為什么我們的數(shù)據(jù)會被分為 6 個 chunks,而且每個 shard 集群個分配了 2 個 chunk。是誰來保證數(shù)據(jù)的均勻分配?下面我就給大家解釋一下他們的概念以及我們應(yīng)當(dāng)如何使用。

    Chunk

    我們已經(jīng)知道 MongoDB 是通過 shard key 來對數(shù)據(jù)進(jìn)行切分,被切分出來的數(shù)據(jù)被分配到若干個 chunks 中。一個 chunk 可以被認(rèn)為是一臺 shard 服務(wù)器中數(shù)據(jù)的子集,根據(jù) shard key,每個 chunk 都有上下邊界,在我們的例子中,邊界值就是用戶年齡。chunk 有自己的大小,數(shù)據(jù)不斷插入到 mongos 的過程中,chunk 的大小會發(fā)生變化,chunk 的默認(rèn)大小是 64M。當(dāng)然 MongoDB 允許你對 chunk 的大小進(jìn)行設(shè)置,你也可以把一個 chunk 切分成若干個小 chunk,或者合并多個 chunk。一般我不建議大家手動操作 chunk 的大小,或者在 mongos 層面切分或合并 chunk,除非真有合適的原因才去這么做。原因是,在數(shù)據(jù)不斷插入到我們的集群中時,mongodb 中的 chunk 大小會發(fā)生很大的變化,當(dāng)一個 chunk 的大小超過了最大值,mongo 會根據(jù) shard key 對 chunk 進(jìn)行切分,在必要的時候,一個 chunk 可能會被切分成多個小 chunk,大多數(shù)情況下這種自動行為已經(jīng)滿足了我們?nèi)粘5臉I(yè)務(wù)需求,無需進(jìn)行手動操作,另一點(diǎn)原因是當(dāng)進(jìn)行 chunk 切分后,直接的結(jié)果會導(dǎo)致數(shù)據(jù)分配的不均勻,此時 balancer 會被調(diào)用來進(jìn)行數(shù)據(jù)重新分配,很多時候這個操作會運(yùn)行很長時間,無形中導(dǎo)致了內(nèi)部結(jié)構(gòu)的負(fù)載平衡,因此不建議大家手動拆分。當(dāng)然,理解 chunk 的分配原理還是有助于大家分析數(shù)據(jù)庫性能的必要條件。我在這里不過多的將如何進(jìn)行這些操作,有興趣的讀者可以參考 MongoDB 官方文檔,上面有比較全面的解釋。這里我只強(qiáng)調(diào)在進(jìn)行 chunk 操作的時候,要注意一下幾個方面,這些都是影響你 MongoDB 性能的關(guān)鍵因素。

    如果存在大量體積很小的 chunk,他可以保證你的數(shù)據(jù)均勻的分布在 shard 集群中但是可能會導(dǎo)致頻繁的數(shù)據(jù)遷移。這將加重 mongos 層面上的操作。大的 chunk 會減少數(shù)據(jù)遷移,減輕網(wǎng)絡(luò)負(fù)擔(dān),降低在 mongos 路由層面上的負(fù)載,但弊端是有可能導(dǎo)致數(shù)據(jù)在 shard 集群中分布的不均勻。Balancer 會在數(shù)據(jù)分配不均勻的時候自動運(yùn)行,那么 Balancer 是如何決定什么情況下需要進(jìn)行數(shù)據(jù)遷移呢?答案是 Migration Thresholds,當(dāng) chunk 的數(shù)量在不同 shard replica 之間超過一個定值時,balancer 會自動運(yùn)行,這個定值根據(jù)你的 shard 數(shù)量不同而不同。

    Zones

    可以說 chunk 是 MongoDB 在多個 shard 集群中遷移數(shù)據(jù)的最小單元,有時候數(shù)據(jù)的分配不會按照我們臆想的方向進(jìn)行,就拿上面的例子來說,雖然我們選擇了用戶年齡作為 shard key,但是 MongoDB 并不會按照我們設(shè)想的那樣來分配數(shù)據(jù),如何進(jìn)行數(shù)據(jù)分配就是通過 Zones 來實(shí)現(xiàn)。Zones 解決了 shard 集群與 shard key 之間的關(guān)系,我們可以按照 shard key 對數(shù)據(jù)進(jìn)行分組,每一組稱之為一個 Zone,之后把 Zone 在分配給不同的 Shard 服務(wù)器。一個 Shard 可以存儲一個或多個 Zone,前提是 Zone 之間沒有數(shù)據(jù)沖突。Balancer 在運(yùn)行的時候會把在 Zone 里的 chunk 遷移到關(guān)聯(lián)這個 Zone 的 shard 上。

    理解了這些概念以后,我們對數(shù)據(jù)的分配就有了更清楚的認(rèn)識。我們對前面提到的問題就有了充分的解釋。表面上看,數(shù)據(jù)的分布貌似均勻,我們執(zhí)行幾個查詢語句看看性能怎樣。這里再次用到 dbKoda 中的 explain 視圖。

    大數(shù)據(jù)

    上圖中查找年齡在 18 周歲以上的用戶,根據(jù)我們的分組定義,三個 shard 上都有對應(yīng)的紀(jì)錄,但是 shard1 對應(yīng)的年齡組是 20 歲以下,應(yīng)該包括數(shù)量較少的數(shù)據(jù),所以在圖中 shard 里表里現(xiàn)實(shí)的 shard01 返回了 9904 條記錄,遠(yuǎn)遠(yuǎn)少于其他兩個 shard,這也符合我們的數(shù)據(jù)定義。在上面性能描述中也可以看出,這條語句在 shard01 上面運(yùn)行的時間也是相對較少的。

    再看看下面的例子,如果我們查找 25 周歲以上的用戶,得到的結(jié)果中并沒有出現(xiàn) shard1 的身影,這也是符合我們的數(shù)據(jù)分配,因為 shard1 只存儲了年齡小于 20 周歲的用戶。

    大數(shù)據(jù)

    你選擇的 Shard Key 合適嗎?

    了解了數(shù)據(jù)是如何分布的以后,咱們再回過頭來看看我們選擇的 shard key 是否合理。細(xì)心的讀者已經(jīng)發(fā)現(xiàn),上面運(yùn)行的 explain 結(jié)果中存在一個問題,就是 shard3 存儲了大量的數(shù)據(jù),如果我們看一下每個年齡組的紀(jì)錄個數(shù),會發(fā)現(xiàn) shard1、shard2、shard3 分別包括 198554, 187975, 593673,顯然年齡大于 40 歲的用戶占了大多數(shù)。這并不是我們希望的結(jié)果,因為 shard3 成為了集群中的一個瓶頸,數(shù)據(jù)庫操作語句在 shard3 上運(yùn)行的速度會大大超過另外兩個 shard,這點(diǎn)從上面的 explain 結(jié)果中也可以看到,查詢語句在 shard3 上的運(yùn)行時間是另外兩個 shard 的兩倍以上。更重要的是,隨著用戶數(shù)量的不斷增加,數(shù)據(jù)的分布也會出現(xiàn)顯著變化,在系統(tǒng)運(yùn)行一段時間以后,可能 shard2 的用戶數(shù)超過 shard3,也有可能 shard1 稱為存儲數(shù)據(jù)量最多的服務(wù)器。這種數(shù)據(jù)不平衡是我們不希望看到的。原因在哪里呢?是不是覺得我們選擇的用戶年齡作為分組條件是一個不太理想的 key。那么什么樣的 key 能夠保證數(shù)據(jù)的均勻分布呢?接下來我們分析一下 shard key 的種類。

    Ranged Shard Key

    我們上面選擇的年齡分組就是用的這種 shard key。根據(jù) shard key 的取值,它把數(shù)據(jù)切分成連續(xù)的幾個區(qū)間。取值相近的紀(jì)錄會放進(jìn)同一個 shard 服務(wù)器。好處是查詢連續(xù)取值紀(jì)錄時,查詢效率可以得到保證。當(dāng)數(shù)據(jù)庫查詢語句發(fā)送到 mongos 中時,mongos 會很快的找到目標(biāo) shard,而且不需要將語句發(fā)送到所有的 shard 上,一般只需要少量的 shard 就可以完成查詢操作。缺點(diǎn)是不能保證數(shù)據(jù)的平均分配,在數(shù)據(jù)插入和修改時會產(chǎn)生比較嚴(yán)重的性能瓶頸。

    Hashed Shard Key

    于 Ranged Shard Key 對應(yīng)的一種被稱之為 Hashed Shard Key,它采用字段的索引哈希值作為 shard key 的取值,這樣做可以保證數(shù)據(jù)的均勻分布。在 mongos 和各個 shard 集群之間存在一個哈希值計算方法,所有的數(shù)據(jù)在遷移時都是根據(jù)這個方法來計算數(shù)據(jù)應(yīng)當(dāng)被遷移到什么地方。當(dāng) mongos 接收到一條語句時,通常他會把這條語句廣播到所有的 shard 上去執(zhí)行。

    有了上面的認(rèn)識,我們?nèi)绾卧?Ranged 和 Shard 之間進(jìn)行選擇呢?下面兩個屬性是我們選擇 shard key 的關(guān)鍵。

    Shard Key Cardinality (集)

    Cardinality指的是 shard key 可以取到的不同值的個數(shù)。他會影響到 Balancer 的運(yùn)行,這個值也可以被看做是 Balancer 可以創(chuàng)建的最大 chunk 個數(shù)。以我們年齡字段為例,假如一個人的年齡在 100 歲以下,那么這個字段的 cardinality 可以取 100 個不同的值。對于一個唯一的年齡數(shù)據(jù),不會出現(xiàn)在不同的 chunk 中。如果你選擇的 Shard Key 的 cardinality 很小,比如只有 4 個,那么數(shù)據(jù)最多會被分發(fā)到 4 個不同的 shard 中,這樣的結(jié)構(gòu)也不適合服務(wù)器的水平擴(kuò)展,因為不會有數(shù)據(jù)被分割到第五個 shard 服務(wù)器上。

    Shard Key Frequency(頻率)

    Frequency指的是 shard key 的重復(fù)度,也就是對于一個字段,有多少取值相同的紀(jì)錄。如果大部分?jǐn)?shù)據(jù)的 shard key 取值相同,那么存儲他們的 chunk 會成為數(shù)據(jù)庫的一個瓶頸。而且,這些 chunk 也變成了不可再切分的 chunk,嚴(yán)重影響了數(shù)據(jù)庫的水平擴(kuò)展。在這種情況下應(yīng)當(dāng)考慮使用組合索引的方式來創(chuàng)建 shard key。所以,盡量選擇低頻率的字段作為 shard key。

    Shard Key Increasing Monotonically (單調(diào)增長)

    單調(diào)增長在這里的意思是在數(shù)據(jù)被切分以后,新增加的數(shù)據(jù)會按照其 shard key 取值向 shard 中插入,如果新增的數(shù)據(jù)的 key 值都是向最大值方向增加,那么這些新的數(shù)據(jù)會被插入到同一個 shard 服務(wù)器上。例如我們前面的用戶年齡分組字段,如果系統(tǒng)的新增用戶都是年齡大于 40 歲的,那么 shard3 將會存儲所有的新增用戶,shard3 會成為系統(tǒng)的性能瓶頸。在這種情況下,應(yīng)當(dāng)考慮使用 Hashed Shard Key。

    重新設(shè)計 Shard Key

    通過上面的分析我們可以得出結(jié)論,前面例子中的用戶年齡字段是一個很糟糕的方案。有幾個原因:

    用戶的年齡不是固定不變的,由于 shard key 是不可變字段,一旦確定下來以后不能進(jìn)行修改,所以年齡字段顯然不是很合適,畢竟沒有年齡永遠(yuǎn)不增長的用戶。一個系統(tǒng)的用戶在不同年齡階段的分布是不一樣的,對于像游戲、娛樂方面的應(yīng)用可能會吸引年輕人多一些。而對于醫(yī)療、養(yǎng)生方面也許會有更多老年人關(guān)注。從這一點(diǎn)上說,這樣的切分也是不恰當(dāng)?shù)?。選擇年齡字段并沒有考慮到未來用戶增長方面帶來的問題,有可能在數(shù)據(jù)切分的時候年齡是均勻分布的,但是系統(tǒng)運(yùn)行一段時間以后有可能出現(xiàn)不平等的數(shù)據(jù)分布,這點(diǎn)會給數(shù)據(jù)維護(hù)帶來很大的困擾。

    那么我們應(yīng)當(dāng)如何進(jìn)行選擇呢?看一下用戶表的所有屬性可以發(fā)現(xiàn),其中有一個created_at字段,它指的是紀(jì)錄創(chuàng)建的時間戳。如果采用 Ranged Key 那么在數(shù)據(jù)增長方向上會出現(xiàn)單調(diào)增長問題,在分析一下發(fā)現(xiàn)這個字段重復(fù)的紀(jì)錄不多,他有很高的cardinality和非常低的頻率,這樣 Harded key 就成為了很好的備選方案。

    分析完理論以后咱們實(shí)踐一下看看效果,不幸的是我們并不能修改 shard key,最好的方法就是備份數(shù)據(jù),重新創(chuàng)建 shard 集群。創(chuàng)建和數(shù)據(jù)準(zhǔn)備的過程我就不在重復(fù)了,你們可以根據(jù)前面的例子自己作一遍。

    下圖中是我新建的一個userscollection,并以created_at為索引創(chuàng)建了 Hashed Shard Key,注意created_at必須是一個 hash index 才能成為 hashed shard key。下面是針對用戶表的一次查詢結(jié)果。

    大數(shù)據(jù)

    從圖中可以看到,explain 的結(jié)果表示了三個 shard 服務(wù)器基本上均勻分布了所有的數(shù)據(jù),三個 shard 上執(zhí)行時間也都基本均勻,在 500 到 700 多毫秒以內(nèi)。還記得上面的幾次查詢結(jié)果嗎?在數(shù)據(jù)比較多的 shard 上的運(yùn)行時間在 1 到 2 毫秒??梢钥吹娇偟男阅艿玫搅孙@著提高。

    選擇完美的 Shard Key

    在 shard key 的選擇方面,我們需要考慮很多因素,有些是技術(shù)的,有些是業(yè)務(wù)層面的。通常來講應(yīng)當(dāng)注意下面幾點(diǎn):

    所有增刪改查語句都可以發(fā)送到集群中所有的 shard 服務(wù)器中任何操作只需要發(fā)送到與其相關(guān)的 shard 服務(wù)器中,例如一次刪除操作不應(yīng)當(dāng)發(fā)送到?jīng)]有包括要刪除的數(shù)據(jù)的 shard 服務(wù)器上

    權(quán)衡利弊,實(shí)際上沒有完美的 shard key,只有選擇 shard key 時應(yīng)當(dāng)注意和考慮的要素。不會出現(xiàn)一種 shard key 可以滿足所有的增刪改查操作。你需要從給你的應(yīng)用場景中抽象出用來選擇 shard key 的元素,考量這些要素并作出最后選擇,例如:你的應(yīng)用是處理讀操作多還是寫操作多?最常用的寫操作場景是什么樣子的?

    小結(jié)

    在 shard key 的選擇方面沒有一個統(tǒng)一的方法,要根據(jù)具體的需求和數(shù)據(jù)增長的方向來設(shè)計。在我們?nèi)粘5拈_發(fā)過程中,并不是所有技術(shù)問題都應(yīng)當(dāng)由技術(shù)人員來解決,這個世界是一個業(yè)務(wù)驅(qū)動的時代,而技術(shù)主要是為業(yè)務(wù)服務(wù),我們要提高對需求變化的相應(yīng)速度。像本文中如何選擇 Shard Key 的問題,我覺得并不能單純的通過技術(shù)來考量,更多的是要和業(yè)務(wù)人員討論各個數(shù)據(jù)字段的意義,使用的業(yè)務(wù)價值以及未來業(yè)務(wù)的增長點(diǎn)。如果在一開始 shard key 的選擇出現(xiàn)錯誤,那么在接下來的應(yīng)用過程中想要改變 shard key 是一件極其繁瑣的過程。可能你需要備份你的 collection,然后重新創(chuàng)建 shard 服務(wù)并恢復(fù)數(shù)據(jù),這個過程很可能需要運(yùn)行很長一段時間。在互聯(lián)網(wǎng)應(yīng)用的今天,服務(wù)器的宕機(jī)事件都是以秒為單位計算,很可能錯誤的 shard key 選擇會給你的應(yīng)用帶來災(zāi)難性的后果。希望此文能給各位一點(diǎn)啟示,在項目初期的設(shè)計階段充分考慮到各方面的因素。

    極客網(wǎng)企業(yè)會員

    免責(zé)聲明:本網(wǎng)站內(nèi)容主要來自原創(chuàng)、合作伙伴供稿和第三方自媒體作者投稿,凡在本網(wǎng)站出現(xiàn)的信息,均僅供參考。本網(wǎng)站將盡力確保所提供信息的準(zhǔn)確性及可靠性,但不保證有關(guān)資料的準(zhǔn)確性及可靠性,讀者在使用前請進(jìn)一步核實(shí),并對任何自主決定的行為負(fù)責(zé)。本網(wǎng)站對有關(guān)資料所引致的錯誤、不確或遺漏,概不負(fù)任何法律責(zé)任。任何單位或個人認(rèn)為本網(wǎng)站中的網(wǎng)頁或鏈接內(nèi)容可能涉嫌侵犯其知識產(chǎn)權(quán)或存在不實(shí)內(nèi)容時,應(yīng)及時向本網(wǎng)站提出書面權(quán)利通知或不實(shí)情況說明,并提供身份證明、權(quán)屬證明及詳細(xì)侵權(quán)或不實(shí)情況證明。本網(wǎng)站在收到上述法律文件后,將會依法盡快聯(lián)系相關(guān)文章源頭核實(shí),溝通刪除相關(guān)內(nèi)容或斷開相關(guān)鏈接。

    2017-11-09
    MongoDB的水平擴(kuò)展,你做對了嗎?
    作者:趙翼 分布式數(shù)據(jù)庫的前世今生 當(dāng)人們一開始使用數(shù)據(jù)庫系統(tǒng)的時候,所有數(shù)據(jù)都是跑在一臺服務(wù)器上,即所謂的單機(jī)數(shù)據(jù)庫服務(wù)器。在企業(yè)級應(yīng)用中,我們會搭建一臺應(yīng)用

    長按掃碼 閱讀全文