zoukankan      html  css  js  c++  java
  • 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选举的过程:

    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:Goingo.exe build -i -o C:Users
    enjiashuoAppDataLocalTempBuild_main_go_and_rungo D:/golang/src/leader/main.go
    "C:Program FilesJetBrainsIntelliJ IDEA 2017.1.5in
    unnerw.exe" C:Users
    enjiashuoAppDataLocalTempBuild_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:Goingo.exe build -i -o C:Users
    enjiashuoAppDataLocalTempBuild_main_go_and_run1go D:/golang/src/leader/main.go
    "C:Program FilesJetBrainsIntelliJ IDEA 2017.1.5in
    unnerw.exe" C:Users
    enjiashuoAppDataLocalTempBuild_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:Goingo.exe build -i -o C:Users
    enjiashuoAppDataLocalTempBuild_main_go_and_run1go D:/golang/src/leader/main.go
    "C:Program FilesJetBrainsIntelliJ IDEA 2017.1.5in
    unnerw.exe" C:Users
    enjiashuoAppDataLocalTempBuild_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选举实现完毕。

  • 相关阅读:
    jQuery火箭图标返回顶部代码
    网站开发之免费的图标库——iconfont
    网站开发之免费的图片库——undraw
    在webpack中使用echarts
    WeUI+的使用
    微信小程序引用自定义组件
    显示字符串中间加星号
    解决history的方法执行后不刷新页面的问题
    阻止input输入框弹出输入法
    使用taro框架开发小程序
  • 原文地址:https://www.cnblogs.com/renjiashuo/p/7247388.html
Copyright © 2011-2022 走看看