經過一步步建立sharded cluster來認識mongodb

  mongodb是目前使用很是普遍的nosql(not only sql)之一,在db engines上排名很是靠前,下圖是5月份的排名:python

  

  能夠看到前面四個都是傳統的關係型數據庫,而mongodb在nosql中拔得頭籌。本文會簡單介紹mongodb的一些特性,而後經過在Linux環境下一步步搭建sharded cluster來學習mongodb。本文實驗的mongodb是mongodb3.0,可能與最新的版本(mongodb3.4)在細節之處略有差別。

 

Mongodb特性

   官方一句話就能歸納Mongodb的特色:
  MongoDB is an open-source document database that provides high performance, high availability, and automatic scaling.

  開源、基於文檔(document oriented)、高性能、高可用、自動伸縮。mysql

  開源sql

  這個好處就不用多說了,GitHub上有源碼。mongodb

   面向文檔
  文檔(document)在不少編程語言都有相似的數據結構,各類table、map、dict,不再用使用DAO(data access object)。好比在python中,document與dict對應,array與list對應。
      document也支持嵌套的document和array,這樣的話也能部分解決關聯查詢(固然,雖然把相關信息放在一個嵌套的document下降了關聯查詢的開銷,但在某些狀況不得不須要關聯查詢的時候仍是有點頭疼)
  因爲基於document,因此就schema free(模式自由)啦,使用關係型數據的同窗都知道,線上修改表結構是多麼麻煩的一件事情。但在mongodb中,增該刪一個字段太容易了,這個也是最後開發人員喜歡的一點,好比遊戲服務器,玩家的持久化數據會不停的變化,每次更新都會增長一些功能,也就回增長一些須要持久化的字段,用mongodb就很合適。
  
  高性能
  支持嵌套的document,在關係型數據庫中須要聯合查詢的耗時操做,在mongodb中一條查詢就能搞定
  豐富的索引支持,並且索引還支持嵌套文檔和數組
  在使用了sharding機制的狀況下,讀寫操做能夠路由到不一樣的shard,提到了集羣的併發性能
 
  高可用
  要想mongodb高可用,那麼就得使用mongodb的複製機制:replica set
  replica set經過異步複製(Asynchronous replication)和自動Failover來保證可用性
  後面會專門介紹replica set
 
  自動擴展(水平拆分)
  在關係型數據中,當單個表數據量過大的時候,通常會經過垂直分表或者水平分表的方式來提到數據庫吞吐量。在mongodb中,sharding是其核心功能之一,提供了自動的水平擴展(horizontal scalability)來對數據量比較大的集合進行拆分,sharding將同一個集合的不一樣子集數據放在不一樣的機器上,當應用程序選擇好適當的sharding key,能夠將讀寫操做路由到某一個shard上,大大提升了集羣吞吐性能。
  後面會專門介紹sharding cluster的組成
 

預備知識

  本文並不涵蓋mongodb的基礎知識,可是爲了後面介紹sharding知識,以及搭建sharded cluster,在這裏介紹一下 _id這個特殊的字段(field)
  默認狀況下,mongodb會在集合的_id字段上建立unique index。若是沒有在持久化的文檔中包含_id,那麼mongodb會自動添加這個字段,其value是一個ObjectId。
  mongodb官方建議_id使用ObjectId、或者天然惟一標示(unique identifier)、說着自增的數字、或者UUID。_id在sharding相關的CRUD中有特殊性,具體使用的時候能夠參加文檔。
  另外,爲了後文描述方便,這裏聲明幾個在mongodb中的概念
  DB:與mysql的DB對應
  collection、集合: 與mysql的table對應
  document:與mysql的record對應

replica set

  MongoDB中經過 replica set(複製集)來提供高可用性:冗餘與自動failover。
  複製集是說將 同一份數據的多分拷貝放在不一樣的機器(甚至不一樣的數據中心)來提升容錯。一個典型的replica set由一組mongod實例組成,其中有且僅有一個節點提供寫操做,稱之爲primary,primary也是默認的讀節點。同時,replica set中可包含一個到多個secondary節點,secondary節點只提供讀操做。以下圖所示:
  

 

  應用程序經過驅動與Primary鏈接,全部的寫操做都在Primary上進行,同時primary會將這些操做寫到oplog(operation log)中,secondary經過異步複製oplog,而後在本地數據集上執行oplog中的操做,這樣就達到了數據的一致性。從這裏能夠看到,雖然secondary和primary維護的上同一份數據,可是其變動是要遲於primary的。數據庫

  若是應用程序對數據的實時性要求不過高,好比評論數據、聚合數據等,那麼能夠從secondary讀取,這樣能夠作到讀寫分離和高併發。若是拷貝放在不一樣的數據中心,能更好的提供數據局部性(data locality)和分佈式服務的可用性。
 
  咱們看到,若是一個Secondary不能正常工做了(多是進程crash、物理損壞、網絡故障),對應用程序來講影響並不大。可是若是primary不能工做了呢?這個時候mongodb的automatic failover就開始發揮做用了。
  在replica set中的所用mongod節點之間都會有心跳(heartbeat)存在,若是超過必定時間其餘節點沒有收到primary的心跳,那麼就認爲primary掛掉了。 可被選舉的secondary會投票選舉出新的primary。整個過程以下所示:

 

  自動的failover 雖然保證了mongodb的高可用性,可是在primary到secondary的切換過程當中,這一段時間,mongodb是沒法提供寫操做的。表現就是對於應用程序的數據庫操做請求會返回一些錯誤,這個時候應用程序須要識別這些錯誤,而後作重試。編程

  

  除了Primary和Secondary,在replica set中還能夠存在存在另一種節點:Arbiter。Arbiter與Secondary節點的區別在於,Arbiter不持久化數據(do not bearing data),  天然也不可能在Primary掛掉的時候被選舉。Arbiter的做用在於投票:爲了選出新的primary,secondary投票規則是少數服從多數,若是replica set中的節點數目是偶數,那麼就可能出現「平局」的狀況,因此加入一個Arbiter就能夠以最小的代價解決這個問題。數組

 

   Arbiter不持久化數據,因此佔用的磁盤空間也不多,對硬件的要求也不高。官方建議,Arbiter不要和primary或者secondary放在同一個物理主機上。
  在後面的演示中,也會在replica set中加入一個Arbiter,減小磁盤佔用。

sharded cluster

  所謂sharding就是將同一個集合的不一樣子集分發存儲到不一樣的機器(shard)上,Mongodb使用sharding機制來支持超大數據量,將不一樣的CRUD路由到不一樣的機器上執行,提到了數據庫的吞吐性能。因而可知,sharding是很是常見的scale out方法。bash

  

 

  如上圖所示,一個集合(Collection1)有1T的數據,本來放在一個單獨的數據庫中,經過sharding,將這個集合的數據放在四個獨立的shard中,每個shard存儲這個集合256G的數據。每一個shard物理上是獨立的數據庫,但邏輯上共同組成一個數據庫。服務器

  一個sharded cluster由一下三部分組成:config server,shards,router。如圖所示:網絡

  

 

  shards

  存儲數據,能夠是單個的mongod,也能夠是replica set。在生產環境中,爲了提升高可用性,都會使用replica set。存儲在mongod上的數據以chunk爲基本單位,默認的大小爲64M,後面會介紹shard上數據的分裂(split)與遷移(migration)

  config server

  存儲集羣的元數據(metadata),即數據的哪一部分放在哪個shard上,router將會利用這些元數據將請求分發到對應的shards上,shards上chunk的遷移也是config server來控制的。

  router:

  mongos實例,在一個集羣中直接爲應用程序提供服務,利用config server上的元數據來制定最佳的查詢計劃。

 

  數據分割(data partition)

  從前文知道,MongoDB在collection這個級別進行數據的切塊,稱之爲sharding。塊的最小粒度是chunk,其大小(chunkSize)默認爲64M。

  當一個集合的數據量超過chunkSize的時候,就會被拆分紅兩個chunk,這個過程稱爲splitting。那麼按照什麼原則將一個chunk上的數據拆分紅兩個chunk,這就是Sharding key的做用,Sharding key是被索引的字段,經過sharding key,就能夠把數據均分到兩個chunk,每個document在哪個chunk上,這就是元數據信息。元數據信息存放在config server上,方便router使用。

  若是sharding cluster中有多個shard,那麼不一樣shard上的chunk數目多是不一致的,這個時候會有一個後臺進程(balancer)來遷移(migrate)chunk,從chunk數目最多的shard遷移到chunk數目最少的chunk,直到達到均衡的狀態。遷移的過程對應用程序來講是透明的。

  以下圖所示,遷移以前ShardA ShardB上都有3個chunk,而Shard C上只有一個Chunk。經過從ShardB上遷移一個chunk到ShardC,就達到了一個均衡的狀態。  

  splitting和migration 的目的是爲了讓數據在shards之間均勻分佈,其根本目標是爲了將對數據的CRUD操做均衡地分發到各個shard,提升集羣的併發性能。

  

Sharded cluster搭建

  聲明,本章節只是演示Sharded Cluster的搭建過程,與生產環境仍是有較大差別,不過我也會在文中儘可能指出這些差別。首先須要注意的是,本文的演示不涉及到鑑權(--auth),但在生產環境中鑑權是很是重要的,相信你們都還記得春節期間Mongodb被劫持、被攻擊的事件。
  前文已經提到,一個典型的Sharded Cluster包括router(mongos)、config server和shards,其中每一個shard均可以是單點(standalone)或者複製集(replica set)。 接下來的演示包括一個router, 三個config server,兩個shard。每個shard都是有一個primary、一個secondary和一個arbiter組成的replica set。
  在開始以前,首先預約義好全部須要用到的變量,以下所示:
 1 #!/bin/bash
 2 export BIN_HOME=/usr/local/mongodb/bin
 3 export DB_PATH=/home/mongo_db/data
 4 export LOG_PATH=/home/mongo_db/log
 5 
 6 LOCAL=127.0.0.1
 7 
 8 #config rs
 9 export RS1_1_DB_PATH=$DB_PATH/rs1_1
10 export RS1_2_DB_PATH=$DB_PATH/rs1_2
11 export RS1_3_DB_PATH=$DB_PATH/rs1_3
12 export RS2_1_DB_PATH=$DB_PATH/rs2_1
13 export RS2_2_DB_PATH=$DB_PATH/rs2_2
14 export RS2_3_DB_PATH=$DB_PATH/rs2_3
15 
16 export RS1_1_DB_LOG=$LOG_PATH/rs1_1.log
17 export RS1_2_DB_LOG=$LOG_PATH/rs1_2.log
18 export RS1_3_DB_LOG=$LOG_PATH/rs1_3.log
19 export RS2_1_DB_LOG=$LOG_PATH/rs2_1.log
20 export RS2_2_DB_LOG=$LOG_PATH/rs2_2.log
21 export RS2_3_DB_LOG=$LOG_PATH/rs2_3.log
22 
23 export RS1_1_PORT=27018
24 export RS1_2_PORT=27019
25 export RS1_3_PORT=27020
26 export RS2_1_PORT=27021
27 export RS2_2_PORT=27022
28 export RS2_3_PORT=27023
29 
30 export RS1=rs1
31 export RS2=rs2
32 
33 #config config_server
34 export CONF1_DB_PATH=$DB_PATH/db_conf1
35 export CONF2_DB_PATH=$DB_PATH/db_conf2
36 export CONF3_DB_PATH=$DB_PATH/db_conf3
37 
38 export CONF1_DB_LOG=$LOG_PATH/conf1.log
39 export CONF2_DB_LOG=$LOG_PATH/conf2.log
40 export CONF3_DB_LOG=$LOG_PATH/conf3.log
41 
42 export CONF1_PORT=40000
43 export CONF2_PORT=40001
44 export CONF3_PORT=40002
45 
46 export CONF1_HOST=$LOCAL:$CONF1_PORT
47 export CONF2_HOST=$LOCAL:$CONF2_PORT
48 export CONF3_HOST=$LOCAL:$CONF3_PORT
49 
50 #config route_server
51 export ROUTE_DB_LOG=$LOG_PATH/route.log
52 
53 export ROUTE_PORT=27017

   能夠在會話窗口中將這些命令執行一遍,不過更好的方式是將其保存在一個文件中(如mongodb_define.sh),而後執行這個文件就好了:source mongodb_define.sh

啓動shards(replica set)

  在這一部分,會建立兩個replica set,分別是rs1, rs2。每一個replica set包含三個節點,且其中一個是arbiter。因爲兩個replica set建立過程沒什麼區別,所以以rs1爲例。關於replica set的搭建,可參見mongodb doc中 deploy-replica-set-for-testing部分,講得比較清楚。
   step1: 首先得建立好存放數據的目錄:
  mkdir -p $RS1_1_DB_PATH
  mkdir -p $RS1_2_DB_PATH
  mkdir -p $RS1_3_DB_PATH
  PS: -p means "no error if existing, make parent directories as needed"
 
   step2: 啓動組成rs1的三個mongod 

  $BIN_HOME/mongod --port $RS1_1_PORT --dbpath $RS1_1_DB_PATH --fork --logpath $RS1_1_DB_LOG --replSet $RS1 --smallfiles --nojournal
  $BIN_HOME/mongod --port $RS1_2_PORT --dbpath $RS1_2_DB_PATH --fork --logpath $RS1_2_DB_LOG --replSet $RS1 --smallfiles --nojournal
  $BIN_HOME/mongod --port $RS1_3_PORT --dbpath $RS1_3_DB_PATH --fork --logpath $RS1_3_DB_LOG --replSet $RS1 --smallfiles --nojournal

  關於mongod的啓動選項,能夠經過mongod --help查看,在上面的命令行中,--replSet 指定了replica set的名字, --smallfiles 聲明使用更小的默認文件, --nojournal代表不開啓journaling機制。注意,在這個地方不開啓journaling是由於實驗環境磁盤空間有限,而全部的mongod實例都在這個機器上,在生成環境中,必定要開始journaling,這個是mongodb durability的保證。
  
   step3:初始化複製集rs1
  在這一步,須要經過mongdb的客戶端mongo鏈接到複製集的任何一個節點,對複製集初始化,這裏鏈接到RS1_1(端口爲27018):
  mongo --port $RS1_1_PORT
  先來看一下如今複製集的狀態(PS:下面全部以 > 開頭的命令行都表示是在mongo這個交互式客戶端輸入的指令)
  > rs.status()
  
  能夠看到這個複製集尚未初始化
  >config = {

    _id : "rs1",
    members : [
      {_id : 0, host : "127.0.0.1:27018"},
      {_id : 1, host : "127.0.0.1:27019"},
      {_id : 2, host : "127.0.0.1:27020", arbiterOnly: true},
    ]
  }

  >rs.initiate(config)

  
  從config和運行後的複製集狀態均可以看到,RS1_3(127.0.0.1:27020)這個mongod爲一個Arbiter,即只參與投票,不持久化數據。另外RS1_1爲Primary, RS1_2爲Secondary。
  到此爲止,複製集rs1就啓動好了。
 
 
  關於s2的啓動,下面也給出全部命令。方便讀者實踐

  mkdir -p $RS2_1_DB_PATH
  mkdir -p $RS2_2_DB_PATH
  mkdir -p $RS2_3_DB_PATH

  $BIN_HOME/mongod --port $RS1_1_PORT --dbpath $RS1_1_DB_PATH --fork --logpath $RS1_1_DB_LOG --replSet $RS1 --smallfiles --nojournal
  $BIN_HOME/mongod --port $RS1_2_PORT --dbpath $RS1_2_DB_PATH --fork --logpath $RS1_2_DB_LOG --replSet $RS1 --smallfiles --nojournal
  $BIN_HOME/mongod --port $RS1_3_PORT --dbpath $RS1_3_DB_PATH --fork --logpath $RS1_3_DB_LOG --replSet $RS1 --smallfiles --nojournal

  mongo --port $RS2_1_PORT
  >config = {
  _id : "rs2",
  members : [
  {_id : 0, host : "127.0.0.1:27021"},
  {_id : 1, host : "127.0.0.1:27022"},
  {_id : 2, host : "127.0.0.1:27023", arbiterOnly: true},
  ]
  }
  >rs.initiate(config)

啓動config servers

  mongodb官方建議config server須要三個mongod實例組成,每個mongod最好部署在不一樣的物理機器上。這個三個mongod並非複製集的關係,

  step1:建立db目錄

  mkdir -p $CONF1_DB_PATH
  mkdir -p $CONF2_DB_PATH
  mkdir -p $CONF3_DB_PATH

  step2:啓動三個mongod實例:

  $BIN_HOME/mongod --port $CONF1_PORT --dbpath $CONF1_DB_PATH --fork --logpath $CONF1_DB_LOG --configsvr --smallfiles --nojournal
  $BIN_HOME/mongod --port $CONF2_PORT --dbpath $CONF2_DB_PATH --fork --logpath $CONF2_DB_LOG --configsvr --smallfiles --nojournal
  $BIN_HOME/mongod --port $CONF3_PORT --dbpath $CONF3_DB_PATH --fork --logpath $CONF3_DB_LOG --configsvr --smallfiles --nojournal

  一樣啓動參數中nojournal只是爲了節省存儲空間,在生產環境中必定要使用journaling。與建立replica set時mongod的啓動不一樣的是,這裏有一個configsvr 選項,代表這些節點都是做爲config server存在。

  再啓動這三個mongod以後,不會有相似replica set那樣講三個mongod綁定之類的操做,也說明了config server之間是相互獨立的

 

啓動router

  在Sharded Cluster中,router(mongos)是應用程序鏈接的對象,一切對mongodb的操做都經過router來路由
   step1:啓動mongos
 
   $BIN_HOME/mongos --port $ROUTE_PORT --configdb $CONF1_HOST,$CONF2_HOST,$CONF3_HOST --fork --logpath $ROUTE_DB_LOG --chunkSize 32
  注意這裏的可執行程序是mongos,而不是以前的mongod,關於參數,也是能夠經過mongos --help查看的。在上面的命令中,--configdb選項指定了三個config server,--chunkSize指定了chunk的大小,單位爲M。關於chunksize,默認是64M,雖然能夠在初次啓動的時候指定chunksize,但mongodb官方推薦按照 如下方式修改。 在本文中將chunkSize改小的目的,是爲了之後實驗的時候更方便觀察數據的拆分和遷移。
  chunkSize事實上會持久化到config.setting中,鏈接到mongos可查看:
  mongo --port $ROUTE_PORT 
  

  在上面截圖藍色框中能夠看出,如今尚未任何shard的信息,緣由是到如今爲止,config servers與replica set尚未任何關係

 
   step2:將在前面建立的兩個replica set(rs1 rs2)加入到Sharded Cluster中
   mongo --port $ROUTE_PORT 

  mongos> sh.addShard('rs1/127.0.0.1:27018')
  mongos> sh.addShard('rs2/127.0.0.1:27021')

  PS:爲何須要在rs1後面指定一個mongod的ip port,這個是用來找到對應的mongod,繼而找到相應rs

  再次查看結果:

  
  能夠看到已經添加了兩個shard,每個都是一個replica set。有意思的是Arbiter(好比RS1_3)並無顯示在查詢結果中,可能的緣由是Arbiter並不持久化數據,顯示在這裏也沒有什麼意義。
 
  到此爲止,整個Sharded Cluster就算搭建好了,可是還未進入真正使用階段,要發揮Sharded Cluster的做用,咱們得指定哪些collection能夠被sharding,以及如何sharding

建立sharding key

  爲了演示,咱們假設添加一個db叫test_db, 其中有兩個collection,一個是須要sharding的,叫sharded_col;另外一個暫時不用sharding,叫non_sharded_col, 固然以後也能夠增長新的集合,或者把原來沒有sharding的集合改爲sharding。

  一下操做都須要登陸到router進行: mongo --port $ROUTE_PORT 

  step1:首先得告知mongodb test_db這個數據庫支持sharding

  mongos> sh.enableSharding("test_db")
  { "ok" : 1 }

  這個時候能夠查看數據庫的狀態,注意,是在config這個db下面的databases集合

  mongos> use config

  mongos> db.databases.find()
  { "_id" : "admin", "partitioned" : false, "primary" : "config" }
  { "_id" : "test_db", "partitioned" : true,  "primary" : "rs1" }
  從查詢結果能夠看到,test_db是支持sharding的("partitioned" : true)。另外上面加粗部分primary: rs1,這個primary與replica set裏面的primary不是一回事,這裏的primary是primary shard的意思,咱們知道即便在sharded cluster環境中,一些數據量比較小的db也是無需分片的,這些db默認就存放在primary shard上面
 
   step2:爲須要的collection(即這裏的sharded_col)指定sharding key
  前面已經提到了sharding key的做用,關於sharding key的選擇,是一個比較複雜的問題,sharding key對索引,對CRUD語句的操做都有諸多限制,這一部分之後再細講,在這裏默認使用_id作sharding key(_id是mongodb默認爲每一個集合增長的索引)
  mongos> sh.shardCollection('test_db.sharded_col', {'_id': 1})
  接下來看看整個sharded cluster的狀態:

  

  sh.status()反應的內容事實上也是來自config整個數據庫的內容,只不過作了必定程度的整合。從上面能夠看到,有兩個shard,rs1, rs2;test_db容許sharding,test_db.sharded_col整個collection的sharding key爲{"_id": 1},且目前只有一個chunk在rs1整個shard上。

總結:

  到目前爲止,咱們已經搭建了一個有三個config server,兩個shard的sharded cluster,其中每個shard包含三個節點的replica set,且都包含一個Arbiter。咱們能夠查看一下剛建立好以後各個mongod實例持久化的數據大小:

  

  能夠看到,兩個Arbiter(rs1_3, rs2_3)所佔的空間要小得多。

  對於應用程序來講,集羣(sharded cluster)和單點(standalone)是有必定差別的,若是須要發揮sharded cluster高性能、高可用的特色,須要根據應用場景精心選擇好sharding key,而sharding key的選擇跟索引的創建以及CRUD語句息息相關,這一部分之後再聊。對於目前搭建的這個實例,簡單測試的話,往sharded_col插入足夠多條document就能看到chunks的拆分和遷移。

 

references:

db engines

the-id-field

replication-introduction

deploy-replica-set-for-testing

deploy-shard-cluster

相關文章
相關標籤/搜索