2016年1月25日

如何在 CentOS 安裝 R 語言,如何產生 PDF Report

要在 CentOS 安裝 R 語言執行環境,如果已經安裝好了 EPEL,直接以 yum 安裝就好了。

首先搜尋 R-project 相關的套件

yum search R-project

然後安裝需要的套件

yum -y install R.x86_64 R-devel.x86_64 R-java.x86_64 R-java-devel.x86_64

如果要自己編譯 R,必須要先下載套件(必須有 --enable-R-shlib,否則安裝 RStudio-server 會遇到問題)

yum -y install readline-common readline-devel.x86_64 texinfo texlive

tar zxvf R-3.2.2.tar.gz
cd R-3.2.2
./configure --prefix=/opt/R-3.2.2 --enable-R-shlib --with-libpng --with-jpeglib --with-libtiff --with-x
make
make install

執行 configure 時,會抱怨

configure: WARNING: neither inconsolata.sty nor zi4.sty found: PDF vignettes and package manuals will not be rendered optimally

簡單測試一下,首先編輯一個檔案 test.r

#!/usr/bin/Rscript
print("HelloR")

修改執行的權限: chmod 755 test.r,有兩種方式可以執行 R Script

# 方法1
Rscript test.r
# 方法2
./test.r

在 windows 安裝 R 並測試產生 PDF

我們先在 windows 環境測試如何產生 PDF。

為了要處理文件中以及圖表中的中文顯示問題,還必須做一些額外的設定,在設定過程中,如果有遇到 MiKTeX 或是 RStudio 需要更新套件,就直接接受更新就可以了。

  1. 安裝 R 以及 RStudio
  2. 安裝 rmarkdown, devtools, showtext

    install.packages("rmarkdown")
    install.packages("devtools")
    install.packages("showtext")
  3. 安裝 rticles

    devtools::install_github("rstudio/rticles")
  4. 安裝 MiKTeX 安裝後要到選單 (MilkTex 2.9 -> Maintenance(Admin) -> Update(Admin) 更新套件

  5. 打開 RStudio Tools -> Global Options -> Sweave 將 "Weave Rnw Files using" 改為 "knitr" 將 "Typeset LaTex into PDF using" 改為 "XeLaTex"

  6. File -> New File -> R Markdown Default Ouput Format 選擇 PDF 假設存檔是存成 test.Rmd,在該檔案的同一個目錄中,建立一個新檔案 header.tex,編碼為 UTF-8,以下為內容,如果把 標楷體 改成 simsun.ttc 就會變成宋體

    \usepackage{xeCJK}
    \setCJKmainfont{標楷體}    % 可換字型 NSimSun
  7. 在 test.Rmd 中,調整檔案的內容如下

    ---
    title: "test"
    output:
      pdf_document:
        keep_tex: yes
        latex_engine: xelatex
        includes:
          in_header: header.tex
      html_document: default
      word_document: default
    ---
    
    file content: 中文
    ```{r fig.showtext=TRUE}
    summary(cars)
    print("中文")
    plot(cars,main='中文issue')
    plot(cars,main='中文issue2')
    ```
    
    ```{r, echo=FALSE}
    plot(cars)
    ```
  8. 為了控制報表輸出的目錄及檔案名稱,可以搭配 setwd 及 rmarkdown::render 的一些參數使用

    library("rmarkdown")
    setwd("D:/git/project/r")
    rmarkdown::render(input="test.Rmd", output_format="all", output_file="20151201", output_dir="output", encoding="UTF-8")

Ref: showtext package integration

在 CentOS 7 測試產生 PDF

為了在 CentOS 7 產生 PDF Report 我們必須先安裝一些套件

  1. 安裝 rstudio-server,因為 pandoc 是產生 PDF 常用的指令,所以直接在 /usr/bin/pandoc 產生一個連結

    wget https://download2.rstudio.org/rstudio-server-rhel-0.99.489-x86_64.rpm
    yum install --nogpgcheck rstudio-server-rhel-0.99.489-x86_64.rpm
    ln -s /usr/lib/rstudio-server/bin/pandoc/pandoc /usr/bin/pandoc
  2. 安裝 textlive 及相關套件

    yum -y install texlive texlive-latex texlive-xetex
    yum -y install texlive-collection-latex
    yum -y install texlive-collection-latexrecommended
    yum -y install texlive-xetex-def

    因為在測試過程中,一直發生一些套件缺少的錯誤訊息,所以直接把所有 textlive 的相關套件都安裝進去。

    yum -y install 'texlive-*'
  3. 因為測試時發生以下的 titling.sty 錯誤,但在 CentOS 卻沒有這個套件,所以根據 Missing *.sty files when generating PDFs #359 的建議,直接安裝 titling

    wget http://mirrors.ctan.org/macros/latex/contrib/titling.zip
    unzip titling.zip
    cd titling
    latex titling.ins
    sudo mkdir -p /usr/share/texlive/texmf-dist/tex/latex/titling
    sudo cp titling.sty /usr/share/texlive/texmf-dist/tex/latex/titling/
    sudo texhash
  4. 處理 linux 的 fonts 通常習慣使用 windows 字型,我們可以將 c:\windows\fonts 裡面的幾個常用字型上傳到 linux

    mkdir /usr/share/fonts

    然後將 simsun.ttc, msyh.ttf, msyhbd.ttf, sumhei.ttf 上傳到這個資料夾

    用以下指令更新 linux fonts

    fc-cache -f -v

    如果有更新成功,可以在這個指令的輸出結果中,看到中文字型

    fc-list
  5. 將剛剛測試的 header.tex 以及 test.Rmd 上傳到 linux 任意一個資料夾 但要注意的是,header.tex 只能填寫剛剛有上傳的字型名稱,不能填寫為字型的檔案名稱

    \usepackage{xeCJK}
    \setCJKmainfont{NSimSun}
  6. 進入 R console 在 text.Rmd 以及 header.tex 的目錄中,進入 R console 的命令列中,安裝 rmarkdown以及 showtext,然後用 rmarkdown render PDF

    install.packages("rmarkdown")
    install.packages("showtext")
    
    library("rmarkdown")
    rmarkdown::render("test.Rmd")

    測試過程如下:

    > rmarkdown::render("test.Rmd")
    processing file: test.Rmd
      |...........                                                      |  17%
      ordinary text without R code
    
      |......................                                           |  33%
    label: unnamed-chunk-1 (with options) 
    List of 1
     $ fig.showtext: logi TRUE
    
      |................................                                 |  50%
      ordinary text without R code
    
      |...........................................                      |  67%
    label: unnamed-chunk-2 (with options) 
    List of 1
     $ echo: logi FALSE
    
      |......................................................           |  83%
      ordinary text without R code
    
      |.................................................................| 100%
    label: unnamed-chunk-3 (with options) 
    List of 1
     $ echo: logi FALSE
    
    output file: test.knit.md
    
    /usr/bin/pandoc +RTS -K512m -RTS test.utf8.md --to latex --from markdown+autolink_bare_uris+ascii_identifiers+tex_math_single_backslash-implicit_figures --output test.tex --template /usr/lib64/R/library/rmarkdown/rmd/latex/default.tex --highlight-style tango --latex-engine xelatex --include-in-header header.tex --variable 'geometry:margin=1in' 
    /usr/bin/pandoc +RTS -K512m -RTS test.utf8.md --to latex --from markdown+autolink_bare_uris+ascii_identifiers+tex_math_single_backslash-implicit_figures --output test.pdf --template /usr/lib64/R/library/rmarkdown/rmd/latex/default.tex --highlight-style tango --latex-engine xelatex --include-in-header header.tex --variable 'geometry:margin=1in' 
    
    Output created: test.pdf
  7. 最後我們就能在 linux 得到裡面有中文字的 PDF Report

  8. 如果在 render 時加上 "all" 的參數,就可以同時取得 html, docx 及 pdf 三種格式的文件

    rmarkdown::render("test.Rmd", "all")

2016年1月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年1月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年1月4日

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