一、准備工作
1.下載安裝vmware,步驟省略。
2.下載CentOS系統ios包:http://isoredirect.centos.org/centos/7/isos/x86_64/CentOS-7-x86_64-Everything-1611.iso
3.下載安裝Xshell5,步驟省略。
4.下載安裝git,步驟省略。
5.zookeeper官網:http://zookeeper.apache.org/
6.zookeeper用於golang的api:https://github.com/samuel/go-zookeeper
7.vmware中依次點擊“創建新的虛擬機”->“典型”->“安裝程序光盤映像文件”選擇上面下載的ios文件,然后一路下一步即可快速安裝CentOS系統。
至於為什么安裝CentOS,其實其他linux版本也可以。安裝完成的CentOS系統如下圖:

二、安裝配置zookeeper
點擊屏幕左上角的Applications,打開Terminal,下面我們把zookeeper下載到/usr/local目錄下,這個目錄可以理解為windows下的C:/Progrem Files/目錄。
首先切換到root權限:
輸入su,回車,看到Password:后輸入密碼,注意密碼不會顯示出來,輸入完畢直接回車就好。
接下來執行命令進入/usr/local目錄:
cd /usr/local
下載 zookeeper-3.5.3-beta.tar.gz:
wget https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.5.3-beta/zookeeper-3.5.3-beta.tar.gz
terminal里會出現下載進度條,進度條讀完后文件下載完畢,我們先將其解壓縮:
tar -xvf zookeeper-3.5.3-beta.tar.gz

接下來我們進入conf目錄進行設置:
cd zookeeper-3.5.3-beta/conf/
復制 zoo_sample.cfg 文件的並命名為為 zoo.cfg:
cp zoo_sample.cfg zoo.cfg
我們編輯zoo.cfg,進行相關設置:
vi zoo.cfg
這個時候如果想修改這個文件的內容,按下間鍵盤上的“i”鍵,最下方就會變成INSERT,就可以修改了
移動光標,將
dataDir=/tmp/zookeeper
改為
dataDir=/usr/local/zookeeper-3.5.3-beta

按Esc退出編輯模式,然后輸入:wq回車進行保存並關閉文件。

下面我們查看一下虛擬機的局域網ip地址:
ifconfig

可以看到我虛擬機的IP地址為:192.168.40.129
這里我們要用到一個軟件xshell5,其實在CentOS的terminal下運行也是一樣的,但是如果zookeeper安裝在服務器端而不是本地虛擬機的話,使用xshell5就會顯得更方便了,所以這里我們使用xshell5。
打開xshell5,登錄我們的虛擬機:

用戶名填root,密碼是虛擬機的密碼。
進入zookeeper的運行目錄:
cd /usr/local/zookeeper-3.5.3-beta/bin
下面我們啟動zookeeper的服務:
./zkServer.sh start

出現STARTED,說明我們的zookeeper已經啟動了,我們可以通過它提供的clint端在命令行下直接進行一些簡易的操作,輸入:
./zkCli.sh
可以看到我們已經進入了zookeeper的clint端。

輸入help,可以查看一些命令行下的命令。

三、golang實現分布式系統的Leader選舉
下面我們利用搭建好的zookeeper,使用golang實現分布式系統的Leader選舉
安裝配置golang環境不是本文討論的重點,這里省略。
我們可以利用git來獲取,git可自行下載安裝,下面是git命令。
查看golang的位置:
which go
設置GOROOT:
export GOROOT=/c/Go
我將golang程序放到了d盤的golang文件夾下,那么設置一下GOPATH:
export GOPATH=/d/golang/
獲取zookeeper對go的api:
go get github.com/samuel/go-zookeeper/zk
下面在d盤golang文件夾下新建一個go項目,寫入如下代碼來模擬Leader選舉的過程:
package main
import (
"github.com/samuel/go-zookeeper/zk"
"errors"
"time"
"fmt"
)
type ZookeeperConfig struct {
Servers []string
RootPath string
MasterPath string
}
type ElectionManager struct {
ZKClientConn *zk.Conn
ZKConfig *ZookeeperConfig
IsMasterQ chan bool
}
func NewElectionManager(zkConfig *ZookeeperConfig, isMasterQ chan bool) *ElectionManager {
electionManager := &ElectionManager{
nil,
zkConfig,
isMasterQ,
}
electionManager.initConnection()
return electionManager
}
func (electionManager *ElectionManager) Run() {
err := electionManager.electMaster()
if err != nil {
fmt.Println("elect master error, ", err)
}
electionManager.watchMaster()
}
// 判斷是否成功連接到zookeeper
func (electionManager *ElectionManager) isConnected() bool {
if electionManager.ZKClientConn == nil {
return false
} else if electionManager.ZKClientConn.State() != zk.StateConnected {
return false
}
return true
}
// 初始化zookeeper連接
func (electionManager *ElectionManager) initConnection() error {
// 連接為空,或連接不成功,獲取zookeeper服務器的連接
if !electionManager.isConnected() {
conn, connChan, err := zk.Connect(electionManager.ZKConfig.Servers, time.Second)
if err != nil {
return err
}
// 等待連接成功
for {
isConnected := false
select {
case connEvent := <-connChan:
if connEvent.State == zk.StateConnected {
isConnected = true
fmt.Println("connect to zookeeper server success!")
}
case _ = <-time.After(time.Second * 3): // 3秒仍未連接成功則返回連接超時
return errors.New("connect to zookeeper server timeout!")
}
if isConnected {
break
}
}
electionManager.ZKClientConn = conn
}
return nil
}
// 選舉master
func (electionManager *ElectionManager) electMaster() error {
err := electionManager.initConnection()
if err != nil {
return err
}
// 判斷zookeeper中是否存在root目錄,不存在則創建該目錄
isExist, _, err := electionManager.ZKClientConn.Exists(electionManager.ZKConfig.RootPath)
if err != nil {
return err
}
if !isExist {
path, err := electionManager.ZKClientConn.Create(electionManager.ZKConfig.RootPath, nil, 0, zk.WorldACL(zk.PermAll))
if err != nil {
return err
}
if electionManager.ZKConfig.RootPath != path {
return errors.New("Create returned different path " + electionManager.ZKConfig.RootPath + " != " + path)
}
}
// 創建用於選舉master的ZNode,該節點為Ephemeral類型,表示客戶端連接斷開后,其創建的節點也會被銷毀
masterPath := electionManager.ZKConfig.RootPath + electionManager.ZKConfig.MasterPath
path, err := electionManager.ZKClientConn.Create(masterPath, nil, zk.FlagEphemeral, zk.WorldACL(zk.PermAll))
if err == nil { // 創建成功表示選舉master成功
if path == masterPath {
fmt.Println("elect master success!")
electionManager.IsMasterQ <- true
} else {
return errors.New("Create returned different path " + masterPath + " != " + path)
}
} else { // 創建失敗表示選舉master失敗
fmt.Printf("elect master failure, ", err)
electionManager.IsMasterQ <- false
}
return nil
}
// 監聽zookeeper中master znode,若被刪除,表示master故障或網絡遲緩,重新選舉
func (electionManager *ElectionManager) watchMaster() {
for {
// watch zk根znode下面的子znode,當有連接斷開時,對應znode被刪除,觸發事件后重新選舉
children, state, childCh, err := electionManager.ZKClientConn.ChildrenW(electionManager.ZKConfig.RootPath + electionManager.ZKConfig.MasterPath)
if err != nil {
fmt.Println("watch children error, ", err)
}
fmt.Println("watch children result, ", children, state)
select {
case childEvent := <-childCh:
if childEvent.Type == zk.EventNodeDeleted {
fmt.Println("receive znode delete event, ", childEvent)
// 重新選舉
fmt.Println("start elect new master ...")
err = electionManager.electMaster()
if err != nil {
fmt.Println("elect new master error, ", err)
}
}
}
}
}
func main() {
// zookeeper配置
zkConfig := &ZookeeperConfig{
Servers: []string{"192.168.40.129"},
RootPath: "/ElectMasterDemo",
MasterPath: "/master",
}
// main goroutine 和 選舉goroutine之間通信的channel,同於返回選角結果
isMasterChan := make(chan bool)
var isMaster bool
// 選舉
electionManager := NewElectionManager(zkConfig, isMasterChan)
go electionManager.Run()
for {
select {
case isMaster = <-isMasterChan:
if isMaster {
// do some job on master
fmt.Println("do some job on master")
}
}
}
}
其中,main函數的ip改寫成上面查到的虛擬機ip。
在Xshell5中輸入:
ls /
我們可以看到目前根目錄下只有一個zookeeper。
要使用golang程序成功調用zookeeper,還需要關掉我們CentOS系統下的防火牆。
在Xshell5中輸入:
systemctl | grep fire
可以看到防火牆正在運行。
我們先停止防火牆:
stop firewalld
然后禁用防火牆:
disable firewalld
保存修改:
iptables-save

成功關掉防火牆之后,運行上述go程序,我們可以看到第一個進程成功成為了leader:
GOROOT=C:\Go
GOPATH=D:/golang
C:\Go\bin\go.exe build -i -o C:\Users\renjiashuo\AppData\Local\Temp\Build_main_go_and_rungo D:/golang/src/leader/main.go
"C:\Program Files\JetBrains\IntelliJ IDEA 2017.1.5\bin\runnerw.exe" C:\Users\renjiashuo\AppData\Local\Temp\Build_main_go_and_rungo
connect to zookeeper server success!
2017/07/27 21:02:38 Connected to 192.168.40.129:2181
2017/07/27 21:02:38 Authenticated: id=72057981844586499, timeout=4000
2017/07/27 21:02:38 Re-submitting `0` credentials after reconnect
elect master success!
do some job on master
watch children result, [] &{9 9 1501160558009 1501160558009 0 0 0 72057981844586499 0 0 9}
不要關閉這個程序,再執行一個此go程序,我們可以看到,因為有第一個進程已經成為了leader,第二個進程只能等待:
GOROOT=C:\Go
GOPATH=D:/golang
C:\Go\bin\go.exe build -i -o C:\Users\renjiashuo\AppData\Local\Temp\Build_main_go_and_run1go D:/golang/src/leader/main.go
"C:\Program Files\JetBrains\IntelliJ IDEA 2017.1.5\bin\runnerw.exe" C:\Users\renjiashuo\AppData\Local\Temp\Build_main_go_and_run1go
connect to zookeeper server success!
2017/07/27 21:05:29 Connected to 192.168.40.129:2181
2017/07/27 21:05:29 Authenticated: id=72057981844586500, timeout=4000
2017/07/27 21:05:29 Re-submitting `0` credentials after reconnect
elect master failure, %!(EXTRA *errors.errorString=zk: node already exists)watch children result, [] &{9 9 1501160558009 1501160558009 0 0 0 72057981844586499 0 0 9}
此時我們關閉第一個進程,可以看到第二個進程的變化:
GOROOT=C:\Go
GOPATH=D:/golang
C:\Go\bin\go.exe build -i -o C:\Users\renjiashuo\AppData\Local\Temp\Build_main_go_and_run1go D:/golang/src/leader/main.go
"C:\Program Files\JetBrains\IntelliJ IDEA 2017.1.5\bin\runnerw.exe" C:\Users\renjiashuo\AppData\Local\Temp\Build_main_go_and_run1go
connect to zookeeper server success!
2017/07/27 21:05:29 Connected to 192.168.40.129:2181
2017/07/27 21:05:29 Authenticated: id=72057981844586500, timeout=4000
2017/07/27 21:05:29 Re-submitting `0` credentials after reconnect
elect master failure, %!(EXTRA *errors.errorString=zk: node already exists)watch children result, [] &{9 9 1501160558009 1501160558009 0 0 0 72057981844586499 0 0 9}
receive znode delete event, {EventNodeDeleted Unknown /ElectMasterDemo/master <nil> }
start elect new master ...
connect to zookeeper server success!
2017/07/27 21:06:28 Connected to 192.168.40.129:2181
2017/07/27 21:06:28 Authenticated: id=72057981844586501, timeout=4000
2017/07/27 21:06:28 Re-submitting `0` credentials after reconnect
elect master success!
do some job on master
watch children result, [] &{14 14 1501160787685 1501160787685 0 0 0 72057981844586501 0 0 14}
第二個進程成功成為了leader。
程序代碼的注釋比較詳細,這里不做詳解,所有調用的api接口都可以進入查看其go源碼。
自此,CentOS環境安裝zookeeper服務並用golang實現分布式系統的Leader選舉實現完畢。
