顯示具有 mongodb 標籤的文章。 顯示所有文章
顯示具有 mongodb 標籤的文章。 顯示所有文章

2021/08/09

MongoDB Cluster

設定三個節點的 MongoDB Cluster

在三台機器設定測試 MongoDB Cluster

環境設定

一個由三個 replica set 組成的 shard server,三個 config server,三個 route server。

shard1: 3 replica sets
192.168.1.11:27019
192.168.1.12:27019
192.168.1.13:27019

3 config server
192.168.1.11:27018
192.168.1.12:27018
192.168.1.13:27018

3 route server
192.168.1.11:27017
192.168.1.12:27017
192.168.1.13:27017

安裝

vi /etc/yum.repos.d/mongodb-org-4.2.repo

[mongodb-org-4.2]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/4.2/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-4.2.asc
yum install -y mongodb-org

# 安裝後,會修改的檔案
# mongodb system service
/usr/lib/systemd/system/mongod.service
# mongodb config file
/etc/mongod.conf

/usr/lib/systemd/system/mongod.service

[Unit]
Description=High-performance, schema-free document-oriented database
After=network.target
Documentation=https://docs.mongodb.org/manual

[Service]
User=mongod
Group=mongod
Environment="OPTIONS=-f /etc/mongod.conf"
ExecStart=/usr/bin/mongod $OPTIONS
ExecStartPre=/usr/bin/mkdir -p /var/run/mongodb
ExecStartPre=/usr/bin/chown mongod:mongod /var/run/mongodb
ExecStartPre=/usr/bin/chmod 0755 /var/run/mongodb
PermissionsStartOnly=true
PIDFile=/var/run/mongodb/mongod.pid
Type=forking
# file size
LimitFSIZE=infinity
# cpu time
LimitCPU=infinity
# virtual memory size
LimitAS=infinity
# open files
LimitNOFILE=64000
# processes/threads
LimitNPROC=64000
# locked memory
LimitMEMLOCK=infinity
# total threads (user+kernel)
TasksMax=infinity
TasksAccounting=false
# Recommended limits for for mongod as specified in
# http://docs.mongodb.org/manual/reference/ulimit/#recommended-settings

[Install]
WantedBy=multi-user.target

/etc/mongod.conf

# more /etc/mongod.conf
# mongod.conf

# for documentation of all options, see:
#   http://docs.mongodb.org/manual/reference/configuration-options/

# where to write logging data.
systemLog:
  destination: file
  logAppend: true
  path: /var/log/mongodb/mongod.log

# Where and how to store data.
storage:
  dbPath: /var/lib/mongo
  journal:
    enabled: true
#  engine:
#  mmapv1:
#  wiredTiger:

# how the process runs
processManagement:
  fork: true  # fork and run in background
  pidFilePath: /var/run/mongodb/mongod.pid  # location of pidfile
  timeZoneInfo: /usr/share/zoneinfo

# network interfaces
net:
  port: 27017
  bindIp: 127.0.0.1  # Listen to local interface only, comment to listen on all interfaces.


#security:

#operationProfiling:

#replication:

#sharding:

## Enterprise-Only Options

#auditLog:

#snmp:

先將 server 停掉

sudo systemctl stop mongod

備份舊資料

mv /var/lib/mongo-cfgsvr /var/lib/mongo-cfgsvr.bak
mv /var/lib/mongo /var/lib/mongo.bak

mkdir -p /var/lib/mongo-cfgsvr
chown -R mongod:mongod /var/lib/mongo-cfgsvr

mkdir -p /var/lib/mongo
chown -R mongod:mongod /var/lib/mongo

修改 os 設定

echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

ssh 免密碼登入

/etc/hosts

192.168.1.11   larzio1
192.168.1.12   larzio2
192.168.1.13   larzio3
# ssh 免密碼

yum -y install openssh-clients

larzio1:
mkdir -p /root/.ssh
cd /root/.ssh
ssh-keygen -t dsa
按enter直到完成

(id_larzio1.pub為自己辨識用的名稱)
scp id_dsa.pub 192.168.1.12:/root/.ssh/id_larzio1.pub
scp id_dsa.pub 192.168.1.13:/root/.ssh/id_larzio1.pub

ssh 192.168.1.12(13)
cd /root/.ssh/
cat id_larzio1.pub >> authorized_keys
完成ssh免密碼

larzio2:
cd /root/.ssh
ssh-keygen -t dsa
按enter直到完成

(id_larzio2.pub為自己辨識用的名稱)
scp id_dsa.pub 192.168.1.11:/root/.ssh/id_larzio2.pub
scp id_dsa.pub 192.168.1.13:/root/.ssh/id_larzio2.pub


ssh 192.168.1.11(13)
cd /root/.ssh/
cat id_larzio2.pub >> authorized_keys
完成ssh免密碼


larzio3:
cd /root/.ssh
ssh-keygen -t dsa
按enter直到完成

(id_larzio3.pub為自己辨識用的名稱)
scp id_dsa.pub 192.168.1.11:/root/.ssh/id_larzio3.pub
scp id_dsa.pub 192.168.1.12:/root/.ssh/id_larzio3.pub


ssh 192.168.1.11(12)
cd /root/.ssh/
cat id_larzio3.pub >> authorized_keys
完成ssh免密碼

Note 備份, 還原 資料庫

mongodump -u root -p passwd --authenticationDatabase admin -d larzio -o /root/download/backup/

mongorestore -u root -p passwd --authenticationDatabase admin -d larzio --drop /root/download/backup/larzio

Config Server

步驟

  1. security key file
  2. config server 設定檔
  3. 產生 config server db path
  4. 啟動 config node service
  5. 部署到其他兩台機器
  6. 建立 replica set

security keyfile

openssl rand -base64 756 > /root/mongodb-keyfile

mkdir -p /var/lib/mongo
chown mongod.mongod /var/lib/mongo

cp -p /root/mongodb-keyfile /var/lib/mongo/
chmod 400 /var/lib/mongo/mongodb-keyfile
chown mongod.mongod /var/lib/mongo/mongodb-keyfile
# 複製到其他兩台機器
scp /var/lib/mongo/mongodb-keyfile  root@192.168.1.12:/var/lib/mongo/mongodb-keyfile
scp /var/lib/mongo/mongodb-keyfile  root@192.168.1.13:/var/lib/mongo/mongodb-keyfile

# 在 12, 13
sudo chmod 400 /var/lib/mongo/mongodb-keyfile
sudo chown mongod.mongod /var/lib/mongo/mongodb-keyfile

config file

cp -p /etc/mongod.conf /etc/mongod-cfgsvr.conf

vi /etc/mongod-cfgsvr.conf
# 修改以下設定
#  systemLog.path
#  storage.dbPath
#  net.port
#  net.bindIp
#  security.keyFile
#  sharding.clusterRole

# mongod.conf

# for documentation of all options, see:
#   http://docs.mongodb.org/manual/reference/configuration-options/

# where to write logging data.
systemLog:
  destination: file
  logAppend: true
  logRotate: reopen
  path: /var/log/mongodb/mongod-cfgsvr.log

# Where and how to store data.
storage:
  dbPath: /var/lib/mongo-cfgsvr
  journal:
    enabled: true
#  engine:
#  mmapv1:
#  wiredTiger:

# how the process runs
processManagement:
  fork: true  # fork and run in background
  pidFilePath: /var/run/mongodb/mongod-cfgsvr.pid  # location of pidfile
  timeZoneInfo: /usr/share/zoneinfo

# network interfaces
net:
  port: 27018
  bindIp: 0.0.0.0  # Listen to local interface only, comment to listen on all interfaces.

security:
  keyFile: /var/lib/mongo/mongodb-keyfile
#  authorization: enabled

#operationProfiling:

replication:
  replSetName: rs-config

sharding:
  clusterRole: configsvr

## Enterprise-Only Options

#auditLog:

#snmp:

config server dbpath

mkdir -p /var/lib/mongo-cfgsvr
chown -R mongod:mongod /var/lib/mongo-cfgsvr

config server service

# 不要用這個方式直接啟動, 所有產生的檔案owner 都會是 root:root
#mongod -f /etc/mongod-cfgsvr.conf

建立 mongod-cfgsvr service file

vi /usr/lib/systemd/system/mongod-cfgsvr.service

[Unit]
Description=High-performance, schema-free document-oriented database
After=network.target
Documentation=https://docs.mongodb.org/manual

[Service]
User=mongod
Group=mongod
Environment="OPTIONS=-f /etc/mongod-cfgsvr.conf"
ExecStart=/usr/bin/mongod $OPTIONS
ExecStartPre=/usr/bin/mkdir -p /var/run/mongodb
ExecStartPre=/usr/bin/chown mongod:mongod /var/run/mongodb
ExecStartPre=/usr/bin/chmod 0755 /var/run/mongodb
PermissionsStartOnly=true
PIDFile=/var/run/mongodb/mongod-cfgsvr.pid
Type=forking
# file size
LimitFSIZE=infinity
# cpu time
LimitCPU=infinity
# virtual memory size
LimitAS=infinity
# open files
LimitNOFILE=64000
# processes/threads
LimitNPROC=64000
# locked memory
LimitMEMLOCK=infinity
# total threads (user+kernel)
TasksMax=infinity
TasksAccounting=false
# Recommended limits for for mongod as specified in
# http://docs.mongodb.org/manual/reference/ulimit/#recommended-settings

[Install]
WantedBy=multi-user.target

啟動

systemctl daemon-reload
systemctl enable mongod-cfgsvr
systemctl start mongod-cfgsvr

deploy

複製到其他兩台機器

scp /etc/mongod-cfgsvr.conf root@192.168.1.12:/etc/mongod-cfgsvr.conf
scp /etc/mongod-cfgsvr.conf root@192.168.1.13:/etc/mongod-cfgsvr.conf

scp /usr/lib/systemd/system/mongod-cfgsvr.service root@192.168.1.13:/usr/lib/systemd/system/mongod-cfgsvr.service

scp /usr/lib/systemd/system/mongod-cfgsvr.service root@192.168.1.12:/usr/lib/systemd/system/mongod-cfgsvr.service

在 12, 13

mkdir -p /var/lib/mongo-cfgsvr
chown -R mongod:mongod /var/lib/mongo-cfgsvr

# 啟動 config-svr
systemctl daemon-reload
systemctl enable mongod-cfgsvr
systemctl start mongod-cfgsvr

replica set

先回到 192.168.1.11 關掉 config server,註解 replica, shard 的設定部分,以免 create user 發生 "no master" 的問題

# sudo mongod -f /etc/mongod-cfgsvr.conf -shutdown
systemctl stop mongod-cfgsvr

vi /etc/mongod-cfgsvr.conf
#註解掉 replica 與 shard

重新啟動 config server

#sudo mongod -f /etc/mongod-cfgsvr.conf
systemctl start mongod-cfgsvr
mongo -port 27018

use admin

db.createUser( {
    user: "root",
    pwd: "passwd",
    roles: [ { role: "root", db: "admin" } ]
  });

db.auth('root', 'passwd');

db.createUser( {
    user: "admin",
    pwd: "passwd",
    roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
  });
vi /etc/mongod-cfgsvr.conf
# 開啟 replica 與 shard

重新啟動服務

#sudo mongod -f /etc/mongod-cfgsvr.conf -shutdown
#sudo mongod -f /etc/mongod-cfgsvr.conf
systemctl restart mongod-cfgsvr

登入 mongo

mongo -port 27018 -u 'root' -p 'passwd' -authenticationDatabase 'admin'

rs.initiate(
  {
    _id: "rs-config",
    configsvr: true,
    members: [
      { _id : 0, host : "192.168.1.11:27018" },
      { _id : 1, host : "192.168.1.12:27018" },
      { _id : 2, host : "192.168.1.13:27018" }
    ]
  }
)

rs.status()

Router Server

192.168.1.11, 192.168.1.12

config file

vi /etc/mongod-router.conf

# where to write logging data.
systemLog:
  destination: file
  logAppend: true
  logRotate: reopen
  path: /var/log/mongodb/mongod-router.log

processManagement:
  fork: true  # fork and run in background
  pidFilePath: /var/run/mongodb/mongod-router.pid  # location of pidfile
  timeZoneInfo: /usr/share/zoneinfo

# network interfaces
net:
  port: 27017
  bindIp: 0.0.0.0  # Listen to local interface only, comment to listen on all interfaces.


security:
  keyFile: /var/lib/mongo/mongodb-keyfile

sharding:
  configDB: rs-config/192.168.1.11:27018,192.168.1.12:27018,192.168.1.13:27018

router service

建立 mongod-router service file

vi /usr/lib/systemd/system/mongod-router.service

[Unit]
Description=High-performance, schema-free document-oriented database
After=network.target
Documentation=https://docs.mongodb.org/manual

[Service]
User=mongod
Group=mongod
Environment="OPTIONS=-f /etc/mongod-router.conf"
ExecStart=/usr/bin/mongos $OPTIONS
ExecStartPre=/usr/bin/mkdir -p /var/run/mongodb
ExecStartPre=/usr/bin/chown mongod:mongod /var/run/mongodb
ExecStartPre=/usr/bin/chmod 0755 /var/run/mongodb
PermissionsStartOnly=true
PIDFile=/var/run/mongodb/mongod-router.pid
Type=forking
# file size
LimitFSIZE=infinity
# cpu time
LimitCPU=infinity
# virtual memory size
LimitAS=infinity
# open files
LimitNOFILE=64000
# processes/threads
LimitNPROC=64000
# locked memory
LimitMEMLOCK=infinity
# total threads (user+kernel)
TasksMax=infinity
TasksAccounting=false
# Recommended limits for for mongod as specified in
# http://docs.mongodb.org/manual/reference/ulimit/#recommended-settings

[Install]
WantedBy=multi-user.target

啟動 router

# sudo mongos -f /etc/mongod-router.conf
systemctl daemon-reload
systemctl enable mongod-router
systemctl start mongod-router

deploy

scp /etc/mongod-router.conf root@192.168.1.12:/etc/mongod-router.conf

scp /usr/lib/systemd/system/mongod-router.service root@192.168.1.12:/usr/lib/systemd/system/mongod-router.service

在 192.168.1.12

systemctl daemon-reload
systemctl enable mongod-router
systemctl start mongod-router

Shard Server

config file

vi /etc/mongod.conf

# mongod.conf
# 修改
# - net.port
# - net.bindIp
# - security.keyFile

# for documentation of all options, see:
#   http://docs.mongodb.org/manual/reference/configuration-options/

# where to write logging data.
systemLog:
  destination: file
  logAppend: true
  logRotate: reopen
  path: /var/log/mongodb/mongod.log

# Where and how to store data.
storage:
  dbPath: /var/lib/mongo
  journal:
    enabled: true
#  engine:
#  mmapv1:
#  wiredTiger:

# how the process runs
processManagement:
  fork: true  # fork and run in background
  pidFilePath: /var/run/mongodb/mongod.pid  # location of pidfile
  timeZoneInfo: /usr/share/zoneinfo

# network interfaces
net:
  port: 27019
  bindIp: 0.0.0.0

security:
    keyFile: /var/lib/mongo/mongodb-keyfile

#operationProfiling:

#replication:

#sharding:

## Enterprise-Only Options

#auditLog:

在另外兩台 server 做一樣的設定 接下來,先啟動第一台 shard server,建立 replica set 之前必須要有 root user,否則會失敗。

scp /etc/mongod.conf root@192.168.1.12:/etc/mongod.conf
scp /etc/mongod.conf root@192.168.1.13:/etc/mongod.conf

啟動第一台 shard server

建立使用者

systemctl start mongod
mongo -port 27019

use admin

db.createUser( {
    user: "root",
    pwd: "passwd",
    roles: [ { role: "root", db: "admin" } ]
  });

db.auth('root', 'passwd');

db.createUser( {
    user: "admin",
    pwd: "passwd",
    roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
  });

關閉 mongod 服務

systemctl stop mongod

修改

vi /etc/mongod.conf

replication:
  replSetName: rs-data
    
sharding:
  clusterRole: shardsvr

啟動 mongod

systemctl start mongod

在 192.168.1.12, 192.168.1.13 複製設定, 啟動 mogod

scp /etc/mongod.conf root@192.168.1.12:/etc/mongod.conf
scp /etc/mongod.conf root@192.168.1.13:/etc/mongod.conf
systemctl start mongod

登入 mongo 並設定 replica

mongo -port 27019 -u 'root' -p 'passwd' -authenticationDatabase 'admin'

rs.initiate(
  {
    _id: "rs-data",
    configsvr: false,
    members: [
      { _id : 0, host : "192.168.1.11:27019" },
      { _id : 1, host : "192.168.1.12:27019" },
      { _id : 2, host : "192.168.1.13:27019" }
    ]
  }
)

登入 router (mongos) 設定 shard

mongo -port 27017 -u 'root' -p 'passwd' -authenticationDatabase 'admin'

sh.addShard('rs-data/192.168.1.11:27019,192.168.1.12:27019,192.168.1.13:27019')

把認證模式給開啟,要加入認證模式的有 config 與 shard 開啟很簡單只要將 security.authorization 設定為 enabled

vi /etc/mongod-cfgsvr.conf

security:
    keyFile: /var/lib/mongo/mongodb-keyfile
    authorization: enabled
vi /etc/mongod.conf

security:
    keyFile: /var/lib/mongo/mongodb-keyfile
    authorization: enabled
scp /etc/mongod-cfgsvr.conf root@192.168.1.12:/etc/mongod-cfgsvr.conf
scp /etc/mongod-cfgsvr.conf root@192.168.1.13:/etc/mongod-cfgsvr.conf

scp /etc/mongod.conf root@192.168.1.12:/etc/mongod.conf
scp /etc/mongod.conf root@192.168.1.13:/etc/mongod.conf

重新啟動服務

systemctl restart mongod
systemctl restart mongod-cfgsvr

logrotate

vim /etc/logrotate.d/mongod

/var/log/mongodb/mongod.log  {
    daily
    missingok
    rotate 30
    copytruncate
    dateext
    compress
    notifempty
    create 644 mongod mongod
    sharedscripts
    postrotate
        /bin/kill -SIGUSR1 'cat /var/run/mongodb/mongod.pid 2> /dev/null' 2> /dev/null || true
    endscript
}

vim /etc/logrotate.d/mongod-cfgsvr


/var/log/mongodb/mongod-cfgsvr.log {
    daily
    missingok
    rotate 30
    copytruncate
    dateext
    compress
    notifempty
    create 644 mongod mongod
    sharedscripts
    postrotate
        /bin/kill -SIGUSR1 'cat /var/run/mongodb/mongod-cfgsvr.pid 2> /dev/null' 2> /dev/null || true
    endscript
}

vim /etc/logrotate.d/mongod-router


/var/log/mongodb/mongod-router.log {
    daily
    missingok
    rotate 30
    copytruncate
    dateext
    compress
    notifempty
    create 644 mongod mongod
    sharedscripts
    postrotate
        /bin/kill -SIGUSR1 'cat /var/run/mongodb/mongod-router.pid 2> /dev/null' 2> /dev/null || true
    endscript
}

測試

logrotate -f -v /etc/logrotate.d/mongod

database user

在目標資料庫建立使用者

mongo -port 27017 -u 'root' -p 'passwd' -authenticationDatabase 'admin'

use larzio

db.createUser({
    user: "larzio",
    pwd: "passwd",
    roles: [{ role: "readWrite", db: "larzio" }, { role: "dbAdmin", db: "larzio" }]
})

References

在 cent os 7上安裝 mongodb with Sharded Cluster (1) config server

在 cent os 7上安裝 mongodb with Sharded Cluster (2) router server 與 shard server

MongoDB Sharding 分散式儲存架構建置 (實作篇)

2021/04/12

mongodb 帳號驗證權限

資料庫的權限有四種

  1. readAnyDatabase 任何資料庫的唯讀權限
  2. userAdminAnyDatabase 任何資料庫的讀寫權限
  3. userAdminAnyDatabase 任何資料庫用戶的管理權限
  4. dbAdminAnyDatabase 任何資料庫的管理權限

以往在啟動 mongodb 時,就直接指定 mongod.conf

mongod -f $MONGODB_HOME/conf/mongod.conf

這時候可用 mongo client 連接資料庫

./mongo

查看有沒有 users

db.system.users.find()

查看 user

show users

建立 admin 資料庫的管理者帳號

use admin

db.createUser({
  user : "root",
  pwd : "password",
  roles : [
    "clusterAdmin",
    "dbAdminAnyDatabase",
    "userAdminAnyDatabase",
    "readWriteAnyDatabase"
  ]
})

重新啟動 mognodb (加上 --auth 參數)

mongod --auth -f $MONGODB_HOME/conf/mongod.conf

再次查看 users

> use admin
> db.system.users.find()
Error: error: {
    "ok" : 0,
    "errmsg" : "command find requires authentication",
    "code" : 13,
    "codeName" : "Unauthorized"
}
> show users
2020-09-02T11:36:48.994+0800 E QUERY    [js] Error: command usersInfo requires authentication :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
DB.prototype.getUsers@src/mongo/shell/db.js:1763:1
shellHelper.show@src/mongo/shell/utils.js:859:9
shellHelper@src/mongo/shell/utils.js:766:15
@(shellhelp2):1:1

驗證後,就可以查看 users

> use admin
switched to db admin
> db.auth("root", "password")
1
> show users
{
    "_id" : "admin.root",
    "user" : "root",
    "db" : "admin",
    "roles" : [
        {
            "role" : "clusterAdmin",
            "db" : "admin"
        },
        {
            "role" : "dbAdminAnyDatabase",
            "db" : "admin"
        },
        {
            "role" : "userAdminAnyDatabase",
            "db" : "admin"
        },
        {
            "role" : "readWriteAnyDatabase",
            "db" : "admin"
        }
    ],
    "mechanisms" : [
        "SCRAM-SHA-1",
        "SCRAM-SHA-256"
    ]
}

針對資料庫,建立管理者

use admin

db.createUser({
  user : "maxkit",
  pwd : "password",
  roles : [
    {role:"readWrite", db:"larzio"}
  ]
})

針對資料庫,建立一般使用者

use larzio

db.createUser({
  user : "maxkit",
  pwd : "max168kit",
  roles : [
    {role:"readWrite", db:"larzio"}
  ]
})

刪除帳號

use admin

db.dropUser("maxkit")

關閉 mognodb

mongo 127.0.0.1:27017 -u root -p 'password' --authenticationDatabase 'admin' --eval "db.getSiblingDB('admin').shutdownServer()"

References

Mongodb 創建管理員帳號與普通帳號

2016/01/18

Geospatial Index in MongoDB

MongoDB 自 2.6 版開始就支援儲存座標點的欄位,並能為該欄位建立 Geospatial Indexes,提供使用者快速搜尋附近的座標點資料的資料查詢功能。例如最簡單的應用:找到我附近的夥伴或是找到家裡附近的餐廳。

座標點 [longitude, latitude]

座標點的資料是兩個浮點數字組成的,先寫 longitude 經度,再寫 latitude 緯度,在思考座標點資料時,必須要用平面座標來看,原點在正中央,x軸往右為正數,y軸往上為正數,這跟跟矩陣的元素表示方法不同,先列後行。

[longitude, latitude]

基本 2d 平面查詢

要測試最基本的平面查詢,我們先產生 100 個座標點的測試資料,其實座標點資料就只是單純的兩個數字而已,重要的是,在建立 index 時,要指定用 2d 的 index:

db.ex.drop()
for( var i=0; i<100; i++ ) {
    db.ex.insert({pos:[i%10, Math.floor(i/10)]})
}

db.ex.ensureIndex({pos:"2d"})

用 $near 查詢時,回傳的資料點會依照距離遠近的順序傳送回來。

> db.ex.find({pos:{$near:[5,5]}})
{ "_id" : ObjectId("565e634fbd3fd37d48c0a691"), "pos" : [ 5, 5 ] }
{ "_id" : ObjectId("565e634fbd3fd37d48c0a690"), "pos" : [ 4, 5 ] }
{ "_id" : ObjectId("565e634fbd3fd37d48c0a69b"), "pos" : [ 5, 6 ] }
{ "_id" : ObjectId("565e634fbd3fd37d48c0a687"), "pos" : [ 5, 4 ] }
{ "_id" : ObjectId("565e634fbd3fd37d48c0a692"), "pos" : [ 6, 5 ] }
{ "_id" : ObjectId("565e634fbd3fd37d48c0a686"), "pos" : [ 4, 4 ] }
.....

以下這個圖形將所有座標點都以一個圓點表示,原點[0,0]位於左下角。

以下是有距離範圍的查詢,但回傳的資料點,並不保證依照距離由小到大排列

position:{$within:[$center, $box, $ploygon, $centerSphere]}

首先是 $center 的測試,我們測試一個半徑為 2 的圓形涵蓋的範圍:

> db.ex.find( {pos:{$within:{$center:[[5,5],2]}}} , {_id:0} )
{ "pos" : [ 4, 4 ] }
{ "pos" : [ 3, 5 ] }
{ "pos" : [ 4, 5 ] }
{ "pos" : [ 5, 3 ] }
{ "pos" : [ 5, 4 ] }
{ "pos" : [ 5, 5 ] }
{ "pos" : [ 4, 6 ] }
{ "pos" : [ 5, 6 ] }
{ "pos" : [ 5, 7 ] }
{ "pos" : [ 6, 4 ] }
{ "pos" : [ 6, 5 ] }
{ "pos" : [ 7, 5 ] }
{ "pos" : [ 6, 6 ] }

$box 是一個矩形

> db.ex.find( {pos:{$within:{$box:[[5,5],[6,6]]}}} , {_id:0} )
{ "pos" : [ 5, 5 ] }
{ "pos" : [ 5, 6 ] }
{ "pos" : [ 6, 5 ] }
{ "pos" : [ 6, 6 ] }

$polygon 則是一個多邊形

> db.ex.find( {pos:{$within:{$polygon:[[3,4],[5,7], [7,3]]}}} , {_id:0} )
{ "pos" : [ 3, 4 ] }
{ "pos" : [ 4, 4 ] }
{ "pos" : [ 4, 5 ] }
{ "pos" : [ 5, 4 ] }
{ "pos" : [ 5, 5 ] }
{ "pos" : [ 5, 6 ] }
{ "pos" : [ 5, 7 ] }
{ "pos" : [ 6, 4 ] }
{ "pos" : [ 7, 3 ] }
{ "pos" : [ 6, 5 ] }

Geospatial Indexes

剛剛的 2d 查詢,看起來還蠻簡單的,但 MongoDB 的 Geospatial Indexes 不光只支援這個功能而已。

MongoDB 支援兩種平面的模型 surface type 1. Spherical: 2dsphere index 座標點是放在像地球一樣的球面上,由於是球面的關係,距離跟相對關係的計算,跟一般的平面座標是不同的 2. Flat: 2d index 傳統的平面座標

如果是一般的 2d index,座標點的儲存只支援 legacy coordinate pairs的表示方式,也就是 [x,y],前面是 longitude 經度,後面是 latitude 緯度。

但如果是 2dsphere index,除了支援 legacy coordinate pairs 的資料外,還可以用 GeoJSON object 的表示方式儲存,預設是以 WGS84 datum,也就是 Degrees, Minutes, Seconds 的地理位置表示方式。

WGS 84 座標點表示系統有五種,可以用 PGC Coordinate Converter 進行轉換的計算,比較常見的有兩種 DMS 及 DD,但要注意的是 GeoJSON 預設是先寫緯度,再寫經度,但 MongoDB 卻是前面是 longitude 經度,後面是 latitude 緯度。

  1. DMS (Degrees, Minutes, Seconds) ex: 24° 15' 28.0800" N, 120° 53' 36.6000" E
  2. DD (Decimal Degrees) ex: 24.2578, 120.8935

GeoJSON 是以 JSON 表示地理資料結構的格式,由兩個部份組成:

  1. type: 有以下這些類型 Point, LineString, Polygon, MultiPoint, MultiLineString, and MultiPolygon, GeometryCollection

  2. coordinates 是這個類型資料的座標點的集合

我們可以透過 http://geojsonlint.com/ 測試 GeoJSON,並將結果顯示在地圖上。

// Point
{
  "type": "Point",
  "coordinates": [
    120.8935,  
    24.2578 
  ]
}
// LineString
{
  "type": "LineString",
  "coordinates": [
    [120.7000,24.2000],
    [120.6900,24.1900],
    [120.6815,24.1833],
    [120.5900,24.1500]
  ]
}

透過 GeoJSON Editor 用線跟多邊形,把文心森林公園框起來:

{
  "type" : "GeometryCollection",
  "geometries" : [
    {
      "type" : "Polygon",
      "coordinates": [
          [
            [
              120.64390238374472,
              24.146822125175643
            ],
            [
              120.64660605043173,
              24.146743804632557
            ],
            [
              120.64653094857931,
              24.14385570109112
            ],
            [
              120.64319428056479,
              24.144188570589225
            ],
            [
              120.64390238374472,
              24.146822125175643
            ]
          ]
        ]
    },
    {
      "type" : "LineString",
      "coordinates": [
          [
            120.64390238374472,
            24.14681233511038
          ],
          [
            120.64656313508749,
            24.146753594703075
          ],
          [
            120.64652021974325,
            24.14385570109112
          ],
          [
            120.64322646707296,
            24.144188570589225
          ],
          [
            120.64390238374472,
            24.14681233511038
          ]
        ]
     }
  ]
}

geospatial query operators

geospatial index 有兩種,geospatial index 不能當作 shard key index,而且在 sharded collection 中,不能使用 $near, $nearSphere,可以用 $geoNear aggregation 取代。

  • 2d 支援平面幾何的計算 legacy coordinate pairs [longitude, latitude]

  • 2dsphere 支援球面的計算 可儲存 GeoJSON 或是 legacy coordinate pairs

MongoDB 的 Query Selectors 有以下這4種

  1. $geoWithin Inclusion 在 GeoJSON geomerty 的裡面的圖形,2d 及 2dsphere indexes 都有支援

  2. $geoIntersects Intersect 跟 GeoJSON geomerty 的相交的圖形, 只有 2dsphere index 支援

  3. $near Proximity 接近某個點的 geospatial objects,2d 及 2dsphere indexes 都有支援

  4. $nearSphere Proximity 在球面上,接近某個點的 geospatial objects,2d 及 2dsphere 都支援

Geometry Specifiers 有以下這幾種

  1. $geometry 以 GeoJSON 格式指定的 geometry
  2. $minDistance 最短距離,用在 $near, $nearSphere,只能用在 2dsphere index
  3. $maxDistance 最長距離,用在 $near, $nearSphere, 2d 及 2dsphere 都支援
  4. $center 圓形,用在 $geoWithin queries 裡面, 2d 支援
  5. $centerSphere 圓形,用在 $geoWithin queries 裡面,可用 [x,y] 或 GeoJSON 格式,2d 及 2dsphere 都支援
  6. $box 矩形,用在 $geoWithin queries 裡面, 2d 支援
  7. $polygon 用 [x,y] 描述的多邊形,用在 $geoWithin queries 裡面, 2d 支援

References

Geospatial Indexes and Queries Geospatial Index Tutorials Geospatial Indexing with MongoDB Presentation

如何基於mongodb來實現用戶當前位置距離顯示順序功能

MongoDB – Geospatial Queries

Spring Data – Part 4: Geospatial Queries with MongoDB

2016/01/11

Production Cluster Testing in MongoDB

基於 MongoDB 正式環境的一些基本要求,應該是要用三台機器來測試 MongoDB,但為了測試 mongodb driver 的 cluster 機制,又沒有那麼多實體的機器,這次先用單機來了解 client 由遠端連接 MongoDB 的一些狀況。

環境設定

一個由三個 replica set 組成的 shard server,三個 config server,三個 route server。

shard1: 3 replica sets
192.168.1.11:27017
192.168.1.11:28017
192.168.1.11:29017

3 config server
192.168.1.11:30000
192.168.1.11:31000
192.168.1.11:32000

3 route server
192.168.1.11:40000
192.168.1.11:41000
192.168.1.11:42000

shard1 的設定以及啟動:

mkdir -p /home/mongodb/shard/data/s1_1
mkdir -p /home/mongodb/shard/data/s1_2
mkdir -p /home/mongodb/shard/data/s1_3
mkdir -p /home/mongodb/shard/logs

/usr/share/mongodb/bin/mongod --port 27017 --fork --dbpath /home/mongodb/shard/data/s1_1 --logpath /home/mongodb/shard/logs/s1_1.log --logappend --shardsvr --replSet shard1 --directoryperdb

/usr/share/mongodb/bin/mongod --port 28017 --fork --dbpath /home/mongodb/shard/data/s1_2 --logpath /home/mongodb/shard/logs/s1_2.log --logappend --shardsvr --replSet shard1 --directoryperdb

/usr/share/mongodb/bin/mongod --port 29017 --fork --dbpath /home/mongodb/shard/data/s1_3 --logpath /home/mongodb/shard/logs/s1_3.log --logappend --shardsvr --replSet shard1 --directoryperdb

連接其中一台,並設定 shard1 的三個 replica set 成員

mongo --port 27017

config_shard1={
    _id: 'shard1',
    members: [
        {_id:0, host:'192.168.1.11:27017'},
        {_id:1, host:'192.168.1.11:28017'},
        {_id:2, host:'192.168.1.11:29017'}
    ]
};

rs.initiate(config_shard1);
rs.status();
rs.isMaster();

建立管理帳號 admin 以及連接 test database 的測試帳號 test

db.createUser({
    user: "admin",
    pwd: "pass",
    roles: [ { role: "root", db: "admin" } ]
});

use test
db.createUser(
    {
      user: "test",
      pwd: "pass",
      roles: [
         { role: "readWrite", db: "test" }
      ]
    }
);
db.getUsers();

啟動三個 config servers

mkdir -p /home/mongodb/shard/data/config1
mkdir -p /home/mongodb/shard/data/config2
mkdir -p /home/mongodb/shard/data/config3

/usr/share/mongodb/bin/mongod --configsvr --fork --port 30000 --dbpath /home/mongodb/shard/data/config1 --logpath /home/mongodb/shard/logs/config1.log --logappend

/usr/share/mongodb/bin/mongod --configsvr --fork --port 31000 --dbpath /home/mongodb/shard/data/config2 --logpath /home/mongodb/shard/logs/config2.log --logappend

/usr/share/mongodb/bin/mongod --configsvr --fork --port 32000 --dbpath /home/mongodb/shard/data/config3 --logpath /home/mongodb/shard/logs/config3.log --logappend

啟動三個 route servers

/usr/share/mongodb/bin/mongos --port 40000 --configdb 192.168.1.11:30000,192.168.1.11:31000,192.168.1.11:32000 --fork  --logpath /home/mongodb/shard/logs/route1.log --logappend --chunkSize 1

/usr/share/mongodb/bin/mongos --port 41000 --configdb 192.168.1.11:30000,192.168.1.11:31000,192.168.1.11:32000 --fork  --logpath /home/mongodb/shard/logs/route2.log --logappend --chunkSize 1

/usr/share/mongodb/bin/mongos --port 42000 --configdb 192.168.1.11:30000,192.168.1.11:31000,192.168.1.11:32000 --fork  --logpath /home/mongodb/shard/logs/route3.log --logappend --chunkSize 1

連接到 route server,設定 shard1 並設定 sharding 的 database

mongo --host 192.168.1.11 --port 40000

use admin
db.runCommand({addshard:"shard1/192.168.1.11:27017,192.168.1.11:28017,192.168.1.11:29017"});
db.runCommand({enablesharding:"test"});
db.runCommand({shardcollection:"test.users", key:{_id:1}});
db.runCommand({shardcollection:"test.doc", key:{_id:1}});

直接測試新增 500000 筆資料

for(var i=1;i<500000; i++) {
    db.users.insert({
        userid:"user_"+i,
        username:"name_"+i,
        age: NumberInt(_rand()*100)
    })
}

use test
db.users.stats();

MongoDB Client Drivers

MongoDB 官方提供的 client driver libary 很完整地支援了多種語言:C, C++, C#, Java, Node.js, Perl, PHP, Python, Motor, Ruby, Scala,另外有兩個社群提供的 driver:Go, Erlang。

使用 driver 之前,最重要的是知道連接 MongoDB 的 connection string

格式為:

mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]

參考 node.js 的 mongoclient 說明頁面,有比較完整的 options 的設定項目。

因為我們建立了三個 route server,因此連線的 uri 填寫為以下的 connection string。

mongodb://192.168.1.11:40000,192.168.1.11:41000,192.168.1.11:42000/test?maxPoolSize=20

我們就利用 scala driver 的 QuickTour Sample,記得要同時把 Helpers.scala 以及 QuickTour.scala 都取回來編譯。

首先建立一個 scala sbt project,然後在 build.sbt 裡面加上一行 mongodb scala driver,接下來把剛剛的 Helper.scala 及 QuickTour.scala 放進 scala soruce 裡面,就可以了。

name := "mongodb"

version := "1.0"

scalaVersion := "2.11.7"

libraryDependencies += "org.mongodb.scala" %% "mongo-scala-driver" % "1.0.0"

接下來只擷取部份的程式碼,也就是我們修改的部份:

首先我們修改 mongodb 的 connection string,連接到三個 route server

    val mongoClient:MongoClient = MongoClient("mongodb://192.168.1.11:40000,192.168.1.11:41000,192.168.1.11:42000/test?maxPoolSize=20")

剛剛我們把 database:test, collection: doc 加入 shardcollection 中

    // get handle to "mydb" database
    val database: MongoDatabase = mongoClient.getDatabase("test")

    // get a handle to the "test" collection
    val collection: MongoCollection[Document] = database.getCollection("doc")

然後是清除 collection,並測試插入一筆資料

    collection.drop().results()

    // make a document and insert it
    val doc: Document = Document("_id" -> 0, "docid" -> "test", "type" -> "database",
      "count" -> 1, "page" -> Document("x" -> 100, "y" -> 200))
    collection.insertOne(doc).results()
    // get it (since it's the only one in there since we dropped the rest earlier on)
    collection.find.first().printResults()

讓 Thread 暫停 30 秒,因為我們想要測試在執行到一半,把其中一台 route server 關掉的狀況。

    Thread sleep 30*1000

當我們執行 QuickTour.scala 的時候,console 會列印以下的資訊:

資訊: Monitor thread successfully connected to server with description ServerDescription{address=192.168.1.11:41000, type=SHARD_ROUTER, state=CONNECTED, ok=true, version=ServerVersion{versionList=[3, 0, 7]}, minWireVersion=0, maxWireVersion=3, electionId=null, maxDocumentSize=16777216, roundTripTimeNanos=39787239}
十一月 20, 2015 5:25:53 下午 com.mongodb.diagnostics.logging.JULLogger log
資訊: Opened connection [connectionId{localValue:4}] to 192.168.1.11:40000
十一月 20, 2015 5:25:53 下午 com.mongodb.diagnostics.logging.JULLogger log
資訊: Opened connection [connectionId{localValue:5}] to 192.168.1.11:42000
{ "_id" : 0, "name" : "MongoDB", "type" : "database", "count" : 1, "axis" : { "x" : 100, "y" : 200 } }

這時候程式會因為 Thread sleep 30*1000 而暫停下來。 接下來有30s 的時間,可以連線到 port 40000 的 route server,並把這個 route server 關掉。

mongo --port 40000

use admin
db.shutdownServer();

當我們把 route server 關掉的瞬間,scala 程式的 console 就會馬上發現,192.168.1.11:40000 這個 route server 現在有問題,沒辦法使用了。

十一月 20, 2015 5:26:04 下午 com.mongodb.diagnostics.logging.JULLogger log
資訊: Exception in monitor thread while connecting to server 192.168.1.11:40000
com.mongodb.MongoException: java.io.IOException: 遠端電腦拒絕網路連線。

    at com.mongodb.connection.InternalStreamConnection.open(InternalStreamConnection.java:125)
    at com.mongodb.connection.DefaultServerMonitor$ServerMonitorRunnable.run(DefaultServerMonitor.java:141)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.io.IOException: 遠端電腦拒絕網路連線。

    at sun.nio.ch.Iocp.translateErrorToIOException(Iocp.java:309)
    at sun.nio.ch.Iocp.access$700(Iocp.java:46)
    at sun.nio.ch.Iocp$EventHandlerTask.run(Iocp.java:399)
    ... 1 more

接下來的程式中,當我們要繼續使用 mongodb,client driver 會自動選擇下一個 route server: 192.168.1.11:41000,然後繼續執行後面的程式。

十一月 20, 2015 5:26:23 下午 com.mongodb.diagnostics.logging.JULLogger log
資訊: Opened connection [connectionId{localValue:8}] to 192.168.1.11:41000
total # of documents after inserting 100 small ones (should be 101):  101

MongoDB 的 driver 真的做得很完整,不但有內建 connection pool 還有自動 fail over 的能力。

2016/01/04

How to Backup & Retore in MangoDB

以下測試如何備份(mongodump)以及還原(mongorestore) MongoDB 資料庫。

備份 mongodump

在備份資料前,必須先讓在記憶體中的資料同步到磁碟中(fsync),並將資料庫上鎖(lock),避免備份過程中,還有資料異動。

use admin
db.runCommand({fsync:1, lock:1})

查詢 DB 的狀態,看看是不是上鎖了

db.currentOp()

然後以 mongodump 進行資料備份

mkdir -p /home/mongodb/project/data/backup
mongodump -d testdatabase -o /home/mongodb/project/data/backup

也可以直接備份遠端的 DB

mongodump -h 192.168.1.11 -p 37017 -d testdatabase -o /home/mongodb/project/data/backup

備份完成後,要解鎖資料庫

db.fsyncUnlock()

執行過程如下:

> use admin
switched to db admin
> db.runCommand({fsync:1, lock:1})
{
    "info" : "now locked against writes, use db.fsyncUnlock() to unlock",
    "seeAlso" : "http://dochub.mongodb.org/core/fsynccommand",
    "ok" : 1
}


> db.currentOp()
{
    "inprog" : [ ],
    "fsyncLock" : true,
    "info" : "use db.fsyncUnlock() to terminate the fsync write/snapshot lock"
}

在另一個 terminal 進行備份

[root@server backup]# /usr/share/mongodb/bin/mongodump -d testdatabase -o /home/mongodb/project/data/backup
2015-11-30T14:43:18.585+0800    writing testdatabase.tracker to /home/mongodb/project/data/backup/testdatabase/tracker.bson
2015-11-30T14:43:18.585+0800    writing testdatabase.user to /home/mongodb/project/data/backup/testdatabase/user.bson
2015-11-30T14:43:18.594+0800    writing testdatabase.user metadata to /home/mongodb/project/data/backup/testdatabase/user.metadata.json
2015-11-30T14:43:18.612+0800    done dumping testdatabase.user (1000 documents)
2015-11-30T14:43:18.612+0800    writing testdatabase.system.indexes to /home/mongodb/project/data/backup/testdatabase/system.indexes.bson
2015-11-30T14:43:18.612+0800    writing testdatabase.event to /home/mongodb/project/data/backup/testdatabase/event.bson
2015-11-30T14:43:18.612+0800    writing testdatabase.event metadata to /home/mongodb/project/data/backup/testdatabase/event.metadata.json
2015-11-30T14:43:18.612+0800    done dumping testdatabase.event (0 documents)
2015-11-30T14:43:18.612+0800    writing testdatabase.eventlog to /home/mongodb/project/data/backup/testdatabase/eventlog.bson
2015-11-30T14:43:18.613+0800    writing testdatabase.eventlog metadata to /home/mongodb/project/data/backup/testdatabase/eventlog.metadata.json
2015-11-30T14:43:18.614+0800    done dumping testdatabase.eventlog (0 documents)
2015-11-30T14:43:21.120+0800    writing testdatabase.tracker metadata to /home/mongodb/project/data/backup/testdatabase/tracker.metadata.json
2015-11-30T14:43:21.121+0800    done dumping testdatabase.tracker (759461 documents)

最後 unlock db

> db.fsyncUnlock()
{ "ok" : 1, "info" : "unlock completed" }

還原 mongorestore

還原指令如下

/usr/share/mongodb/bin/mongorestore -d testdatabase --drop /home/mongodb/project/data/backup/testdatabase

--drop 的部份,就是先將這裡的 database 刪除,然後再進行 restore

執行過程如下

[root@server backup]# /usr/share/mongodb/bin/mongorestore -d testdatabase --drop /home/mongodb/project/data/backup/testdatabase
2015-11-30T14:52:45.306+0800    building a list of collections to restore from /home/mongodb/project/data/backup/testdatabase dir
2015-11-30T14:52:45.306+0800    reading metadata file from /home/mongodb/project/data/backup/testdatabase/tracker.metadata.json
2015-11-30T14:52:45.306+0800    restoring testdatabase.tracker from file /home/mongodb/project/data/backup/testdatabase/tracker.bson
2015-11-30T14:52:45.308+0800    reading metadata file from /home/mongodb/project/data/backup/testdatabase/user.metadata.json
2015-11-30T14:52:45.309+0800    restoring testdatabase.user from file /home/mongodb/project/data/backup/testdatabase/user.bson
2015-11-30T14:52:45.310+0800    reading metadata file from /home/mongodb/project/data/backup/testdatabase/event.metadata.json
2015-11-30T14:52:45.812+0800    restoring testdatabase.event from file /home/mongodb/project/data/backup/testdatabase/event.bson
2015-11-30T14:52:45.812+0800    restoring indexes for collection testdatabase.event from metadata
2015-11-30T14:52:45.813+0800    reading metadata file from /home/mongodb/project/data/backup/testdatabase/eventlog.metadata.json
2015-11-30T14:52:45.813+0800    restoring testdatabase.eventlog from file /home/mongodb/project/data/backup/testdatabase/eventlog.bson
2015-11-30T14:52:45.815+0800    finished restoring testdatabase.event (0 documents)
2015-11-30T14:52:45.815+0800    restoring indexes for collection testdatabase.eventlog from metadata
2015-11-30T14:52:45.817+0800    finished restoring testdatabase.eventlog (0 documents)
2015-11-30T14:52:45.821+0800    restoring indexes for collection testdatabase.user from metadata
2015-11-30T14:52:45.822+0800    finished restoring testdatabase.user (1000 documents)
2015-11-30T14:52:48.306+0800    [#####...................]  testdatabase.tracker  76.7 MB/342.5 MB  (22.4%)
2015-11-30T14:52:51.306+0800    [############............]  testdatabase.tracker  184.9 MB/342.5 MB  (54.0%)
2015-11-30T14:52:54.306+0800    [####################....]  testdatabase.tracker  293.1 MB/342.5 MB  (85.6%)
2015-11-30T14:52:57.025+0800    restoring indexes for collection testdatabase.tracker from metadata
2015-11-30T14:52:57.025+0800    finished restoring testdatabase.tracker (759461 documents)
2015-11-30T14:52:57.025+0800    done

2015/12/28

Production Cluster Architecture in MongoDB: Replica Sets + Sharding

為了建立一個完整的高可用性的 MongoDB 資料庫,我們必須要整合使用 Replica Sets 跟 Sharding。

  1. Shard Server: 2 個以上的 shard server,以 replica sets 建立 shard server,確保每一個節點都有備份,自動容錯及復原的能力
  2. Config Server: 設定 3 個 config server,一定要剛好 3個
  3. Route Server: 1 個以上的 query routers,通常可以在每一個 application server 都配置一個 mongos instance

MongoDB in Trend

在這篇文章中 趨勢科技導入MongoDB 追蹤管理全球10萬個行動裝置,MongoDB 的架構圖其實跟官方提出的架構概念是一致的。

如果是以 Application 的角度來看 MongoDB 的架構,概念上是接近以下這個架構圖。

3 physical servers

因為三個 config server 的基本要求,因此我們最低的硬體要求,是需要三台實體的 server。另外考量到整個 cluster 環境的完整的備援。 MongoDB的Replica Sets+Sharding架構 這篇文章提出了一個架構。

Production Note

MongoDB 官方在 Production Notes 提供了一些在正式環境應該要注意的事項。

  • Storage Engine 有兩種 MMAPv1 及 WiredTiger,預設是使用 MMAPv1。 mongod 在啟動時,會檢查 dbPath 裡面既有的資料,是不是指定了不同的 storage engine。

  • OS Mac OS X, Linux, Windows Server 2012, Windows Server 2008 R2 64bit, Windows 7 (64 bit), Windows Vista, and Solaris 都可以用,但 Production 環境建議要使用 64bits OS。

  • Concurrency MMAPv1 提供 collection-level locking,所有的 collections 都有一個唯一的 read-write lock,允許多個 clients 同時在不同的 collections 裡面修改文件。

    WiredTiger 在一個 collection 裡面 readers 與 writers 同時存取文件的機制,clients 可在其他 write operations 進行過程中,還能讀取文件,多個 threads 可以同時修改在某一個 collection中不同的文件。

  • Data Consistency

    1. Journaling: MongoDB 採取了 write ahead logging to an on-disk journal 的機制,可在 mongod crash 時,還能處理尚未儲存到 data files 的資料。最好要保持 journaling 的功能開啟的狀態。

    2. Write Concern: 當 MongoDB 處理 write operation 時,可設定 guarantee 的強度,當以 weak write concern 執行 insert,update,delete 時,這些 operation 會快速完成,但風險是當發生錯誤時,就有可能會發生資料錯誤的狀況。

  • Networking

    1. Use Trusted Networking Environments MongoDB 預設沒有啟用 authorization 機制,MongoDB 是以 Role-Based Access Control 的方式,處理 client 端授權的問題。

    2. Disable HTTP Interfaces MongoDB 有提供透過 HTTP 查詢 server status 的機制,正式環境要把這個功能關掉。

    3. Manage Connection Pool Sizes 避免在單一個 mongod 或 mongos,使用太多 connection resources,確保 client 只使用了合理的 connection pool sizes,通常設定為 concurrent db requests 總量的 110%~115% 就可以了。connPoolStats 指令可查詢 open connections 的資訊。

  • HW Considerations

    1. MongoDB 核心是使用 litte-endian 硬體,主要是 x86/x86_64 的 CPU,而 client 可以使用 big 或 little endian 系統

    2. 配置足夠的 RAM 及 CPU MMAPv1 不需要太多 CPU cores,增加 RAM 可減少 MongoDB 產生 page faults 的頻率。

      WiredTiger 需要很多 CPU cores 支援,active threads(concurrent operations) 跟 CPU 總數有關係。

      storage.wiredTiger.engineConfig.cacheSizeGB 會限制 cache 的上限。

      可透過 mongostat 指令查看 ar|aw 欄位,得知 active reads/writes 的數量。

    3. 使用 Solid State Disk (SSD)

    4. NUMA (Non-Uniform Access Memory) HW NUMA 是一種記憶體的實作方式,CPU 存取 local memory 的速度會比存取 shard memory 還快。MongoDB 在 NUMA 環境中使用,會遇到很多問題,可能會越來越慢。

  • Disk and Storage System

    • Swap

    1. 為系統設定 swap,當記憶體不夠用時,就不會發生OOM(Out of Memoty) killer 把 mongod 刪除的問題
    2. 因為 MMAPv1 是以 map files to memory 的方式,確保 OS 不會將 MongoDB 資料存在 swap space 裡面。

    • Raid

    1. 大多數的 MongoDB deployments 應該使用 Raid-10
    2. Raid-5 與 Raid-6 的效能表現不是很好
    3. 不要用 Raid-0,Raid-0 的 write operation 表現好,但 read operation 就比較差,在 Amazon EBS volumes 上會發生這個問題。

    • Remote File System

    1. MMAPv1 不建議使用 NFS(Network File System),data 與 journal 都可能會有效能問題,建議將 jornal journal 放在 local or iscsi 硬碟會比較快。

    2. 如果真的要使用 NFS,建議在 /etc/fstab 檔案中加上 bg, nolock, noatime 這些設定。

    • Separate Components onto Different Storage Devices

    1. 為了改善效能,可考慮將 data, journal, log 分開放在不同的 stoage devices上。
    2. WiredTiger 可將 indexes 放在不同的 storage device 上。
  • Architecture

    • Replica Sets

    • Shared Clusters

  • Compression WiredTiger 可使用 snappy 或 zlib 壓縮 data,snappy 壓縮比例較低,但效能耗費較少,zlib 壓縮比例高,但效能耗費也高。

    WiredTiger 預設使用 snappy,可修改設定值 storage.wiredTiger.collectionConfig.blockCompressor

  • Platform Specific Considerations MongoDB 使用 GNU C Library(glibc),建議使用 glibc-2.12-1.2.el6 以上的版本

  • Kernel and File System 建議使用 kernel 2.6.36 以上的 kernel

    MMAPv1 中,MongoDB 會 preallocate db files,產生比較大的 data files,建議使用 XFS 與 EXT4 file system。

    WiredTiger 強烈建議要使用 XFS,避免效能問題。

    XFS file system 至少使用 linux kernal 2.6.25 以上的版本。 EXT4 file system 至少使用 linux kernal 2.6.23 以上的版本。

    以下是一些 Linux Distribution建議的 files sytem 及 kernel 版本

Linux Distribution Filesystem Kernel Version
CentOS 5.5 ext4, xfs 2.6.18-194.el5
CentOS 5.6 ext4, xfs 2.6.18-3.0.el5
CentOS 5.8 ext4, xfs 2.6.18-308.8.2.el5
CentOS 6.1 ext4, xfs 2.6.32-131.0.15.el6.x86_64
RHEL 5.6 ext4 2.6.18-3.0
RHEL 6.0 xfs 2.6.32-71
Ubuntu 10.04.4 LTS ext4, xfs 2.6.32-38-server
Amazon Linux AMI release 2012.03 ext4 3.2.12-3.2.4.amzn1.x86_64
  • fsync() on Directories MongoDB 需要 file system 支援 fsync on directories 的功能,HGFS 與 Virtual Box 並不支援這個功能。

  • Recommended Configuration

    1. 在儲存 data files 的硬碟,要關掉 atime
    2. 設定 file descriptor limit (-n) 以及 user process limit(ulimit) 超過 20000
    3. disable Transparent Huge Pages,MongoDB 在 4096 bytes virtual memory pages 的效能較好

      echo never > /sys/kernel/mm/redhat_transparent_hugepage/defrag
      echo never > /sys/kernel/mm/redhat_transparent_hugepage/enabled
      echo never > /sys/kernel/mm/transparent_hugepage/enabled
      echo never > /sys/kernel/mm/transparent_hugepage/defrag
    4. disable NUMA in BIOS

    5. 設定 SELinux

    在 MMAPv1 1. 檢查針對 block device 的 readahead 設定。一般設定為 32(16kb) 就可以了

    注意:所有 MongoDB 系統都要用 NTP 同步時間。

  • Linux System mongod 及 mongos 在他們自己的 socket 裡面限制 TCP keep alive setting 最大值為 300 seconds。

    查詢設定

    sysctl net.ipv4.tcp_keepalive_time
    cat /proc/sys/net/ipv4/tcp_keepalive_time

    修改設定

    sudo sysctl -w net.ipv4.tcp_keepalive_time=<value>
    echo <value> | sudo tee /proc/sys/net/ipv4/tcp_keepalive_time

    永久修改設定

    vi /etc/sysctl.conf
    net.ipv4.tcp_keepalive_time = <value>

noPadding

mysql遷移到mongodb shared架構的過程中踩到的一個坑 MongoDB 2.x 在儲存資料時,會自動預留一些空間,所以原本在 mysql 259GB 的資料,移到 MongoDB 變成了 1197.416 GB。這個問題在 MongDB 3.x 採用了 WiredTiger 以後,已經有改善了。

就算是使用預設的 MMAPv1 Storage Engine 在 3.0 以後已經可以在建立 collection 時,就加上 noPadding 選項設定,避免 MongoDB 用掉太多預留空間。

2015/12/21

Sharding in MongoDB

Sharding 是資料庫的一種水平擴充的技術,中文稱為「分片」,就是將資料庫分區塊放在不同的資料庫節點上。大量的針對單一個 database daemon 進行 query,會消耗掉很多 CPU 的運算 loading。大量的 data sets 也可能會超過單一台機器所能提供的硬碟容量。

vertical/horizontal scaling

有兩種基本的方式可 scale up Database: vertical scaling and sharding。

  • Vertical scaling 為機器增加 CPU 以及 Storage,但這種方式雖然簡便還是有極限,不可能無止境的將一台機器的 CPU, Storage 一直往上增加。
  • Horizontal scaling: Sharding 將資料分散到多個 server (shards) 上,每一個 shard 都有獨立的資料庫。

    當 sharding cluster 增加 shards,將會減少每一個 shard 處理的 operations 數量,最終可增加整個 cluster 的容量及 throughput。同時也會減少每一個 shard 所要儲存的資料量。

shard, router, config server

MongoDB 的 Sharding 機制中有三個角色

  • Shards 儲存分片後的部份資料,可能是一個獨立的 server 或是 replica set。在正式環境中,最好都是使用 repica sets。
  • Query Routers 每個 router 都是一個 mongos instance,可接受來自 application 的 read/write,並轉向到 shards,application 並不會直接存取 shards。
  • Config Servers 每一個 config server 都是一個 mongod instance,儲存這個 cluster 相關的 metadata,metadata中記錄 chunks 對應到 shards 的資訊。分片後的資料稱為 chunk,每個 chunk 都是 collection 中一段連續的資料記錄,通常最大是 200MB。正式環境必須恰好要有三個 config servers。

Sharded cluster

當系統遇到以下的問題時,就必須要採用 sharded cluster

  1. data set 接近或超過單一 MongoDB instance 所能儲存的容量
  2. 系統 working set (MongoDB 最常使用的資料,通常會儲存在 RAM 或 SSD裡面) 即將超過系統的 RAM 容量上限
  3. 單一 MongoDB instance 無法滿足大量 write operaions 需求時

Shard Key

為了要建置 collection 的 sharding,必須要有一個 shard key,在 collection 中要選擇一個 indexed field 或是 indexed compund filed 作為 shard key。MongoDB 會以 range/hash based partitioning 的方式將shard key values 切割為 chunks。

  • Range Based Sharding 現在如果有一個 numeric shard key,畫出一條由負無限大到正無限大的數線。shard key 上每一個 key 的數值都會落在數線上某一點,MongoDB會將這條線切割成幾個不會互相重疊的線段,也就是 chunks。兩個數值很接近的 shard key,會放在同一個 chunk 中。

  • Hash Based Sharding 計算 shard key 的 hash value,使用這個 hash 建立 chunks,兩個很數值很接近的 shard key,不一定會區分在同一個 chunk 中。

  • Tag Aware Sharding MongoDB 另外允許管理者使用 tag aware sharding 的機制,管理者可根據 shard key 的區間建立 tags,然後指定哪些 tags 放在那個 shards。這種機制非常適合存放有地區特性的資料,也就是可以選擇最接近的 data center。

如果是對 shard key 的 range queries,range based partitioning 效能會比較好,因為查詢的資料大部分都在同一個 chunk 裡面。

但是 range based partitioning 可能會造成資料不平均的狀況。舉例來說,如果是跟著時間線性增加的欄位,針對 time range 進行的查詢,就會落在同一個 chunk 中。但也因此會造成某一個 chunk 會收到較多 request,所以整個系統的 scalibility 表現會差一些。

hashed based partitioning 會確保資料會平均散布在 chunks 之間,因此 range query 就會動用到所有的 chunks。

Balanced Data Distribution

持續增加的資料會造成資料不平均的散布狀況,也就是某一個 shard 存放了比較多的 chunks。MongoDB 使用兩個背景的 process: splitting, balancer 來確保整個 cluster 能保持 balanced。

  • Splitting splitting 可避免 chunks 增加地太大,當某一個 chunk 超過 MongoDB 指定的 chunk size 時,就會將 chunk 分成兩塊。

    在處理 splits 時,MongoDB 將不會移動任何資料,也不會修改 shards。

  • Balancer balancer 是處理 chunk migration 的背景 process,balancer 可由 cluster 中任何一個 query router 執行。

    當 cluter 裡面的 sharded collection 資料散布不平均時,balancer 會在 shard 之間移動 chunks,通常就是從有最多個 chunks 的 shard 移動到最少的 shard。

    在 chunks migration 期間,來源 shard 的文件會傳送到目標的 shard,接下來會處理移動期間對資料的所有異動 operations,最後再更新 config server 上關於這個 chunk 位置的 metadata。

    如果在 migration 期間發生任何錯誤,balancer 會放棄已經更新的資料,並保留原始 chunks 的資料,MongoDB 只會在 migration process 處理完成時,才會將來源 chunk 裡面的資料刪除。

由於新增或移除 shards 都會造成 imbalance,因此在新增 shard 之後,MongoDB 就會馬上進行 data migration,會需要花一些時間,才能達到 cluster balanced。移除 shard 時,balancer 會先將這個 shard 的所有 chunks 移動到其他 shard,在處理完成之後,才能正確地移除 shard。

測試 Sharding

首先啟動兩個 shard server,分別使用 Port 20000 及 20001

mkdir -p /home/mongodb/shard/data/s1
mkdir -p /home/mongodb/shard/data/s2
mkdir -p /home/mongodb/shard/logs

/usr/share/mongodb/bin/mongod --port 20000 --fork --dbpath /home/mongodb/shard/data/s1 --logpath /home/mongodb/shard/logs/s1.log --logappend --shardsvr --directoryperdb

/usr/share/mongodb/bin/mongod --port 20001 --fork --dbpath /home/mongodb/shard/data/s2 --logpath /home/mongodb/shard/logs/s2.log --logappend --shardsvr --directoryperdb

然後在 Port 30000 啟動 Config Server

mkdir -p /home/mongodb/shard/data/config
/usr/share/mongodb/bin/mongod --configsvr --fork --port 30000 --dbpath /home/mongodb/shard/data/config --logpath /home/mongodb/shard/logs/config.log --logappend

在 Port 40000 啟動 Router

/usr/share/mongodb/bin/mongos --port 40000 --configdb localhost:30000 --fork  --logpath /home/mongodb/shard/logs/route.log --logappend --chunkSize 1

chunkSize 是 sharded cluster 每一個 chunk 的大小,單位是 MegaBytes,預設值為 64,這個參數只會在一開始初始化 cluster 時,設定後才有作用,如果要修改一個既有的 Sharded Cluster 的 Chunk Size 要參考 Modify Chunk Size in a Sharded Cluster

以 mongo client 連線到 Router

mongo admin --port 40000

增加兩個 shard server

db.runCommand({addshard:"localhost:20000"});
db.runCommand({addshard:"localhost:20001"});

讓資料庫 test 以 sharding 方式儲存

db.runCommand({enablesharding:"test"});

設定 test.users 以 _id 作為 sharded key,進行 sharding

db.runCommand({shardcollection:"test.users", key:{_id:1}});

直接寫入 500000 筆測試資料

use test

for(var i=1;i<500000; i++) {
    db.users.insert({
        userid:"user_"+i,
        username:"name_"+i,
        age: NumberInt(_rand()*100)
    })
}

以 db.usrs.stats() 查看 sharding 的結果

mongos> db.users.stats();
{
    "sharded" : true,   // 有分片
    "paddingFactorNote" : "paddingFactor is unused and unmaintained in 3.0. It remains hard coded to 1.0 for compatibility only.",
    "userFlags" : 1,
    "capped" : false,
    "ns" : "test.users",
    "count" : 499999,   // 499999 筆資料
    "numExtents" : 16,
    "size" : 55999888,
    "storageSize" : 75595776,
    "totalIndexSize" : 16278416,
    "indexSizes" : {
        "_id_" : 16278416
    },
    "avgObjSize" : 112,
    "nindexes" : 1,
    "nchunks" : 95,     // 共有 95個 chunks
    "shards" : {
        "shard0000" : {
            "ns" : "test.users",
            "count" : 246899,   // 第一個 shard 儲存了 246899 筆資料
            "size" : 27652688,
            "avgObjSize" : 112,
            "numExtents" : 8,
            "storageSize" : 37797888,
            "lastExtentSize" : 15290368,
            "paddingFactor" : 1,
            "paddingFactorNote" : "paddingFactor is unused and unmaintained in 3.0. It remains hard coded to 1.0 for compatibility only.",
            "userFlags" : 1,
            "capped" : false,
            "nindexes" : 1,
            "totalIndexSize" : 8028832,
            "indexSizes" : {
                "_id_" : 8028832
            },
            "ok" : 1
        },
        "shard0001" : {
            "ns" : "test.users",
            "count" : 253100,   // 第二個 shard 儲存了 253100 筆資料
            "size" : 28347200,
            "avgObjSize" : 112,
            "numExtents" : 8,
            "storageSize" : 37797888,
            "lastExtentSize" : 15290368,
            "paddingFactor" : 1,
            "paddingFactorNote" : "paddingFactor is unused and unmaintained in 3.0. It remains hard coded to 1.0 for compatibility only.",
            "userFlags" : 1,
            "capped" : false,
            "nindexes" : 1,
            "totalIndexSize" : 8249584,
            "indexSizes" : {
                "_id_" : 8249584
            },
            "ok" : 1
        }
    },
    "ok" : 1
}

查詢一下,就會發現 users 不會按照 insert 的順序回傳回來

mongos> db.users.find();
{ "_id" : ObjectId("56457eb55b043e713fa43441"), "userid" : "user_10", "username" : "name_10", "age" : 33 }
{ "_id" : ObjectId("56457eb55b043e713fa43438"), "userid" : "user_1", "username" : "name_1", "age" : 0 }
{ "_id" : ObjectId("56457eb65b043e713fa43442"), "userid" : "user_11", "username" : "name_11", "age" : 25 }
{ "_id" : ObjectId("56457eb55b043e713fa43439"), "userid" : "user_2", "username" : "name_2", "age" : 79 }
{ "_id" : ObjectId("56457eb65b043e713fa43443"), "userid" : "user_12", "username" : "name_12", "age" : 55 }
{ "_id" : ObjectId("56457eb55b043e713fa4343a"), "userid" : "user_3", "username" : "name_3", "age" : 83 }
{ "_id" : ObjectId("56457eb65b043e713fa43444"), "userid" : "user_13", "username" : "name_13", "age" : 24 }
{ "_id" : ObjectId("56457eb55b043e713fa4343b"), "userid" : "user_4", "username" : "name_4", "age" : 10 }
{ "_id" : ObjectId("56457eb65b043e713fa43445"), "userid" : "user_14", "username" : "name_14", "age" : 33 }
{ "_id" : ObjectId("56457eb55b043e713fa4343c"), "userid" : "user_5", "username" : "name_5", "age" : 76 }
{ "_id" : ObjectId("56457eb65b043e713fa43446"), "userid" : "user_15", "username" : "name_15", "age" : 54 }
{ "_id" : ObjectId("56457eb55b043e713fa4343d"), "userid" : "user_6", "username" : "name_6", "age" : 63 }
{ "_id" : ObjectId("56457eb65b043e713fa43447"), "userid" : "user_16", "username" : "name_16", "age" : 27 }
{ "_id" : ObjectId("56457eb55b043e713fa4343e"), "userid" : "user_7", "username" : "name_7", "age" : 42 }
{ "_id" : ObjectId("56457eb65b043e713fa43448"), "userid" : "user_17", "username" : "name_17", "age" : 78 }
{ "_id" : ObjectId("56457eb55b043e713fa4343f"), "userid" : "user_8", "username" : "name_8", "age" : 99 }
{ "_id" : ObjectId("56457eb65b043e713fa43449"), "userid" : "user_18", "username" : "name_18", "age" : 34 }
{ "_id" : ObjectId("56457eb55b043e713fa43440"), "userid" : "user_9", "username" : "name_9", "age" : 67 }
{ "_id" : ObjectId("56457eb65b043e713fa4344a"), "userid" : "user_19", "username" : "name_19", "age" : 25 }
{ "_id" : ObjectId("56457eba5b043e713fa45ff1"), "userid" : "user_11194", "username" : "name_11194", "age" : 64 }
Type "it" for more

mongos> db.users.find().sort({userid:1}).limit(5);
{ "_id" : ObjectId("56457eb55b043e713fa43438"), "userid" : "user_1", "username" : "name_1", "age" : 0 }
{ "_id" : ObjectId("56457eb55b043e713fa43441"), "userid" : "user_10", "username" : "name_10", "age" : 33 }
{ "_id" : ObjectId("56457eb65b043e713fa4349b"), "userid" : "user_100", "username" : "name_100", "age" : 50 }
{ "_id" : ObjectId("56457eb75b043e713fa4381f"), "userid" : "user_1000", "username" : "name_1000", "age" : 67 }
{ "_id" : ObjectId("56457eba5b043e713fa45b47"), "userid" : "user_10000", "username" : "name_10000", "age" : 69 }

Sharding 的維護

列出所有 shard servers

mongos> use admin;
switched to db admin
mongos> db.runCommand({listshards:1});
{
    "shards" : [
        {
            "_id" : "shard0000",
            "host" : "localhost:20000"
        },
        {
            "_id" : "shard0001",
            "host" : "localhost:20001"
        }
    ],
    "ok" : 1
}

列印 sharding 的狀態

mongos> printShardingStatus();
--- Sharding Status --- 
  sharding version: {
    "_id" : 1,
    "minCompatibleVersion" : 5,
    "currentVersion" : 6,
    "clusterId" : ObjectId("56457d8ca361603fe6aa49ce")
}
  shards:
    {  "_id" : "shard0000",  "host" : "localhost:20000" }
    {  "_id" : "shard0001",  "host" : "localhost:20001" }
  balancer:
    Currently enabled:  yes
    Currently running:  no
    Failed balancer rounds in last 5 attempts:  0
    Migration Results for the last 24 hours: 
        47 : Success
  databases:
    {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
    {  "_id" : "test",  "partitioned" : true,  "primary" : "shard0000" }
        test.users
            shard key: { "_id" : 1 }
            chunks:
                shard0000   48
                shard0001   47
            too many chunks to print, use verbose if you want to force print

判斷是否是 sharding cluster mongos> db.runCommand({isdbgrid:1}) { "isdbgrid" : 1, "hostname" : "server", "ok" : 1 }

新增或移除 shard server

首先在另一個 console 產生第三個 shard server mkdir -p /home/mongodb/shard/data/s3 /usr/share/mongodb/bin/mongod --port 20002 --fork --dbpath /home/mongodb/shard/data/s3 --logpath /home/mongodb/shard/logs/s3.log --logappend --shardsvr --directoryperdb

回到 mongo route server 的 client

mongo admin --port 40000

將第三個 shard server 增加到 cluster

db.runCommand({addshard:"localhost:20002"})

printShardingStatus() 查看 sharding 的狀態,可以發現現在 MongoDB 已經開始進行 balancer 的處理

mongos> printShardingStatus();
--- Sharding Status --- 
  sharding version: {
    "_id" : 1,
    "minCompatibleVersion" : 5,
    "currentVersion" : 6,
    "clusterId" : ObjectId("56457d8ca361603fe6aa49ce")
}
  shards:
    {  "_id" : "shard0000",  "host" : "localhost:20000" }
    {  "_id" : "shard0001",  "host" : "localhost:20001" }
    {  "_id" : "shard0002",  "host" : "localhost:20002" }
  balancer:
    Currently enabled:  yes
    Currently running:  yes
        Balancer lock taken at Fri Nov 13 2015 14:47:40 GMT+0800 (CST) by kokola:40000:1447394699:1804289383:Balancer:1681692777
    Collections with active migrations: 
        test.users started at Fri Nov 13 2015 14:47:40 GMT+0800 (CST)
    Failed balancer rounds in last 5 attempts:  0
    Migration Results for the last 24 hours: 
        55 : Success
  databases:
    {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
    {  "_id" : "test",  "partitioned" : true,  "primary" : "shard0000" }
        test.users
            shard key: { "_id" : 1 }
            chunks:
                shard0000   43
                shard0001   44
                shard0002   8
            too many chunks to print, use verbose if you want to force print

最後完成時,三個 shard server 分別儲存了 32, 32, 31 個 chunks

mongos> printShardingStatus();
--- Sharding Status --- 
  sharding version: {
    "_id" : 1,
    "minCompatibleVersion" : 5,
    "currentVersion" : 6,
    "clusterId" : ObjectId("56457d8ca361603fe6aa49ce")
}
  shards:
    {  "_id" : "shard0000",  "host" : "localhost:20000" }
    {  "_id" : "shard0001",  "host" : "localhost:20001" }
    {  "_id" : "shard0002",  "host" : "localhost:20002" }
  balancer:
    Currently enabled:  yes
    Currently running:  no
    Failed balancer rounds in last 5 attempts:  0
    Migration Results for the last 24 hours: 
        78 : Success
  databases:
    {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
    {  "_id" : "test",  "partitioned" : true,  "primary" : "shard0000" }
        test.users
            shard key: { "_id" : 1 }
            chunks:
                shard0000   32
                shard0001   32
                shard0002   31
            too many chunks to print, use verbose if you want to force print

如果用 db.users.stats() 也會看到現在的資料已經分散到三個 shard servers

use test;
db.user.stats();

要移除第三個 shard server,必須持續執行這個指令

db.runCommand({removeshard:"localhost:20002"})

我們可以發現 chunks 慢慢變少,直到最後產生 error

mongos> db.runCommand({removeshard:"localhost:20002"})
{
    "msg" : "draining started successfully",
    "state" : "started",
    "shard" : "shard0002",
    "ok" : 1
}
mongos> db.runCommand({removeshard:"localhost:20002"})
{
    "msg" : "draining ongoing",
    "state" : "ongoing",
    "remaining" : {
        "chunks" : NumberLong(29),
        "dbs" : NumberLong(0)
    },
    "ok" : 1
}
mongos> db.runCommand({removeshard:"localhost:20002"})
{
    "msg" : "draining ongoing",
    "state" : "ongoing",
    "remaining" : {
        "chunks" : NumberLong(2),
        "dbs" : NumberLong(0)
    },
    "ok" : 1
}
mongos> db.runCommand({removeshard:"localhost:20002"})
{
    "msg" : "removeshard completed successfully",
    "state" : "completed",
    "shard" : "shard0002",
    "ok" : 1
}
mongos> db.runCommand({removeshard:"localhost:20002"})
{
    "code" : 13129,
    "ok" : 0,
    "errmsg" : "exception: can't find shard for: localhost:20002"
}

printShardingStatus() 的結果,會看到又變回兩個 shard servers 的狀態。

mongos> printShardingStatus();
--- Sharding Status --- 
  sharding version: {
    "_id" : 1,
    "minCompatibleVersion" : 5,
    "currentVersion" : 6,
    "clusterId" : ObjectId("56457d8ca361603fe6aa49ce")
}
  shards:
    {  "_id" : "shard0000",  "host" : "localhost:20000" }
    {  "_id" : "shard0001",  "host" : "localhost:20001" }
  balancer:
    Currently enabled:  yes
    Currently running:  no
    Failed balancer rounds in last 5 attempts:  0
    Migration Results for the last 24 hours: 
        109 : Success
  databases:
    {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
    {  "_id" : "test",  "partitioned" : true,  "primary" : "shard0000" }
        test.users
            shard key: { "_id" : 1 }
            chunks:
                shard0000   48
                shard0001   47
            too many chunks to print, use verbose if you want to force print

2015/12/14

Replica Sets in MongoDB

Replication 的目的是提供資料 redundancy 並確保 availability,在不同的 DB server 保留數個資料副本,可避免因為單一機器失效而遺失資料。

有時我們可以利用 replication 增加資料讀取的效率,clients 可以讓不同的 server,分開處以讀取及寫入的 operations,我們也可以利用不同的 data centers,讓全球的使用者可連接最接近的 data center,以提高 AP 的效能。

mongd instances in Replication Sets

replication set 裡面 mongd daemon 主要有兩個角色: Primary 以及 Secondary,clients 主要跟 Primary node 溝通,所有的 Secondary nodes 會跟著 Primary 執行一樣的 operations,因此可以得到完全一樣的 data set。

每一個 replica set 只能有一個唯一的 Primary node,為了支援 replication,Promary node 必須在 oplog 裡面保留所有資料異動的記錄。

Primary 是唯一可以同時接受 Read/Write 的節點,雖然 Primary 與 Secondary 都可以接受 Read,但預設只有 Primary 可以接受 Read。

Secondary 會複製 Primary 的 oplog,並在自己的 data set 裡面再執行一次。

如果 Primary 超過 10s 無法跟其他節點溝通時,replica set 將會選擇一個 Secondary 作為下一任的 Primary。第一個收到多數投票的,就成為下一個 Primary。

Secondary 可設定作為其他的特殊用途,例如可以設定是否可以參與仲裁投票,或是將 priority 設定為 0,也就是單純只備份資料,不會變成 Primary node。

replica set 裡面有第三個角色:Arbiter,Arbiter 並不會儲存資料的備份,主要的功能是當有偶數個 Secondary 需要在其中選擇一個變成 Primary 的時候,就需要 Arbiter 出面進行投票仲裁。

一個 replica set 裡面最多可以有 12 個成員,但有投票權的最多只有有 7 個。一個 replica set 的最低成員要求為:一個 Primary、一個 Secondary、一個 Arbiter。但通常都是 deploy 一個 Primary加上兩個 Secondary。

我們可以增加獨立的 mongod instance 擔任 replica set 的 Arbiter,Arbiter 不需要儲存資料,只需要維護一個 replica set 的 quorum,並遴選出下一個 Primary node。Arbiter 並不需要獨立的硬體機器才能運作。

Secondary node 複製資料的方式,是採用 asynchronous replication,換句話說,如果固定由 Secondary 讀取資料,有可能會發生讀取到舊資料的狀況,

當我們需要跨越 data center 部屬 replica sets 的時候,以提高 data redundancy 時,參考Geographically Distributed Replica Sets 的作法:

  1. 在 main data center 建立一個 Primary,設定 priority 為 1
  2. 在 main data center 建立一個 Secondary,設定 priority 為 1
  3. 在 second data center 建立一個 Secondary,設定 priority 為 0

因為 second data center 的 Secondary node 的優先權為 0,所以不會變成下一任的 Primary node。當 main data center 失效時,

單機測試 replica sets

雖然實際上運作的環境必須要把 Primary, Secondary and Arbiter 放在不同的機器甚至是不同的 data centers。第一次測試 replica sets ,就先以同一台機器進行測試。

  • 建立資料及 log 儲存路徑

      mkdir -p /usr/share/mongodb/repl/data/r0
      mkdir -p /usr/share/mongodb/repl/data/r1
      mkdir -p /usr/share/mongodb/repl/data/r2
      mkdir -p /usr/share/mongodb/repl/logs
    
  • 建立 key files
    在 replica set 中用來互相驗證,這個部份也可以不設定,只要在啟動 mongod 時,不要加上 --keyFile 參數就好了,注意一定要將 key file 的檔案權限改為 600

      mkdir -p /usr/share/mongodb/repl/key
      echo "rs1 keyfile" > /usr/share/mongodb/repl/key/r0
      echo "rs1 keyfile" > /usr/share/mongodb/repl/key/r1
      echo "rs1 keyfile" > /usr/share/mongodb/repl/key/r2
      chmod 600 /usr/share/mongodb/repl/key/r*
    
  • 啟動三個 mongod daemon

在 cli 啟動三個 mongd

/usr/share/mongodb/bin/mongod --replSet rs1 --keyFile /usr/share/mongodb/repl/key/r0 --fork --port 27017 --dbpath /usr/share/mongodb/repl/data/r0 --logpath /usr/share/mongodb/repl/logs/r0.log --logappend

/usr/share/mongodb/bin/mongod --replSet rs1 --keyFile /usr/share/mongodb/repl/key/r1 --fork --port 28017 --dbpath /usr/share/mongodb/repl/data/r1 --logpath /usr/share/mongodb/repl/logs/r1.log --logappend

/usr/share/mongodb/bin/mongod --replSet rs1 --keyFile /usr/share/mongodb/repl/key/r2 --fork --port 29017 --dbpath /usr/share/mongodb/repl/data/r2 --logpath /usr/share/mongodb/repl/logs/r2.log --logappend

可以用 ps aux 查看 mongod daemons

# ps aux|grep mongod
root     13478  1.2  1.3 531552 50020 ?        Sl   14:24   0:02 /usr/share/mongodb/bin/mongod --replSet rs1 --keyFile /usr/share/mongodb/repl/key/r0 --fork --port 27017 --dbpath /usr/share/mongodb/repl/data/r0 --logpath /usr/share/mongodb/repl/logs/r0.log --logappend
root     13623  1.7  1.3 531556 52800 ?        Sl   14:25   0:02 /usr/share/mongodb/bin/mongod --replSet rs1 --keyFile /usr/share/mongodb/repl/key/r1 --fork --port 28017 --dbpath /usr/share/mongodb/repl/data/r1 --logpath /usr/share/mongodb/repl/logs/r1.log --logappend
root     13665  3.2  1.3 531552 53232 ?        Sl   14:27   0:02 /usr/share/mongodb/bin/mongod --replSet rs1 --keyFile /usr/share/mongodb/repl/key/r2 --fork --port 29017 --dbpath /usr/share/mongodb/repl/data/r2 --logpath /usr/share/mongodb/repl/logs/r2.log --logappend
  • 連接到 r0,並初始化 replica sets

以 mongo 連接到 r0 這個 node,然後透過 rs.initiate 進行 replica set 初始化,最後用 rs.stats() 查看狀態。

mongo -port 27017

config_rs1={
    _id: 'rs1',
    members: [
        {_id:0, host:'localhost:27017', priority:1},
        {_id:1, host:'localhost:28017'},
        {_id:2, host:'localhost:29017'}
    ]
};

rs.initiate(config_rs1);

rs.status();

rs.isMaster();

以下是測試的過程

# mongo -port 27017
MongoDB shell version: 3.0.7
connecting to: 127.0.0.1:27017/test
> config_rs1={
... _id: 'rs1',
... members: [
... {_id:0, host:'localhost:27017', priority:1},
... {_id:1, host:'localhost:28017'},
... {_id:2, host:'localhost:29017'}
... ]
... };
{
    "_id" : "rs1",
    "members" : [
        {
            "_id" : 0,
            "host" : "localhost:27017",
            "priority" : 1
        },
        {
            "_id" : 1,
            "host" : "localhost:28017"
        },
        {
            "_id" : 2,
            "host" : "localhost:29017"
        }
    ]
}
> rs.status();
{
    "info" : "run rs.initiate(...) if not yet done for the set",
    "ok" : 0,
    "errmsg" : "no replset config has been received",
    "code" : 94
}
> rs.initiate(config_rs1);
{ "ok" : 1 }
rs1:SECONDARY> rs.status();
{
    "set" : "rs1",
    "date" : ISODate("2015-11-12T06:29:01.774Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 0,
            "name" : "localhost:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 280,
            "optime" : Timestamp(1447309732, 1),
            "optimeDate" : ISODate("2015-11-12T06:28:52Z"),
            "electionTime" : Timestamp(1447309734, 1),
            "electionDate" : ISODate("2015-11-12T06:28:54Z"),
            "configVersion" : 1,
            "self" : true
        },
        {
            "_id" : 1,
            "name" : "localhost:28017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 9,
            "optime" : Timestamp(1447309732, 1),
            "optimeDate" : ISODate("2015-11-12T06:28:52Z"),
            "lastHeartbeat" : ISODate("2015-11-12T06:29:00.389Z"),
            "lastHeartbeatRecv" : ISODate("2015-11-12T06:29:00.533Z"),
            "pingMs" : 0,
            "configVersion" : 1
        },
        {
            "_id" : 2,
            "name" : "localhost:29017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 9,
            "optime" : Timestamp(1447309732, 1),
            "optimeDate" : ISODate("2015-11-12T06:28:52Z"),
            "lastHeartbeat" : ISODate("2015-11-12T06:29:00.425Z"),
            "lastHeartbeatRecv" : ISODate("2015-11-12T06:29:00.659Z"),
            "pingMs" : 0,
            "configVersion" : 1
        }
    ],
    "ok" : 1
}
rs1:PRIMARY> rs.isMaster();
{
    "setName" : "rs1",
    "setVersion" : 1,
    "ismaster" : true,
    "secondary" : false,
    "hosts" : [
        "localhost:27017",
        "localhost:28017",
        "localhost:29017"
    ],
    "primary" : "localhost:27017",
    "me" : "localhost:27017",
    "electionId" : ObjectId("564431a624f5e9c6e3c56242"),
    "maxBsonObjectSize" : 16777216,
    "maxMessageSizeBytes" : 48000000,
    "maxWriteBatchSize" : 1000,
    "localTime" : ISODate("2015-11-12T06:29:09.040Z"),
    "maxWireVersion" : 3,
    "minWireVersion" : 0,
    "ok" : 1
}

oplog

MongoDB 是透過 oplog 記錄資料異動過程的,replica sets 實際上是複製 oplog,oplog.rs 是一個固定長度的 Capped Collection,存放在 local 資料庫中,可透過 --oplogSize 調整這個資料庫的大小。

查閱 oplog 的資料,必須先切換到 admin 的身份。

首先建立一個 admin 的帳號。

mongo --port 27017

db.createUser({
    user: "admin",
    pwd: "pass",
    roles: [ { role: "root", db: "admin" } ]
});
exit;

然後再重新以 admin 登入到 mongod

mongo --port 27017 -u admin -p pass --authenticationDatabase admin

接下來就可以用以下的指令查閱 oplog

use local;

show collections;

db.oplog.rs.find();

db.printReplicationInfo();

db.printSlaveReplicationInfo();

db.system.replset.find();

測試過程如下

rs1:PRIMARY> use local;
switched to db local
rs1:PRIMARY> show collections;
me
oplog.rs
startup_log
system.indexes
system.replset

查閱 oplog.rs 裡面的資料

rs1:PRIMARY> db.oplog.rs.find();
{ "ts" : Timestamp(1447309732, 1), "h" : NumberLong(0), "v" : 2, "op" : "n", "ns" : "", "o" : { "msg" : "initiating set" } }
{ "ts" : Timestamp(1447310547, 1), "h" : NumberLong("-5005889511402504175"), "v" : 2, "op" : "c", "ns" : "admin.$cmd", "o" : { "create" : "system.version" } }
{ "ts" : Timestamp(1447310547, 2), "h" : NumberLong("7158333189499425797"), "v" : 2, "op" : "i", "ns" : "admin.system.version", "o" : { "_id" : "authSchema", "currentVersion" : 5 } }
{ "ts" : Timestamp(1447310547, 3), "h" : NumberLong("-1384756181233850446"), "v" : 2, "op" : "c", "ns" : "admin.$cmd", "o" : { "create" : "system.users" } }
{ "ts" : Timestamp(1447310547, 4), "h" : NumberLong("-8251644809692327494"), "v" : 2, "op" : "i", "ns" : "admin.system.users", "o" : { "_id" : "admin.admin", "user" : "admin", "db" : "admin", "credentials" : { "SCRAM-SHA-1" : { "iterationCount" : 10000, "salt" : "YfZzd27A323g5bWhk+TAcA==", "storedKey" : "27CwlfcUPxr92bB6yr+AoExiBmE=", "serverKey" : "SxiNruT9vznek+uSkM8xIKlK3a8=" } }, "roles" : [ { "role" : "root", "db" : "admin" } ] } }

查看 replication 的資訊,主要就是看 oplog 裡面的資訊

rs1:PRIMARY> db.printReplicationInfo();
configured oplog size:   990MB
log length start to end: 815secs (0.23hrs)
oplog first event time:  Thu Nov 12 2015 14:28:52 GMT+0800 (CST)
oplog last event time:   Thu Nov 12 2015 14:42:27 GMT+0800 (CST)
now:                     Thu Nov 12 2015 14:50:58 GMT+0800 (CST)

查看 slave 同步的狀態

rs1:PRIMARY> db.printSlaveReplicationInfo();
source: localhost:28017
    syncedTo: Thu Nov 12 2015 14:42:27 GMT+0800 (CST)
    0 secs (0 hrs) behind the primary 
source: localhost:29017
    syncedTo: Thu Nov 12 2015 14:42:27 GMT+0800 (CST)
    0 secs (0 hrs) behind the primary

local 資料庫中,還有另一個 system.replset 裡面有記錄 replica set 的資訊

rs1:PRIMARY> db.system.replset.find();
{ "_id" : "rs1", "version" : 1, "members" : [ { "_id" : 0, "host" : "localhost:27017", "arbiterOnly" : false, "buildIndexes" : true, "hidden" : false, "priority" : 1, "tags" : {  }, "slaveDelay" : 0, "votes" : 1 }, { "_id" : 1, "host" : "localhost:28017", "arbiterOnly" : false, "buildIndexes" : true, "hidden" : false, "priority" : 1, "tags" : {  }, "slaveDelay" : 0, "votes" : 1 }, { "_id" : 2, "host" : "localhost:29017", "arbiterOnly" : false, "buildIndexes" : true, "hidden" : false, "priority" : 1, "tags" : {  }, "slaveDelay" : 0, "votes" : 1 } ], "settings" : { "chainingAllowed" : true, "heartbeatTimeoutSecs" : 10, "getLastErrorModes" : {  }, "getLastErrorDefaults" : { "w" : 1, "wtimeout" : 0 } } }

讓 Secondary 也能讀取資料

Secondary 預設是不允許進行資料查詢的,因為非同步複製的關係,資料有可能在查詢時,還沒有複製過來。但如果能容忍這種非同步的錯誤,讓 Secondary 也能進行資料查詢,對系統來說,可達到讀寫分離,可以減輕 Primary 的 loading。

因為剛剛為了處理 oplog 加上了 user 權限,我們必須先建立一個帳號,可以讀寫 test 資料庫

mongo --port 27017 -u admin -p pass --authenticationDatabase admin

use test
db.createUser(
    {
      user: "test",
      pwd: "pass",
      roles: [
         { role: "readWrite", db: "test" }
      ]
    }
);

db.getUsers();

連接到 r0 (port 27017),然後 insert 一筆資料到 temp

mongo --port 27017 -u test -p pass --authenticationDatabase test

db.temp.insert({age:22});
db.temp.find();

連接到 Secondary,執行 show collections 指令時,會發生錯誤。

# mongo --port 28017 -u test -p pass --authenticationDatabase test
MongoDB shell version: 3.0.7
connecting to: 127.0.0.1:28017/test
rs1:SECONDARY> show collections
2015-11-12T15:31:34.394+0800 E QUERY    Error: listCollections failed: { "note" : "from execCommand", "ok" : 0, "errmsg" : "not master" }
    at Error (<anonymous>)
    at DB._getCollectionInfosCommand (src/mongo/shell/db.js:646:15)
    at DB.getCollectionInfos (src/mongo/shell/db.js:658:20)
    at DB.getCollectionNames (src/mongo/shell/db.js:669:17)
    at shellHelper.show (src/mongo/shell/utils.js:625:12)
    at shellHelper (src/mongo/shell/utils.js:524:36)
    at (shellhelp2):1:1 at src/mongo/shell/db.js:646

必須要先 db.getMongo().setSlaveOk(); 就可以從 Slave 讀取資料。

rs1:SECONDARY> db.getMongo().setSlaveOk();
rs1:SECONDARY> show collections;
system.indexes
temp
rs1:SECONDARY> db.temp.find();
{ "_id" : ObjectId("56443fedeabb66f27f42b08a"), "age" : 22 }

Primary node 轉移

replica sets 中發生故障節點的時候,會有自動轉移的程序。如果把 replica sets 中的 Primary 節點停止,剩下來的節點會自動選出下一個 Primary node。

首先查看目前的三個 mongod

# ps aux|grep mongod
root     13478  0.4  2.6 3366244 99916 ?       Sl   14:24   0:20 /usr/share/mongodb/bin/mongod --replSet rs1 --keyFile /usr/share/mongodb/repl/key/r0 --fork --port 27017 --dbpath /usr/share/mongodb/repl/data/r0 --logpath /usr/share/mongodb/repl/logs/r0.log --logappend
root     13623  0.4  2.4 3353924 95288 ?       Sl   14:25   0:19 /usr/share/mongodb/bin/mongod --replSet rs1 --keyFile /usr/share/mongodb/repl/key/r1 --fork --port 28017 --dbpath /usr/share/mongodb/repl/data/r1 --logpath /usr/share/mongodb/repl/logs/r1.log --logappend
root     13665  0.4  2.4 3352892 93024 ?       Sl   14:27   0:19 /usr/share/mongodb/bin/mongod --replSet rs1 --keyFile /usr/share/mongodb/repl/key/r2 --fork --port 29017 --dbpath /usr/share/mongodb/repl/data/r2 --logpath /usr/share/mongodb/repl/logs/r2.log --logappend

直接將 port 27017 的 process 砍掉

kill -9 13478

當我們連結到 r1 (port 28017),以 rs.status() 查看 replica sets 狀態時,會發現 r1 (port 28017) 已經自動成為 Primary node。

mongo --port 28017 -u admin -p pass --authenticationDatabase admin
rs1:SECONDARY> rs.status();
{
    "set" : "rs1",
    "date" : ISODate("2015-11-12T07:44:33.873Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 0,
            "name" : "localhost:27017",
            "health" : 0,
            "state" : 8,
            "stateStr" : "(not reachable/healthy)",
            "uptime" : 0,
            "optime" : Timestamp(0, 0),
            "optimeDate" : ISODate("1970-01-01T00:00:00Z"),
            "lastHeartbeat" : ISODate("2015-11-12T07:44:33.798Z"),
            "lastHeartbeatRecv" : ISODate("2015-11-12T07:44:29.359Z"),
            "pingMs" : 0,
            "lastHeartbeatMessage" : "Failed attempt to connect to localhost:27017; couldn't connect to server localhost:27017 (127.0.0.1), connection attempt failed",
            "configVersion" : -1
        },
        {
            "_id" : 1,
            "name" : "localhost:28017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 4734,
            "optime" : Timestamp(1447313389, 1),
            "optimeDate" : ISODate("2015-11-12T07:29:49Z"),
            "electionTime" : Timestamp(1447314272, 1),
            "electionDate" : ISODate("2015-11-12T07:44:32Z"),
            "configVersion" : 1,
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "localhost:29017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 4541,
            "optime" : Timestamp(1447313389, 1),
            "optimeDate" : ISODate("2015-11-12T07:29:49Z"),
            "lastHeartbeat" : ISODate("2015-11-12T07:44:33.521Z"),
            "lastHeartbeatRecv" : ISODate("2015-11-12T07:44:33.713Z"),
            "pingMs" : 0,
            "configVersion" : 1
        }
    ],
    "ok" : 1
}

如果再把 r0 (port 27017) 執行起來

/usr/share/mongodb/bin/mongod --replSet rs1 --keyFile /usr/share/mongodb/repl/key/r0 --fork --port 27017 --dbpath /usr/share/mongodb/repl/data/r0 --logpath /usr/share/mongodb/repl/logs/r0.log --logappend

r1 (port 28017) 成為 Primary node 的身份還是不變,差別只是 r0 (port 27017) 的 health 由 0 變回 1,身份維持是 SECONDARY。

rs1:PRIMARY> rs.status();
{
    "set" : "rs1",
    "date" : ISODate("2015-11-12T07:49:30.493Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 0,
            "name" : "localhost:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 46,
            "optime" : Timestamp(1447313389, 1),
            "optimeDate" : ISODate("2015-11-12T07:29:49Z"),
            "lastHeartbeat" : ISODate("2015-11-12T07:49:29.975Z"),
            "lastHeartbeatRecv" : ISODate("2015-11-12T07:49:30.440Z"),
            "pingMs" : 0,
            "configVersion" : 1
        },
        {
            "_id" : 1,
            "name" : "localhost:28017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 5031,
            "optime" : Timestamp(1447313389, 1),
            "optimeDate" : ISODate("2015-11-12T07:29:49Z"),
            "electionTime" : Timestamp(1447314272, 1),
            "electionDate" : ISODate("2015-11-12T07:44:32Z"),
            "configVersion" : 1,
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "localhost:29017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 4837,
            "optime" : Timestamp(1447313389, 1),
            "optimeDate" : ISODate("2015-11-12T07:29:49Z"),
            "lastHeartbeat" : ISODate("2015-11-12T07:49:29.722Z"),
            "lastHeartbeatRecv" : ISODate("2015-11-12T07:49:29.909Z"),
            "pingMs" : 0,
            "configVersion" : 1
        }
    ],
    "ok" : 1
}

replica sets 的節點維護

如果三台機器已經沒有辦法負荷壓力,可以透過增刪節點的方式,維護 replica sets。

首先產生兩個新的 mongod 節點 r3, r4。

mkdir -p /usr/share/mongodb/repl/data/r3
mkdir -p /usr/share/mongodb/repl/data/r4
echo "rs1 keyfile" > /usr/share/mongodb/repl/key/r3
echo "rs1 keyfile" > /usr/share/mongodb/repl/key/r4
chmod 600 /usr/share/mongodb/repl/key/r*

/usr/share/mongodb/bin/mongod --replSet rs1 --keyFile /usr/share/mongodb/repl/key/r3 --fork --port 30017 --dbpath /usr/share/mongodb/repl/data/r3 --logpath /usr/share/mongodb/repl/logs/r3.log --logappend

/usr/share/mongodb/bin/mongod --replSet rs1 --keyFile /usr/share/mongodb/repl/key/r4 --fork --port 31017 --dbpath /usr/share/mongodb/repl/data/r4 --logpath /usr/share/mongodb/repl/logs/r4.log --logappend

連接到 Primary node

mongo --port 28017 -u admin -p pass --authenticationDatabase admin

以指令 rs.add 新增節點

rs.add("localhost:30017");

以指令 rs.status(); 查看 rs 狀態

rs1:PRIMARY> rs.status();
{
    "set" : "rs1",
    "date" : ISODate("2015-11-12T08:55:57.447Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 0,
            "name" : "localhost:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 4033,
            "optime" : Timestamp(1447313389, 1),
            "optimeDate" : ISODate("2015-11-12T07:29:49Z"),
            "lastHeartbeat" : ISODate("2015-11-12T08:55:56.100Z"),
            "lastHeartbeatRecv" : ISODate("2015-11-12T08:55:57.086Z"),
            "pingMs" : 0,
            "configVersion" : 1
        },
        {
            "_id" : 1,
            "name" : "localhost:28017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 9018,
            "optime" : Timestamp(1447318556, 1),
            "optimeDate" : ISODate("2015-11-12T08:55:56Z"),
            "electionTime" : Timestamp(1447314272, 1),
            "electionDate" : ISODate("2015-11-12T07:44:32Z"),
            "configVersion" : 2,
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "localhost:29017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 8824,
            "optime" : Timestamp(1447313389, 1),
            "optimeDate" : ISODate("2015-11-12T07:29:49Z"),
            "lastHeartbeat" : ISODate("2015-11-12T08:55:56.101Z"),
            "lastHeartbeatRecv" : ISODate("2015-11-12T08:55:56.550Z"),
            "pingMs" : 0,
            "configVersion" : 1
        },
        {
            "_id" : 3,
            "name" : "localhost:30017",
            "health" : 1,
            "state" : 0,
            "stateStr" : "STARTUP",
            "uptime" : 1,
            "optime" : Timestamp(0, 0),
            "optimeDate" : ISODate("1970-01-01T00:00:00Z"),
            "lastHeartbeat" : ISODate("2015-11-12T08:55:56.116Z"),
            "lastHeartbeatRecv" : ISODate("2015-11-12T08:55:56.201Z"),
            "pingMs" : 16,
            "configVersion" : -2
        }
    ],
    "ok" : 1
}

rs1:PRIMARY> rs.status();
{
    "set" : "rs1",
    "date" : ISODate("2015-11-12T08:55:58.609Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 0,
            "name" : "localhost:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 4034,
            "optime" : Timestamp(1447318556, 1),
            "optimeDate" : ISODate("2015-11-12T08:55:56Z"),
            "lastHeartbeat" : ISODate("2015-11-12T08:55:58.100Z"),
            "lastHeartbeatRecv" : ISODate("2015-11-12T08:55:57.086Z"),
            "pingMs" : 0,
            "syncingTo" : "localhost:28017",
            "configVersion" : 2
        },
        {
            "_id" : 1,
            "name" : "localhost:28017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 9019,
            "optime" : Timestamp(1447318556, 1),
            "optimeDate" : ISODate("2015-11-12T08:55:56Z"),
            "electionTime" : Timestamp(1447314272, 1),
            "electionDate" : ISODate("2015-11-12T07:44:32Z"),
            "configVersion" : 2,
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "localhost:29017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 8826,
            "optime" : Timestamp(1447318556, 1),
            "optimeDate" : ISODate("2015-11-12T08:55:56Z"),
            "lastHeartbeat" : ISODate("2015-11-12T08:55:58.102Z"),
            "lastHeartbeatRecv" : ISODate("2015-11-12T08:55:58.564Z"),
            "pingMs" : 0,
            "syncingTo" : "localhost:28017",
            "configVersion" : 2
        },
        {
            "_id" : 3,
            "name" : "localhost:30017",
            "health" : 1,
            "state" : 5,
            "stateStr" : "STARTUP2",
            "uptime" : 2,
            "optime" : Timestamp(0, 0),
            "optimeDate" : ISODate("1970-01-01T00:00:00Z"),
            "lastHeartbeat" : ISODate("2015-11-12T08:55:58.116Z"),
            "lastHeartbeatRecv" : ISODate("2015-11-12T08:55:58.201Z"),
            "pingMs" : 12,
            "configVersion" : 2
        }
    ],
    "ok" : 1
}
rs1:PRIMARY> rs.status();
{
    "set" : "rs1",
    "date" : ISODate("2015-11-12T08:56:07.981Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 0,
            "name" : "localhost:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 4044,
            "optime" : Timestamp(1447318556, 1),
            "optimeDate" : ISODate("2015-11-12T08:55:56Z"),
            "lastHeartbeat" : ISODate("2015-11-12T08:56:06.100Z"),
            "lastHeartbeatRecv" : ISODate("2015-11-12T08:56:07.089Z"),
            "pingMs" : 0,
            "syncingTo" : "localhost:28017",
            "configVersion" : 2
        },
        {
            "_id" : 1,
            "name" : "localhost:28017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 9028,
            "optime" : Timestamp(1447318556, 1),
            "optimeDate" : ISODate("2015-11-12T08:55:56Z"),
            "electionTime" : Timestamp(1447314272, 1),
            "electionDate" : ISODate("2015-11-12T07:44:32Z"),
            "configVersion" : 2,
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "localhost:29017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 8835,
            "optime" : Timestamp(1447318556, 1),
            "optimeDate" : ISODate("2015-11-12T08:55:56Z"),
            "lastHeartbeat" : ISODate("2015-11-12T08:56:06.119Z"),
            "lastHeartbeatRecv" : ISODate("2015-11-12T08:56:06.566Z"),
            "pingMs" : 0,
            "syncingTo" : "localhost:28017",
            "configVersion" : 2
        },
        {
            "_id" : 3,
            "name" : "localhost:30017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 11,
            "optime" : Timestamp(1447318556, 1),
            "optimeDate" : ISODate("2015-11-12T08:55:56Z"),
            "lastHeartbeat" : ISODate("2015-11-12T08:56:06.119Z"),
            "lastHeartbeatRecv" : ISODate("2015-11-12T08:56:06.205Z"),
            "pingMs" : 4,
            "configVersion" : 2
        }
    ],
    "ok" : 1
}

將 r4 (port 31017) 加入 replica sets 也會看到類似的過程

rs.add("localhost:31017");

連接到新加入的 r4,可以發現資料已經複製過來了。

]# mongo --port 31017 -u test -p pass --authenticationDatabase test
MongoDB shell version: 3.0.7
connecting to: 127.0.0.1:31017/test
rs1:SECONDARY> db.temp.find();
Error: error: { "$err" : "not master and slaveOk=false", "code" : 13435 }
rs1:SECONDARY> db.setSlaveOk();
rs1:SECONDARY> db.temp.find();
{ "_id" : ObjectId("56443fedeabb66f27f42b08a"), "age" : 22 }

以指令 rs.remove(); 就可以移除節點

rs.remove("localhost:30017");
rs.remove("localhost:31017");

References

Replication

Database MongoDB 製作複本 ( Replica Sets )

Replication Concepts—複製的概念