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选举实现完毕。
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步