2016年12月21日 星期三

解決CentOS無法掛載(mount) NTFS檔案系統設備的問題

今天想要複製檔案到隨身碟時發現插下去怎麼沒有反應,雖然可以顯示隨身碟已經插上電腦,但要進行讀寫時出現了一大串錯誤訊息,但是可以看到裡面有這麼一句話
Error mounting: mount: unknown filesystem type 'ntfs'
原來CentOS不能辨識NTFS這個檔案系統的東西阿,搜尋後發現需要安裝ntfs-3g才能正常存取這種檔案系統,安裝方法如下
#yum install ntfs-3g

然後系統顯示
No package ntfs-3g available.
Error: Nothing to do
居然從yum裡面找不到,原來要從epel裡面才找的到阿。

補充:EPEL (Extra Packages for Enterprise Linux) 是一個由 Fedora Special Interest Group 社群所維護的套件庫,其主要目的是提供各種企業級的 Linux 一些額外的高品質套件,這個套件庫可用於RHEL、CentOS、Scientific Linux 與Oracle Linux。

正確的安裝方法如下
先安裝EPEL套件包
#yum install -y epel-release

安裝以EPEL以後就可以用yum安裝ntfs-3g囉
#yum install -y ntfs-3g

安裝完以後,如果將來需要掛載NTFS檔案格式的設備時就不會再發生問題了!

2016年12月20日 星期二

輕鬆解決 VirtualBox is already locked by a session (or being locked or unlocked)

話說前幾天實驗室跳電,雖然伺服器會自己重開機,但虛擬機並不會跟著重開(懶得設定的下場),遠端開啟虛擬機的指令如之前的文章所述

$VBoxManage startvm centos03

在正常情況下,執行成功應該會出現這個訊息
Waiting for VM "centos03" to power on...
VM "centos03" has been successfully started.
但是這次的執行出現的卻是
VBoxManage: error: The machine 'centos03' is already locked by a session (or being locked or unlocked)
VBoxManage: error: Details: code VBOX_E_INVALID_OBJECT_STATE (0x80bb0007), component MachineWrap, interface IMachine, callee nsISupports
VBoxManage: error: Context: "LaunchVMProcess(a->session, sessionType.raw(), env.raw(), progress.asOutParam())" at line 589 of file VBoxManageMisc.cpp
那要怎麼解決呢?先試試看最白癡的方法直接重開好了,反正也沒再跑甚麼東西,因此我們執行關閉虛擬機指令

$ VBoxManage controlvm centos03 poweroff

正常的情況下應該會出現
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
但在這個例子中出現的卻是
VBoxManage: error: Invalid machine state: Starting (must be Running, Paused or Stuck)
VBoxManage: error: Details: code VBOX_E_INVALID_VM_STATE (0x80bb0002), component ConsoleWrap, interface IConsole, callee nsISupports
VBoxManage: error: Context: "PowerDown(progress.asOutParam())" at line 231 of file VBoxManageControlVM.cpp
現在是甚麼情況?開也開不了關也關不了,怎麼辦呢?這時候只要執行只下命令就好

$ VBoxManage startvm centos03 --type emergencystop

這條指令不會聽出任何提示訊息,執行完就直接關閉虛擬機器,因此我們隨後可以直接再次開啟這台虛擬機器就解決囉

2016年11月13日 星期日

How to install VirtualBox on CentOS 7 using command line


前言


最近已經將資料庫在練習的環境測試完了,接下來就該用實驗室的電腦做一點比較正式的環境架設了,但最怕麻煩的我怎麼可能乖乖蹲在實驗室架設環境呢?來回實驗室要快半小時耶,瘋了喔這麼累,在家裡躺在床上用SSH(Secure Shell)遠端實驗室電腦才是王道阿,網路上大多數的教學都是教大家如何在圖形化介面下安裝與設定虛擬機器,這對我這種懶人來說太麻煩了,所以誕生了一個寫一個完全使用command line來安裝VirtualBox教學的念頭,作為小懶人間的小小交流。
  因為平常習慣用的是CentOS,所以下面的指令皆用建立CentOS虛擬機為例。


步驟


Step1:安裝虛擬機器

由於Yum預設的伺服器中並沒有內建VirtualBox軟體庫(Repository)的相關資訊,因此我們必須自己建立一個VirtualBox的repo檔,並安裝目前最新版(20161107)的VirtualBox。
#cd /etc/yum.repos.d
#wget http://download.virtualbox.org/virtualbox/rpm/rhel/virtualbox.repo
#yum install -y VirtualBox-5.1.x86_64

Step2:建立虛擬機器

#VBoxManage createvm --name centos
createvm:建立一個虛擬機器檔案
--name後面加虛擬機器的名字
這個命令在執行成功後會在你的家目錄底下建立VirtualBox VMs/centos/centos.vbox

Step3:幫虛擬機器建立硬碟檔案

#VBoxManage createhd --filename centos --size 1000
createhd:建立硬碟
--filename後面加上剛剛建立的虛擬機器檔名
--size是硬碟大小,以MB(Megabyte)為單位,100MB只是示範使用

Step4:讓VirtualBox找到你的虛擬機器檔案

#VBoxManage registervm '/home/user/VirtualBox VMs/centos/centos.vbox'
registervm後面接虛擬機器檔案的完整路徑,前後用引號圈起來
(其實也可以在createvm階段就進行這個操作,只要在filename打完後,空一格加上--register就好,但因為個人習慣才把這個步驟在這個階段進行。)

Step5:虛擬機器的更多設定

#VBoxManage modifyvm centos --ostype RedHat_64 --memory 512 --vram 10 --accelerate3d on --audio alsa --audiocontroller ac97 --nic1 nat --nictype1 82540EM --cableconnected1 on
modifyvm後面接虛擬機器的名稱,讓你對指定虛擬機器進行更多設定
--ostype在使用圖形化介面版本新增虛擬機器時,有沒有印象有一欄是要選擇虛擬機器類型?
那個步驟就是在這邊進行,注意使用的系統是不是64位元的,像我使用的是64位元的CentOS 7,因此我的類型是輸入RedHat_64 (底下會附上比較常見的幾種,如果使用的作業系統沒有在底下看到可以執行$VBoxManage list ostypes查閱。)
--memory 設定給予虛擬機器的記憶體大小,記憶體大小以MB(Megabyte)為單位,512MB只是示範使用。
--vram 顯示記憶體大小,記憶體大小以MB(Megabyte)為單位,10MB只是示範使用
--accelerate3d 是否啟動3D加速
--audio後接主機的音效驅動程式,可以選擇的有none, null, oss, alsa , pulse
--audiocontroller後接音效晶片,可以選擇的有ac97, hda, sb16(這部分還不確定理解是否正確)
--nic後面不用空格接上網路卡編號(1~n),之後空格接上模式,可以選擇的有none, null, nat, bridged, intnet, hostonly, vde
--nictype後面不用空格接上網路卡編號(1~n),之後空格接上網路卡的類型,可以選擇的選項有Am79C970A, Am79C973, 82540EM, 82543GC, 82545EM, virtio
--cableconnected後面不用空格接上網路卡編號(1~n),選擇是否連接

作業系統與ostype對照
Debian (32-bit)----->Debian
Debian (64-bit)----->Debian_64
Fedora (32-bit)----->Fedora
Fedora (64-bit)----->Fedora_64
Red Hat (32-bit)--->RedHat
Red Hat (64-bit)--->RedHat_64
Ubuntu (32-bit)---->Ubuntu
Ubuntu (64-bit)---->Ubuntu_64
FreeBSD (32-bit)-->FreeBSD
FreeBSD (64-bit)-->FreeBSD_64

Step8:設定虛擬機的儲存設備連接埠

首先我想新增一個IDE給虛擬機器連接光碟機
$VBoxManage storagectl centos --name IDE --add ide --controller PIIX4 --bootable on
再新增一個SATA給虛擬機器連接硬碟
$VBoxManage storagectl centos --name SATA --add sata --controller IntelAhci --bootable on
storagectl後面加上目標虛擬機器的名稱
--name可以讓你幫這個儲存裝置命名,你要想取啥甚至連Trump都行
--add後面接上你的介面,可以選擇的有ide, sata, scsi, floppy
--controller後接的為控制晶片,可以選擇的有LsiLogic, LSILogicSAS, BusLogic, IntelAhci, PIIX3, PIIX4, ICH6, I82078(對這方面較沒研究,因此在這邊都選擇最常見的,IDE對PIIX4,SATA對IntelAhci)
--bootable選擇on讓他可以啟動

Step9:將儲存設備接上虛擬機器

設定虛擬機器連接埠所連接的對象(硬碟),並且放入虛擬機器的硬碟檔案
#VBoxManage storageattach centos --storagectl SATA --port 0 --device 0 --type hdd --medium /home/user/VirtualBox\ VMs/centos/centos.vdi
設定虛擬機器連接埠所連接的對象(光碟機),並且放入虛擬機器的光碟檔案
#VBoxManage storageattach centos --storagectl IDE --port 0 --device 0 --type dvddrive --medium /home/user/CentOS-7-x86_64-Everything-1511.iso
storageattach後面接目標虛擬機器
--storagectl後面接剛剛透過--name所命名的連接埠
--port儲存設備接上的port
--device設定該裝置在該port上的編號
--type後接該儲存設備的型態,可以使用的有dvddrive, hdd, fdd
--medium後接上欲放入的檔案完整路徑

Step10:啟動
經過10個步驟的摧殘,終於可以啟動與關閉虛擬機器了
#VBoxManage startvm centos
#VBoxManage controlvm centos poweroff

總結


  以上就是如何在Linux安裝、設定並且啟動虛擬機器的方法,如果要遠端重灌的話,需要開啟遠端桌面伺服器VRDE,這部分還沒有時做過,因此不在本文章中討論。

【Linux教學】授權使用者使用sudo

在管理伺服器的時候,不建議直接使用root進行控制,雖然在執行指令時完全不用考慮權限問題非常方便 (我自己以前也常常這樣,直到刪掉不該刪的東西...),為了不要讓這種悲劇再次發生在你我身邊,大家在管理伺服器時建議使用可以用超級使用者權限的用戶登入即可,有需要時加個sudo就好,底下附上如何讓使用者取得使用sudo權限的方法。

要讓使用者使用sudo,必須修改/etc/sudoers這個文件,但是因為這個文件的擁有者與群組皆為root所有,且為唯獨檔(-r--r-----),因此必須先取得root密碼才可以進行修改。

1. 登入root或是執行su指令,並輸入root密碼,用超級使用者的身分操作系統
2. 修改/etc/sudoers這個檔案
#vim /etc/sudoers
在大約98行的地方可以找到
root    ALL=(ALL)       ALL
在它底下新增一行
username  ALL=(ALL)       ALL
(username為欲給予使用sudo權限的使用者)
:wq儲存

儲存完之後,該user就可以使用sudo了

2016年11月5日 星期六

(under construction)Linux指令筆記

隨手放一些常常忘記的指令以備不時之需~

1. 查看系統版本
#uname -a

2. 檢查SELinux (Security Enhanced Linux) 狀態
#getenforce

→修改SELinux狀態
#vim /etc/selinux/config
修改SELINUX=enforcing
SELinux的模式可分為三種模式,以下取自於鳥哥的Linux私房菜
  1. enforcing:強制模式,代表 SELinux 運作中,且已經正確的開始限制 domain/type 了。
  2. permissive:寬容模式:代表 SELinux 運作中,不過僅會有警告訊息並不會實際限制domain/type 的存取。
  3. disabled:關閉,SELinux 並沒有實際運作。
3. 修改ip
#ifconfig <device_name> <ip_addr> 
其他網卡資料的修改

#vim /etc/sysconfig/network-scripts/<device_name>

4. hostname
檢查hostname
#hostnamectl

修改hostname
CentOS 7更新了修改hostname的方法,現在只需要
# hostnamectl set-hostname <new_hostname>

2016年10月30日 星期日

【翻譯練習2】Eh Bee Family - THIS WILL GIVE YOU NIGHTMARES!!

Eh Bee Family 一發佈就剛好滑到

還真的是沒有大多數人的惡夢XDD

發現so last month聽不太懂就來研究看看

原來是old的意思R

最重要的是

Miss Monkey還是一如往常的可愛 <3


Cassandra Tutorial - 名詞篇 - 最後更新20161031


1、前言


突然意識到這個部落格荒廢好久了XD,之前都在忙上課或是研討會的東西(或是耍費)所以都沒有增加一點新東西,趁著繼續用碩論的機會,趕快來發一篇文章喚醒自己的記憶。

個人認為相較於MongoDB,Cassandra的學習資源真的少的可憐,因為Cassandra的系統架構更加的複雜(但是網路上都說很簡單...),雖然官方文件以及cassandra.yaml的說明很完整,但又有很多眉眉角角的東西需要注意,因此在學習上面碰到許多障礙,這篇文章整理了官方文件以及網路資源,使用自己可以理解的話整理而來,包括了很多讓我搞混的名詞,在整理的同時希望可以幫助自己以及讀者更加了解這些名詞的意義,而因我也正在學習中,因此這篇文章將會持續更新,如果有寫錯的地方歡迎告知討論。


2、正文


2.1 Gossip

Gossip是一個點對點的傳輸協定,透過這個協定,集群中節點間週期性(秒)的交換彼此的狀態,因此可以使節點快速的了解其他節點的狀態。
為了避免Gossip在溝通時出現問題,同個集群中的節點必須使用相同的seed nodes list。

2.2 seed node

當有新的節點加入集群時,該節點會與seed node溝通(bootstrap),以取得集群中其他節點的狀態,一個集群中可以有一個以上的seed node節點。
當節點被設定為seed node後,在集群運作時,唯一的差別就只是多了與新節點溝通資訊(bootstrap)這項功能功能,因此它也不是SPOF(已經開始運作後,沒有需要bootstrap的問題)。

2.3 Single Point of Failure(SPOF)

SPOF直翻為單點失效,所以SPOF是指一個狀態嗎? 非也,SPOF指的是在某環境中,如果某硬體(或指單位,或指零件等等)故障會造成整個系統不可用時,該硬體就被稱為SPOF。
補充:
在看一些論文時,常常會看到文中稱某硬體redundancy,一直以為都把它翻成多餘的,但是翻譯成備援可能比較恰當,所以在高可用環境中,為了避免SPOF,使用Redundancy的硬體配備有它的必要性。
Cassandra這種每個節點的腳色都一樣的架構可以避免SPOF的問題。

2.4 Bootstrap

單純是指新節點加入時與seed node溝通資料資訊的過程。
---內容持續增加中---

2016年7月18日 星期一

OSI Model L4-L1筆記

壹、前言:


最近突然發現一些對網路架構觀念不清楚的地方,複習東光老師上課內容的同時順便做一個小筆記整理一下自己的思緒,在成為自己小抄的同時希望也可以給其他人在有需要的時候的作為一個簡單參考依據。

這篇文章只會包含幾個對我個人比較容易混淆的部分,也因為自己能力尚且不足,所以並不會對OSI七層作一個全盤的介紹,只將重點主要放在傳輸層、網路層、資料鍵結層、實體層這四個部分。

由於這篇文章最初的用意是一個快速複習筆記,因此並不會對部分功能做詳細的說明,而這些部分的運作流程將在未來的其他主題中進行討論。


貳、正文


一、傳輸層 (Transport Layer)


封包名稱:Segment

主要工作:
1. 資料的切割與重組 (segmenting data and reassembling data)
2. 提供上層 (application layer in TCP/IP model) 多工的網路使用環境
>>因為有把應用層傳下來的資料切小片,因此不用擔心特定應用卡住整個頻寬
3. 確定資料流 (identifying and tracking individual communication)
>>透過port number,知道封包是哪個"應用"傳來的,確認傳到哪個"應用" 
>>不是IP位址,確定IP是網路層的工作

主要協定
1. 傳輸控制協定 (TCP, transmission control protocol)
2. 用戶資料報協定 (UDP, user datagram protocol)

TCP與UDP都支援:
1. Segmentation and Reassembly
2. Conversation multiplexing
>>來源應用與目標應用之間的資料流稱為conversation
>>由底下兩個封包表頭可以發現,兩者皆有Source Port與Destination Port

TCP封包:

UDP封包:

TCP特性:
1. 連線導向 (connection-oriented conversation)
>>傳輸資料前先進行三方交握(three-way handshake)
>>UDP為非連線導向 (connectionless)
2. 建立可靠的資料傳輸 (reliable delivery)
>>將封包編號(sequence number)
>>收到封包後給予正向回應(ACK)
>>如未收到正向回應,重送
3. 流量控制 (flow control)
>>控制window size

使用TCP的時機:
(key: TCP由傳送端決定是否重送)
1. 需要知道有無傳送成功
2. 如果沒成功會自動重送
3. 資料可以照順序 (in order) 的傳送

使用UDP的時機:
(key: UDP由接收端決定是否重送)
1. 追求速度
2. 消耗較少的資源 (low overhead)
3. 不需要回應是否收到
4. 不需要重送遺失的資料
5. 資料一但送出皆視為傳送成功

二、網路層 (Network Layer)

封包名稱:Packet
主要工作:
1. 定義網路位址與電腦位址 (addressing)
2. 幫封包找路 (routing)
>>由以上兩點可以知道,對網路層而言路由器與IP是最重要的兩項元素

主要協定:
1. 網際協定 (IP, internet protocol)
>>分為IPv4 (最常用) 與IPv6 (最新)
2. 網路控制消息協定 (ICMP, internet control message protocol)
>>隨著IP的版本分為ICMPv4與ICMPv6
3. 路由協定 (routing protocal)
>>產生路由表的協定
>>種類五花八門,隨著不同品牌或使用環境等使用不同的協定

IPv4特性:
1. 不需要為了傳封包而是先建立連線 (connectionless)
>>如果有需要事先建立連線的需求,將在傳輸層完成
>>如果有封包丟失重送的需求,將在傳輸層完成
>>不用為了保證連線在PDU (protocol data unit) 加上更多欄位,可以減少overhead
2. 不保證送到 (best effort)
>>IP沒有能力管理或恢復不見的封包
>>但是在特殊情況下,例如使用ping或tracert時,會由ICMP進行回報
3. 不需要理會訊號的載具 (media independent)
>>但是如果Router要將MTU (maximum transmission unit) 由較大MTU設備傳來的封包傳到較小MTU設備上就要將封包切片 (fragment the pocket)
>>data link layer會將MTU大小告知network layer,因此network layer可以依據這項資訊來切割封包
>>MTU: 媒介可傳送的最大PDU,e.g. 乙太網路的MTU為1500byte

IPv4表頭重點欄位:



1. Source Address/Destination Address: 顧名思義,將在這兩欄位填上來源與目的地的IP
2. Time To Live (TTL) : 表示封包最多可以經過的路由器數
>>每經過一個路由器將TTL減1,並在TTL歸0時由接收到的路由器丟棄該封包。
>>假如路由器不小心組成迴圈而封包永遠無法送達目的地,TTL可以避免封包永遠在這些路由器之間傳送,不斷浪費資源與時間。
3. Protocal: 指出此封包是使用哪一種協定,e.g. TCP, UDP, ICMP....etc
4. Type of Service: 為了保證QoS (quality of service) 所保留的欄位,目的在於控制封包優先權,優先權高的封包將由路由器優先處裡
5. (Header) Chechsum 確保這個封包是正確的,沒有損毀
>>重點:這不表示IP保證可靠的資料傳輸,可靠的資料傳輸是保證封包沒有遺失,而IP是保證資料沒有損毀

三、資料鍵結層 (Data Link Layer)

封包名稱:Frame

主要功能 (在網路卡中進行):
1. 加框 (framing)
>>讓封包在不同媒體(媒介、載具)上傳輸時不會造成損壞
2. 媒體存與控制 (MAC, media access control)
>>為了避免封包碰撞 (collision),訊號重疊造成資料損毀或是網路效能低落等問題
>>使用時要考慮到該載具是shared media還是non-shared media

在shared media中的MAC實作:
1. 乙太網路使用的CSMA/CD
>>過去在使用hub的環境下使用,但在交換器 (switch) 發明之後幾乎用不到了
2. 無線網路使用的CSMA/CA
>>CS (carrier sense):偵測媒體使用狀況
>>MA (multiple access):多點傳輸→資料透過廣播方式送出,對有線網路來說會產生碰撞,對無線網路而言可能會產生干擾
>>CD (collision detection):如果偵測到由封包碰撞產生的frequency ripple,所有設備停止傳送封包,並在隨機的時間 (random delay time ) 後重送
>>CA (collision avoidance):先偵測媒體使用狀況,如果媒體無人使用,便向無線接收器 (AP, access point) 發送RTS (require to send),當收到來自AP的CTS (clear to send) 後,便開始傳送封包,如未收到CTS就不能傳送→重點:由AP控制能否傳送封包

在non-shared media (point-to-point)中的MAC實作需要考量的點:
1. 全雙工 (full-duplex) 環境:
>>可同時進行收或送
>>雙絞線開起了全雙工的時代
2. 半雙工 (half-duplex) 環境:
>>可以進行收,也可進行送,但不可同時進行
>>過去使用的同軸電纜就是如此

影響封包長度的因素:
1. 網路環境:
>>shared media與non-shared media
2. 傳輸媒介:
>>fragile (易碎的) environment,需要控制的因素太多→表頭與表尾較長,較消耗資源,傳輸速率較低
>>protect environment,需要控制的因素較少→表頭表尾較短,較節少資源,傳輸速率較高

四、實體層 (Physical Layer)


主要功能:
1. Encoding:
>>將資料轉換為是先定義好的二進位形式
2. Signaling:
>>將數位資料 (010101) 轉換為適合不同媒介的形式
>>e.g. 無線電波
3. 定義不同的接頭標準
>>e.g. RJ-45
4. 分辨那些訊號是屬於哪一個Frame
>>透過事先定義好的二進位格式可以知道在一連串的訊號中,Frame的開頭與結尾在哪裡

參、結語


在寫這篇筆記,對我而言真的是受益良多,在編輯的過程中,加強了過去不完整的概念,同時也將新發現的一些問題順勢解決掉,並且全部想過一次後,更加強了自己的印象,但最大的願望還是希望不要再有把這份筆記拿出來看的一天。

2016年7月5日 星期二

Cassandra Tutorial - 安裝篇


壹、環境

1. 作業系統:CentOS 7
2. Cassandra版本:DataStax Distribution of Apache Cassandra 3.2
看到這邊可能很多人會想說好好的官方版本不用,幹嘛用別的商業公司開發的版本?這有兩個原因:

  1. DataStax維護的版本可以用Yun下載,不管是安裝還是維護都很方便(Apache的版本則無)
  2. 對比過DataStax官方文件Apache官方文件後,應該沒有甚麼人會想使用Apache的版本


3. Java版本:務必確認Java版本為Oracle Java 8或是 OpenJDK 8 (1.8.x) 以上版本
#java -version

貳、步驟

一、建立Cassandra的Repos設定檔
因為CentOS預設的Yum軟體庫中沒有Cassandra軟體庫(Repository)的相關資訊,因此必須先新增Cassandra的Repos設定檔
#vim /etc/yum.repos.d/datastax.repo
[datastax-ddc]  
name = DataStax Repo for Apache Cassandra 
baseurl = http://rpm.datastax.com/datastax-ddc/3.2 
enabled = 1 
gpgcheck = 0
二、安裝Cassandra
# yum install -y datastax-ddc
此時會安裝兩個檔案:datastax-ddc與datastax-ddc-tools

三、執行Cassandra
#service cassandra start

2016年7月1日 星期五

我的第一支Shell Script

前言

因為研究計畫需要,不得不面對自己長久以來一直逃避的Shell Script,但真的開始寫之後,不得了,似乎可以體會到學長以前跟返校演講時說過的一句話:「越厲害的MIS越輕鬆」這句話的涵義了。

介紹

這次計畫需要用到的功能有以下幾點
1. 刪除a0001~a0400這400個資料夾裡面的所有檔案
2. 刪除完之後在每個目錄中建立www資料夾
3. 改變權限讓該目錄用戶可以存取

程式碼

#!/bin/bash
#Introduction: This program has 3 main functions
#       1. remove all files in home directory belong to specific users
#       2. create a directory named www in each home directory
#       3. change the owner of www
#First Release: 20160628
#Last Update: 20160629 - add chown and comment
#By: NCUE IM Li-heng, Chou
dir='/home/'
for number in $(seq -f 'a%04g' 1 400)
do
        cd ${dir}${number}
        rm -rf *
        mkdir www
        chown ${number}:students www
done

2016年6月26日 星期日

最近對CAP theorem的一些體會

前言

在摸索分散式系統的途中,一定會發現CAP理論是一個會在各式文章或論文中會被作者拿出來參考的一個主題,過去自己看待CAP理論也一直有一種「就這樣」的感覺,直到最近開始接觸到Cassandra才有比較深刻的體會。


簡介

CAP Theorem是由UC Berkeley的Eric Brewer於2000年時所提出,並在兩年後的2002年由MIT的Nancy Lynch 與 Seth Gilbert這兩位學者所證明,自此成為一個理論。

CAP Theorem指的是沒有一個分散式的系統可以同時滿足一致性(Consistence)、可用性(Availability)與分區容忍性(Partition Tolerance)三項,最多只能同時滿足兩項需求。



  • 一致性(Consistence)指的是:在同個時間內,任何節點的資料皆相同(all nodes see the same data at the same time)
  • 可用性(Availability)指的是:每個對系統的請求不論成功或失敗都可以獲得回應(a guarantee that every request receives a response about whether it success of failed)
  • 分區容忍性(Partition Tolerance):不論是任何信息遺失或是某節點的系統故障都不會影響到系統的運作(the system continues to operate despite arbitrary message lose or failure of part of the system)

思考


最近重新看到這張圖,上面標示的MongoDB 屬於CP(consistence+partition tolerance),而 Cassandra 屬於AP(availability+partition tolerance)

WHY??

對於這種分類法的想法是

MongoDB對資料的處理方法為採用Primary與Secondary這種分級制的系統,用戶在的所有操作皆會導向Primary,只會由Primary存取資料,正因為如此,大家都可以看到相同的資料(一致性),假如Primary這個節點出問題,系統會自動由Secondary中選出新的Primary,再選出來新的Primary前,系統可能無法正確的給予回應(可用性較低),因此被列為CP。

Cassandra之所以為AP是因為他是使用環狀結構(Ring),在Ring中的每個節點都是平等的,存取時會隨機選擇一個節點來進行存取,因此不會出現像是MongoDB這種由於Primary出問題會造成的極短時間的無法存取,但是資料需要在每個節點間進行同步,同步完成前不能保證資料的一致性,依此將Cassandra列為AP。


結論

以上只是個人對CAP理論的一點點想法,但是坦白說我認為CAP理論這種三選二的說法實在太過狹隘,隨著技術的演進,已經可以漸漸地去彌補掉自己"相對"弱勢的地方,而且可能可以依據用戶需求選擇需要的方案,而不是只有一種作法,因此我認為,最多只能說自己的系統偏向CP或是偏向AP罷了,如果有其他的想法歡迎討論。

2016年6月19日 星期日

MongoDB Sharding Cluster Tutorial

壹、環境

因為是只是練習環境,我開了4台虛擬機 (com6, com7, com8, com9) 進行測試
作業系統皆為CentOS 7
com6: 192.168.1.1
com7: 192.168.1.2
com8: 192.168.1.3
com9: 192.168.1.4


貳、Sharding Cluster構想

預計由三組Replica Set,一個Config Server,一個Routing Server (mongos) 組成
因此
com6: ("rs0/192.168.1.1:27017,192.168.1.1:27018,192.168.1.1:27019")
com7: ("rs1/192.168.1.2:27017,192.168.1.2:27018,192.168.1.2:27019")
com8: ("rs2/192.168.1.3:27017,192.168.1.3:27018,192.168.1.3:27019")
com9: Config Server: ("192.168.1.4:27017") , Routing Server: ("192.168.1.4:27018")


參、步驟

一、安裝MongoDB

1. MongoDB的官方軟體庫包含有五個軟體包(packages)
(1) mongodb-org                  這是一個metapackage,可以透過安裝這個自動安裝其他四個軟體包
(2) mongodb-org-server       mongod伺服器與設定檔
(3) mongodb-org-mongos    mongos伺服器.
(4) mongodb-org-shell         mongo操作介面(shell)

(5) mongodb-org-tools         包括了所有跟mognodb有關的小工具

2. 因為CentOS預設的Yum伺服器中沒有MongoDB軟體庫(Repository)的相關資訊,因此必須先新增Repos設定檔
#vi /etc/yum.repos.d/mongodb-org-3.2.repo
[mongodb-org-3.4]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/3.4/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-3.4.asc
3. 新增完之後就可以使用Yum安裝了
#yum install –y mongodb-org


二、設定MongoDB


在構想階段,有com6, com7 com8分別為三個MongoDB實體的Replica Set,因此設定設定檔、資料庫目錄、記錄檔共要三組
而com9為Config Server與Routing Server所以設定檔跟記錄檔要兩份,但Routing Server本身不儲存任何資料,因此只需要替Config Server建立一個資料庫目錄即可

1.  複製設定檔
com6, com7, com8 都要執行以下指令,將設定檔複製三分,分別為mongod1.conf, mongod2.conf, mongod3,conf
#mv /etc/mongod.conf /etc/mongod1.conf
#cp /etc/mongod1.conf /etc/mongod2.conf
#cp /etc/mongod1.conf /etc/mongod3.conf

com9裡面有兩個MongoDB實體,因此要兩份設定檔,分別為mongod-configserv.conf與mongod-routingserv.conf
#mv /etc/mongod.conf /etc/mongod-configserv.conf
#cp /etc/mongod1.conf /etc/mongod-routingserv.conf

2. 修改設定檔

2.1. com6, com7, com8設定
#vim /etc/mongod1.conf
修改將第10行的path: /var/log/mongodb/mongod.log
將com6的mongod.log改為mongod1.log
將com7的mongod.log改為mongod1.log
將com8的mongod.log改為mongod1.log

修改第14行的dbPath: /var/lib/mongo
將com6的mongo改為dbPath: /var/lib/mongo1
將com7的mongo改為dbPath: /var/lib/mongo1
將com8的mongo改為dbPath: /var/lib/mongo1


註解第29行的bindIp: 127.0.0.1
將com6的bindIp註解起來,變為#bindIp
將com7的bindIp註解起來,變為#bindIp
將com8的bindIp註解起來,變為#bindIp

取消第36#replication的註解
將com6的#replication: 取消註解,變成replication: 
將com7的#replication: 取消註解,變成replication: 
將com8的#replication: 取消註解,變成replication: 

在第37行新增Replica Set的名稱(  replSetName: )
com6為  replSetName: rs0
com7為  replSetName: rs1
com8為  replSetName: rs2
注意:replSetName前面空兩格,:後面空一格,不然會報錯

#vim /etc/mongod2.conf
修改將第10行的path: /var/log/mongodb/mongod.log
將com6的mongod.log改為mongod2.log
將com7的mongod.log改為mongod2.log
將com8的mongod.log改為mongod2.log

修改第14行的dbPath: /var/lib/mongo
將com6的mongo改為dbPath: /var/lib/mongo2
將com7的mongo改為dbPath: /var/lib/mongo2
將com8的mongo改為dbPath: /var/lib/mongo2

修改第28行的port: 27017
com6改為port: 27018
com7改為port: 27018

com8改為port: 27018


註解第29行的bindIp: 127.0.0.1
將com6的bindIp註解起來,變為#bindIp
將com7的bindIp註解起來,變為#bindIp
將com8的bindIp註解起來,變為#bindIp

取消第36#replication的註解
將com6的#replication: 取消註解,變成replication: 
將com7的#replication: 取消註解,變成replication: 
將com8的#replication: 取消註解,變成replication: 

在第37行新增Replica Set的名稱(  replSetName: )
com6為  replSetName: rs0
com7為  replSetName: rs1
com8為  replSetName: rs2
注意:replSetName前面空兩格,:後面空一格,不然會報錯


#vim /etc/mongod3.conf
修改將第10行的path: /var/log/mongodb/mongod.log
將com6的mongod.log改為mongod3.log
將com7的mongod.log改為mongod3.log
將com8的mongod.log改為mongod3.log

修改第14行的dbPath: /var/lib/mongo
將com6的mongo改為dbPath: /var/lib/mongo3
將com7的mongo改為dbPath: /var/lib/mongo3
將com8的mongo改為dbPath: /var/lib/mongo3

修改第28行的port: 27017
com6改為port: 27019
com7改為port: 27019

com8改為port: 27019


註解第29行的bindIp: 127.0.0.1
將com6的bindIp註解起來,變為#bindIp
將com7的bindIp註解起來,變為#bindIp
將com8的bindIp註解起來,變為#bindIp

取消第36#replication的註解
將com6的#replication: 取消註解,變成replication: 
將com7的#replication: 取消註解,變成replication: 
將com8的#replication: 取消註解,變成replication: 

在第37行新增Replica Set的名稱(  replSetName: )
com6為  replSetName: rs0
com7為  replSetName: rs1
com8為  replSetName: rs2
注意:replSetName前面空兩格,:後面空一格,不然會報錯

2.2. com9設定

2.2.1. config server
#vim mongo-config.conf
將第10行的path: /var/log/mongodb/mongod 改為path: /var/log/mongodb/mongod-configserv.log
將第14行的dbPath: /var/lib/mongo改為 dbPath: /var/lib/mongo-configserv
將第29行的bindIp: 127.0.0.1註解掉,改為#bindIp:  127.0.0.1
將第38行的#sharding取消註解,變為sharding
在第39行打上  clusterRole: configsvr
(clusterRole前面空兩格,:後空一格,不然會報錯)

2.2.2. routing server(mongos)
#vim /etc/mongo-routingserv.conf
將第10行的path: /var/log/mongodb/mongod 改為path: /var/log/mongodb/mongod-routingserv.log
將13~16行的與storage與旗下之選項全部註解掉
將第28行的,port:  27017改為port:  27018
將第29行的bindIp: 127.0.0.1註解掉,改為#bindIp:  127.0.0.1
將第38行的#sharding取消註解,變為sharding
在第39行打上  configDB: 192.168.1.4:27017
帶第40行打上  chunkSize: 64
(configDB與chunkSize前面空兩格,:後面空一格,不然會報錯)

3. 建立資料庫目錄

3.1. com6, com7, com8
#mv /var/lib/mongo /var/lib/mongo1
#mkdir mongo2
#mkdir mongo3
#chown mongod:mongod mongo2
#chown mongod:mongod mongo3


3.2. com9
#mv /var/lib/mongo /var/lib/mongo-configserv

三、建立Sharding Cluster

1. 建立Replica Set

1.1. 啟動mongod
在com6, com7, com8都要執行mongod
#mongod -f /etc/mongod1.conf
#mongod -f /etc/mongod2.conf
#mongod -f /etc/mongod3.conf
-f 的意思是使用設定檔執行,後面接設定檔路徑

1.2. 初始化Replica Set
假設三組Replica Set皆要以使用27017這個port的mongod作為為Primary
在com6, com7, com8執行mongo客戶端
#mongo --port 27017
--port後面接的port是mongod設定檔中的設定port
所以如果是打27018就是連入使用mongod2.conf這個設定檔的mongod

分別在三台電腦中輸入
>rs.initiate()
輸入完之後如果有顯示{“ok” : 1}表示設定成功
會看到你的輸入欄位由>變為rs0:OTHER>
過幾秒按一下Enter會發現rs0:OTHER>變為rs0>PRIMARY,此時可以知道這個mongod在Replica Set的地位已經是Primary

1.3. 替mongod加入新成員
在com6中輸入
rs.0:PRIMARY>rs.add(”192.168.1.1:27018”)
rs.0:PRIMARY>rs.add(”192.168.1.1:27019”)
顯示{“ok” : 1}表示新增成功

在com7中輸入
rs.0:PRIMARY>rs.add(”192.168.1.2:27018”)
rs.0:PRIMARY>rs.add(”192.168.1.2:27019”)
顯示{“ok” : 1}表示新增成功

在com8中輸入
rs.0:PRIMARY>rs.add(”192.168.1.3:27018”)
rs.0:PRIMARY>rs.add(”192.168.1.3:27019”)
顯示{“ok” : 1}表示新增成功

新增成功後可以輸入rs.status()查看Replica Set查看狀態

2. Sharding Cluster設定

2.1. 在com9中啟動config server與mongos
#mongod -f /etc/mongod-configserv.conf
#mongos -f /etc/mongod-routingserv.conf

2.2. 將Replica Set轉為Shard

2.2.1. 進入mongos
#mongo --port 27018

2.2.2. 將Replica Set加入Sharding Cluster中

輸入
mongos>sh.shardAdd("rs0/192.168.1.1:27017,192.168.1.1:27018,192.168.1.1:27019”)
回傳{ "shardAdded" : "rs0", "ok" : 1 }代表成功
mongos>sh.shardAdd("rs1/192.168.1.2:27017,192.168.1.2:27018,192.168.1.2:27019”)
回傳{ "shardAdded" : "rs1", "ok" : 1 }代表成功
mongos>sh.shardAdd("rs2/192.168.1.3:27017,192.168.1.3:27018,192.168.1.3:27019”)
回傳{ "shardAdded" : "rs2", "ok" : 1 }代表成功

新增完成後可以輸入
mongos>sh.status()
檢視sharding cluster狀態

做到這邊有三組ReplicaSet作為Shard的Sharding Cluster就建立成功了!!
(導入資料進入Sharding Cluster時,要導入到mongos中)

肆、測試成果

一、Replica Set測試

1. 檢查否有容錯轉移的功能,以rs0為例

關閉Primary的mongod
#mongod -f /etc/mongod1.conf --shutdown

連入使用原先為Primary的mongod
#mongo --port 27017

出現類似以下訊息表示已經成功關閉
exception: connect failed
此時在連上原先為Secondary的mongod
#mongo --port 27018
可以發現輸入指令的地方從原先的 rs0:SECONDARY> 變為 rs0:PRIMARY>

此時再重新啟動剛剛關閉的mongod
#mongod -f /etc/mongod1.conf

連上該mongod
#mongo --port 27017
可以發現輸入指令的地方從原先的 rs0:Primary> 變為 rs0:Secondary>

此時輸入
rs0:SECONDARY>rs.status()
可以發現使用port27018的mongod的"stateStr"已經由"SECONDARY"變為"PRIMARY"
而可以使用port27017的mongod的"stateStr"已經由"PRIMARY"變為"SECONDARY"

證明這個Replica Set具有容錯轉移功能

二、檢查資料是否會正常複製

1. 下載MongoDB所提供的資料及進行測試
#wget https://raw.githubusercontent.com/mongodb/docs-assets/primer-dataset/primer-dataset.json

2. 將資料集導入mongod中

#mongoimport --port 27017 --db rstest --collection restaurants --file primer-dataset.json
(只能將資料導入PRIMARY中,在這個例子中,PRIMARY還是port27017這個mongod)

3. 進入PRIMARY
#mongo --port27017

4. 進入導入的資料庫
rs1:PRIMARY>use rstest

5. 查看資料表是否有導入成功
rs1:PRIMARY>show collections
有顯示restaurants表示導入成功

6. 查看資料表內容
rs1:PRIMARY>db.restaurants.find()

7.離開mongo shell
方法a: Ctrl+C
方法b: rs1:PRIMARY>exit

7. 看看其他Replica Set中的mongod是否有相同的資料
#mongo --port 27018

8. 查看資料庫
rs1:SECONDARY>show databases

此時會報錯
Error: error: { "ok" : 0, "errmsg" : "not master and slaveOk=false", "code" : 13435 }

原因在此處有詳細的說明
請注意在預設的情況下,即使連結到 Secondary 節點也無法進行查詢的指令,必須透過 rs.slaveOk() 的指令才能宣告此一連線可用來進行查詢,此一設計的目的是為了避免程式誤連到 Secondary 後而不自知。因為 Secondary 節點與 Primary 節點內的資料存在一段的時間差,因此兩邊的資料可能不完全一致,這個現象對於某些應用來說將導致嚴重的不良後果,所以才需透過這種方式來確認程式的意圖。
9. 執行slaveOk
 rs1:SECONDARY>rslaveOk()

10. 此時就可以在Secondary執行與Primary相同的查詢

如果查詢發現結果與Primary相同,表示資料可以成功的自動複製

三、測試Sharding Cluster是否會正確切碎資料

1. 下載免費資料集並解壓縮
#wget http://jsonstudio.com/wp-content/uploads/2014/02/companies.zip
#unzip companies.zip

2. 導入資料到mongos(因為在sharding cluster中,mongos負責傳遞所有的請求)
#mongoimport --host 192.168.1.4 --port 27018 --db shtest --collection companies --file companies.json

3. 進入mongos並執行sh.status()
#mongo --port 27018
mongos>sh.status()
可以看到顯示的信息有這樣的東西
  databases:
        {  "_id" : "shtest",  "primary" : "rs2",  "partitioned" : false }

4. 讓shtest這個資料庫可以被切割
mongos>sh.enableSharding("shtest")
Enables sharding on the specified database. This does not automatically shard any collections but makes it possible to begin sharding collections using sh.shardCollection().
透過官方解釋可以得知,這個指令並不會讓資料庫裡面的資料表被切割,透過這個指令只是允許透過sh.shardCollection()來讓資料表可以開始被切割

5. 建立資料表索引
mongos>db.companies.ensureIndex({_id:"hashed"})
官方說明中寫道db.collection.ensureIndex()在3.0版後已經被較新的db.collection.createIndex()取代,但是使用較新的反而會無法切割成功,這點我還在研究

6. 開始切割
mongos>sh.shardCollection("shtest.companies", { "_id": "hashed" } )

7. 確認碎片狀態
mongos>sh.status()
可以看到以下訊息
 shtest.companies
                        shard key: { "_id" : "hashed" }
                        unique: false
                        balancing: true
                        chunks:
                                rs0     1
                                rs1     1
                                rs2     1
                        { "_id" : { "$minKey" : 1 } } -->> { "_id" : NumberLong("-591762798808544439") } on : rs0 Timestamp(2, 0)
                        { "_id" : NumberLong("-591762798808544439") } -->> { "_id" : NumberLong("7934911588406832349") } on : rs2 Timestamp(3, 0)
                        { "_id" : NumberLong("7934911588406832349") } -->> { "_id" : { "$maxKey" : 1 } } on : rs1 Timestamp(3, 1)
可以看出每一個Replica Set都已經獲得了一份切片,同時還會告訴用戶哪部分的切片在哪裡


伍、MongoDB相關指令


執行mongod
#mongod -f /etc/mongod.conf
關閉 mongod
#mongod -f /etc/mongod.conf --shutdown
執行config server
#mongod -f /etc/mongod-configserv.conf
關閉config server
#mongod -f /etc/mongod-configserv.conf --shutdown
執行 mongos
#mongos -f /etc/mongod-routingserv.conf
關閉 mongos instance
#killall mongos
導入資料表
#mongoimport --port port_number --db database_name --collection collection_name [--drop] --file file_name
(使用--drop的話,如果資料庫中有名稱相同的collection,會先將舊的collection刪除,在導入新的collection)
(導入CSV指定有點不同,可以去查閱官方文件)


陸、Q&A

問題一:

執行sh.addShard()時出現類似的錯誤
{        "ok" : 0,        "errmsg" : "in seed list rs0/192.168.1.1:27017,192.168.1.1:27018,192.168.1.1:27019, host 192.168.1.1:27017 does not belong to replica set rs0; found { hosts: [ \"com6:27017\", \"192.168.1.1:27018\", \"192.168.1.1:27019\" ], setName: \"rs0\", setVersion: 3, ismaster: true, secondary: false, primary: \"com6:27017\", me: \"com6:27017\", electionId: ObjectId('7fffffff0000000000000001'), maxBsonObjectSize: 16777216, maxMessageSizeBytes: 48000000, maxWriteBatchSize: 1000, localTime: new Date(1466274842539), maxWireVersion: 4, minWireVersion: 0, ok: 1.0 }",        "code" : 96}
解決辦法:修改rs0的hostname

1. 進入rs0的Primary並輸入rs.status(),檢查"members"的"name"
                {                        "_id" : 0,                        "name" : "com6:27017",                        "health" : 1,                        "state" : 1,                        "stateStr" : "PRIMARY",                        "uptime" : 103335,                        "optime" : {                                "ts" : Timestamp(1466175445, 12359),                                "t" : NumberLong(1)                        },                        "optimeDate" : ISODate("2016-06-17T14:57:25Z"),                        "electionTime" : Timestamp(1466171888, 2),                        "electionDate" : ISODate("2016-06-17T13:58:08Z"),                        "configVersion" : 3,                        "self" : true                }
如果是像這樣"name":"com6:27017",就要用rs.reconfig()修改"name"

2. 執行以下指令修改"name"
rs0:PRIMARY> cfg=rs.conf()
rs0:PRIMARY> cfg.members[0].host="192.168.1.1:27017"
rs0:PRIMARY> rs.reconfig(cfg)

此時會回傳{ "ok" : 1 }表示修改成功,可以輸入rs.status()檢查成果

此時sh.addShard()應該就可以正常執行

問題二:執行sh.addShard()時出現類似的錯誤

解決辦法一:檢查防火牆設定

解決辦法二:在mongos所在的電腦中的/etc/hosts
例如
192.168.1.1 com6
192.168.1.2 com7
192.168.1.3 com8


問題三:執行rs.initiate()時出現以下錯誤


{ "info2" : "no configuration explicitly specified -- making one", "me" : "0.0.0.0:27017", "errmsg" : "couldn't initiate : can't find self in the replset config", "ok" : 0}
解決辦法:輸入以下指令
>cfg = {"_id" : "rs0","members" : [{"_id" : 0,"host" : "192.168.1.1:27017"}]}
(Primary的資料)
>rs.initiate(cfg)

柒、免費資料集


1. https://raw.githubusercontent.com/mongodb/docs-assets/primer-dataset/primer-dataset.json

2. http://jsonstudio.com/wp-content/uploads/2014/02/companies.zip
(14.8M compressed)

3. http://jsonstudio.com/wp-content/uploads/2014/02/stocks.zip
(1.6M compressed)

4. http://jsonstudio.com/wp-content/uploads/2014/02/enron.zip
(3.9M compressed)

5. http://jsonstudio.com/wp-content/uploads/2014/02/zips.zip
(656K compressed).

6. http://jsonstudio.com/wp-content/uploads/2014/02/world_bank.zip

(436K compressed)