CentOS环境安装zookeeper服务并用golang实现分布式系统的Leader选举

一、准备工作

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选举的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
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选举实现完毕。

posted @   任家硕  阅读(3792)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示