0


zookeeper的介绍和用docker搭建zookeeper集群,以及Go语言使用zookeeper


typora-copy-images-to: imgs


Zookeeper的使用

1、Zookeeper简介

 Apache ZooKeeper 是 Apache 软件基金会的一个软件项目,为大型分布式系统提供开源分布式配置服务、同步服务和命名注册。ZooKeeper原本是Hadoop的一个子项目,但现在它本身已经是一个顶级项目了。

 zookeeper是经典的分布式数据一致性解决方案,致力于为分布式应用提供一个高性能,高可用,且具有严格顺序访问控制能力的分布式协调存储服务。 

2、使用Docker快速部署zookeeper

2.1、Docker官方镜像

Docker Zookeeper

2.2、Docker安装zookeeper

下载zookeeper最新版的镜像

docker search zookeeper 
docker pull zookeeper
docker images
docker inspect zookeeper 

docker inspect zookeeper用来查看zookeeper的详细信息

在/root/docker/目录下新建一个zookeeper挂载点文件夹

mkdir /root/docker/zookeeper

挂载本地文件夹并启动服务

docker run -e TZ="Asia/Shanghai" -d -p 2181:2181 -v /root/docker/zookeeper:/data --name zookeeper --restart always zookeeper

参数解释

-e TZ="Asia/Shanghai" # 指定上海时区
-d # 指示后台运行容器
-p 2181:2181 # 端口映射,前面的端口为本地的2181端口,后者为容器内的端口
--name # 设置创建的容器的名称
-v # 挂在文件,将本地目录或文件挂在到容器指定目录
--restart always # 始终重新启动zookeeper

2.3、进入zookeeper容器客户端

方式一

docker run -it --rm --link zookeeper:zookeeper zookeeper zkCli.sh -server zookeeper       

运行上诉命令后会进入到zkCli

在这里插入图片描述

方式二

前台进入zookeeper容器执行脚本新建一个Client

dockerexec -it zookeeper bash   //进入zookeeper容器,退出时不会关闭容器
 ./bin/zkCli.sh        //执行脚本新建一个Client    

3、docker构建zookeeper集群

3.1、创建docker-compose.yml文件

cd /root/docker/docker-compose/zookeeper # 进入你想要存放docker-compose.yml的文件
vim docker-compose.yml        # 把下面的代码复制到docker-compose.yml文件中

version: 'latest'
services:
  zoo1:
    image: zookeeper
    restart: always
    hostname: zoo1
    container_name: zoo1
    ports:
      - 2181:2181
    environment:
      ZOO_MY_ID: 1
      ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
    volumes:
      - /root/docker/docker-compose/zoo1/data:/data
      - /root/docker/docker-compose/zoo1/datalog:/datalog

  zoo2:
    image: zookeeper
    restart: always
    hostname: zoo2
    container_name: zoo2
    ports:
      - 2182:2181
    environment:
      ZOO_MY_ID: 2
      ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
    volumes:
      - /root/docker/docker-compose/zoo2/data:/data
      - /root/docker/docker-compose/zoo2/datalog:/datalog

  zoo3:
    image: zookeeper
    restart: always
    hostname: zoo3
    container_name: zoo3
    ports:
      - 2183:2181
    environment:
      ZOO_MY_ID: 3
      ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
    volumes:
      - /root/docker/docker-compose/zoo3/data:/data
      - /root/docker/docker-compose/zoo3/datalog:/datalog

volumes表示的是文件映射要根据自己的情况进行修改

3.2、执行构建集群命令

3.2.1、安装Docker Compose(Linux下)

我们先要搭建以下docker-compose的环境,执行以下命令下载Docker Compose,要更改版本的话替换v2.2.2就好

sudocurl -L "https://github.com/docker/compose/releases/download/v2.2.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

赋予可执行权限

sudochmod +x /usr/local/bin/docker-compose

创建软链接

sudoln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

3.2.2、启动docker-compose.yml文件

在执行命令之前要保证2181-2183端口没有被占用

cd /root/docker/docker-compose/zookeeper  # 到docker-compose.yml所在的目录docker-compose up -d

3.2.3、验证集群是否搭建成功

dockerexec -it zoo1 bash
zkServer.sh status

看到如下信息表示集群已经搭建成功,可以看出zoo1的角色是follower,查看其他的会发现zoo2是follower,zoo3是leader。

在这里插入图片描述

4、Zookeeper的简单使用

3.1、Go语言与Zookeeper服务端建立连接

3.1.1、建立连接的代码

package main

import("fmt""log""time""github.com/samuel/go-zookeeper/zk")//打印节点状态信息的函数funcStatePrintf(state *zk.Stat){
    fmt.Println("State->")
    fmt.Printf("Czxid = %v,\nMzxid = %v,\nCtime = %v,\nMtime = %v,\nVersion = %v,\nCversion = %v,\nAversion = %v,\nEphemeralOwner = %v,\nDataLength = %v,\nNumChildren = %v,\nPzxid = %v,\n",
        state.Czxid,//创建该节点的zxid
        state.Mzxid,//最后一个修改该节点的zxid
        state.Ctime,//创建该节点的时间
        state.Mtime,//最后一次修改该节点的时间
        state.Version,//修改该节点数据的次数
        state.Cversion,//修改该节点儿子节点的次数
        state.Aversion,//修改ACL的次数
        state.EphemeralOwner,//创建该临时节点的会话id
        state.DataLength,//节点数据的长度
        state.NumChildren,//该节点的儿子节点的数量
        state.Pzxid,//最后一个修改的儿子节点的zxid(当创建或者删除子节点时才会改变))}//输出错误信息的函数funcFailOnError(msg string, err error){if err !=nil{
        log.Fatalf("%v : %v\n", msg, err)}}funcmain(){
    hosts :=[]string{"192.168.17.95:2181"}
    conn,_, err := zk.Connect(hosts, time.Second*5)if err !=nil{
        fmt.Println("Failed to Connected to zookeeper")}CreateNode(conn)SetTest(conn)GetTest(conn)DeleteTest(conn)//休眠5分钟再关闭,以便看到创建的临时节点
    time.Sleep(5* time.Minute)defer conn.Close()}

3.1.2、关于zxid的一些解释

Zookeeper中的**zxid**是一个长为64位的数字。高32位用来表示当前Leader的周期,低32位用来表示当前请求产生的事务在当前Leader周期内的顺序。每产生一个新的事务,zxid的低32位就会自动加1。当zxid达到最大值,即zxid的低32位达到`0xffffffff`,就会触发集群强制选主,Leader变更后高32位都会自增1,并重置zxid低32位的计数值(zxid高32位变为新Leader的周期,低32位变为0)。

如果一个zookeeper集群每秒能操作10000次,即10k/s ops,那么
    2^32/(86400*10000)≈4.97天

也就是说4.97天之后就会进行自动切主的操作,对于一些服务来说平均五天切一次主是难以容许的,我们可以重新设计zxid,增加低位技术的位数到自己需要的值,假设64位全部用做低位计数。

    2^64/(86400*10000)≈21350398233.46天,即58494241.73年

一般来说集群不可能可靠的运行这么多年,所以重新设计zxid还是要根据业务需求来进行。

3.2、创建节点

3.2.1、zkCli操作

create /节点路径 value  # 可以在创建节点的同时设置节点的值,创建的节点是持久化的节点
create -e /节点路径 value  # 创建临时节点,在客户端断开后会自动删除的节点
create -s /节点路径 value  # 创建顺序节点,zookeeper会自动在节点路径后面加顺序递增的编号

直接创建节点

在这里插入图片描述

临时节点,顺序节点,quit退出之后再进入刚创建的temp节点会消失。

在这里插入图片描述

3.2.2、Go语言API操作

// 创建节点funcCreateNode(conn *zk.Conn){//创建永久节点
    path, err := conn.Create("/app3",[]byte("zhangsan"),0, zk.WorldACL(zk.PermAll))FailOnError("Failed to Create node", err)
    fmt.Printf("Created node path[%v]\n", path)//创建临时节点,在会话结束时会自动删除临时节点
    ephemeral, err := conn.Create("/ephemeral",nil, zk.FlagEphemeral, zk.WorldACL(zk.PermAll))FailOnError("Failed to Create ephemeral node", err)
    fmt.Printf("Created ephemeral node path[%v]\n", ephemeral)//创建顺序节点
    sequence, err := conn.Create("/sequence",nil, zk.FlagSequence, zk.WorldACL(zk.PermAll))FailOnError("Failed to Create sequence node", err)
    fmt.Printf("Created sequence node path[%v]\n", sequence)//创建临时顺序节点 create -es /ephemeralsequece
    ephemeralsequece, err := conn.Create("/ephemeralsequece",nil, zk.FlagEphemeral|zk.FlagSequence, zk.WorldACL(zk.PermAll))FailOnError("Failed to Create ephemeralsequece node", err)
    fmt.Printf("Created ephemeralsequece node path[%v]\n", ephemeralsequece)}

3.3、修改节点

3.3.1、zkCli操作

set /节点路径 value

示例如下

在这里插入图片描述

3.3.2、Go语言API操作

// Set操作funcSetTest(conn *zk.Conn){//获取节点的version信息_, state,_:= conn.Get("/app3")//set /app3 lisi
    state, err := conn.Set("/app3",[]byte("lisi"), state.Version)FailOnError("Failed to Set Value", err)StatePrintf(state)//获取修改后的值
    value,_, err := conn.Get("/app3")FailOnError("Failed to Get New value", err)
    fmt.Println("New Value = ", value)}

3.4、查询节点

3.4.1、zkCli操作

ls 目录   # 查看目录下的所有子节点 
get /节点路径
ls -s 目录   # 查看目录的所有详细信息

示例如下

在这里插入图片描述

在这里插入图片描述

3.4.2、Go语言API操作

// 查询节点funcGetTest(conn *zk.Conn){
    result, state, err := conn.Get("/app3")//获取子节点//children,state,err:=conn.Children("/app3")FailOnError("Failed to Get Node Info", err)
    fmt.Printf("result:[%v]\n",string(result))StatePrintf(state)}

3.5、删除节点

3.5.1、zkCli操作

delete /节点路径   # 删除单个节点
deleteall /节点路径     # 删除带有子节点的节点

示例如下

在这里插入图片描述

3.5.2、Go语言API操作

// 删除节点funcDeleteTest(conn *zk.Conn){
    path :="/app3"//先判断节点存不存在
    exists, state,_:= conn.Exists(path)
    fmt.Printf("path[%s] exists:%v\n", path, exists)//删除节点
    err := conn.Delete("/app3", state.Version)FailOnError("Failed to Delete node", err)
    fmt.Printf("path[%s] is deleted.", path)

    exists,_,_= conn.Exists(path)
    fmt.Printf("path[%s] exists: %v\n", path, exists)}

5、go-zookeeper权限(ACL)

zookeeper的节点有五种权限:Create、Read、Write、Delete、Admin。

ACL权限由

schema:id:permissions

组成

schema

有四种方式

  • world
  • auth
  • digest
  • ip

下面对这四种方式都测试一遍

4.1、world

默认方式,相当于全世界都能访问。

/app3

节点的权限修改为

crwa

后尝试删除其子节点

/p1

package main

import("fmt""log""time""github.com/go-zookeeper/zk")funcFailOnError(msg string, err error){if err !=nil{
        log.Fatalf("%v : %v\n", msg, err)}}funcmain(){
    hosts :=[]string{"192.168.17.95:2181"}
    conn,_, err := zk.Connect(hosts, time.Second*5)FailOnError("Failed to Connected to zookeeper", err)defer conn.Close()//获取/app3节点的acl信息
    acl, state, err := conn.GetACL("/app3")FailOnError("Failed to GetACL", err)
    fmt.Println("\nget acl:")
    fmt.Println("scheme =", acl[0].Scheme)
    fmt.Println("id =", acl[0].ID)
    fmt.Println("permissions =", acl[0].Perms)//修改/app3节点的权限修改为crwa
    perms := zk.PermCreate | zk.PermRead | zk.PermWrite | zk.PermAdmin
    _, err = conn.SetACL("/app3", zk.WorldACL(int32(perms)), state.Aversion)FailOnError("Failed to SetACL", err)
    fmt.Println("SetAcl successful.")//create child node_, err = conn.Create("/app3/p1",nil,0, zk.WorldACL(zk.PermAll))FailOnError("Failed to Create node", err)//get state of child node_, state, err = conn.Get("/app3/p1")FailOnError("Failed to Get node info", err)//delete /app3/p1
    err = conn.Delete("/app3/p1", state.Version)FailOnError("Failed to Delete Node", err)}

测试结果如下:因为我们没有赋予/app3节点Delete权限,即使子结点/p1赋予了全部权限也不能删除该子节点。

在这里插入图片描述

4.2、auth

auth 用来授予用户权限,所以需要先创建用户。

package main

import("fmt""log""time""github.com/go-zookeeper/zk")funcFailOnError(msg string, err error){if err !=nil{
        log.Fatalf("%v : %v\n", msg, err)}}funcmain(){
    hosts :=[]string{"192.168.17.95:2181"}
    conn,_, err := zk.Connect(hosts, time.Second*5)FailOnError("Failed to Connected to zookeeper", err)defer conn.Close()//获取/auth节点的状态信息_, state, err := conn.Get("/auth")FailOnError("Failed to Get node info", err)//用户授权,用户不存在的话会新建
    err = conn.AddAuth("digest",[]byte("user1:123456"))FailOnError("Failed to AddAuth", err)

    acl := zk.ACL{
        Scheme:"auth",
        Perms:  zk.PermAll,
        ID:"user1:123456",}//为用户授权_, err = conn.SetACL("/auth",[]zk.ACL{acl}, state.Version)FailOnError("Failed to SetACL", err)
    fmt.Println("AddAuthSuccess")}

在这里插入图片描述

授权成功之后如果在其他连接中要查询节点信息要先验证用户信息才能进入下一步操作,也就是把conn.AddAuth操作提前,如果使用不正确的用户名和密码,得到的会是同样的用户认证失败的结果。

package main

import("fmt""log""time""github.com/go-zookeeper/zk")funcFailOnError(msg string, err error){if err !=nil{
        log.Fatalf("%v : %v\n", msg, err)}}funcmain(){
    hosts :=[]string{"192.168.17.95:2181"}
    conn,_, err := zk.Connect(hosts, time.Second*5)FailOnError("Failed to Connected to zookeeper", err)defer conn.Close()//先进行访问/auth节点的话会报错_,_, err = conn.Get("/auth")if err !=nil{
        fmt.Println("Get Node info error:", err)}//要先进行AddAuth操作
    err = conn.AddAuth("digest",[]byte("user1:123456"))FailOnError("Failed to Add Auth", err)//再获取/auth节点的信息就不会报错了
    acl,_, err := conn.GetACL("/auth")FailOnError("Failed to Get node info", err)
    fmt.Println("acl 信息:")for i :=0; i <len(acl); i++{
        fmt.Println("scheme =", acl[0].Scheme)
        fmt.Println("id =", acl[0].ID)
        fmt.Println("permissions =", acl[0].Perms)}}

从测试结果来看在未进行AddAuth操作时我们是获取不到/auth节点信息的,节点的密码返回的是加密后的密码。

在这里插入图片描述

4.3、digest

digest

auth

基本相同,唯一的区别在于设置权限时,密码需要使用密文。

zk golang 库中有专为

digest

构造的方法:

zk.DigestACL(perms int32, user, password string)

此方法传入的密码需要是明文,其内部逻辑会将明文转为密文再向 zookeeper 传递。

使用示例:

package main

import("fmt""log""time""github.com/go-zookeeper/zk")funcFailOnError(msg string, err error){if err !=nil{
        log.Fatalf("%v : %v\n", msg, err)}}funcmain(){
    hosts :=[]string{"192.168.17.95:2181"}
    conn,_, err := zk.Connect(hosts, time.Second*5)FailOnError("Failed to Connected to zookeeper", err)defer conn.Close()//获取/digest_, state, err := conn.Get("/digest")FailOnError("Failed to Get node info", err)//用户授权,用户不存在的话会新建
    err = conn.AddAuth("digest",[]byte("user1:123456"))FailOnError("Failed to AddAuth", err)//zk.DigestACL会将传入的明文转换成密文acl
    acl := zk.DigestACL(zk.PermAll,"user1","123456")//为用户授权_, err = conn.SetACL("/digest", acl, state.Version)FailOnError("Failed to SetACL", err)
    fmt.Println("节点[/digest]已对用户 user1 授权")}

4.4、ip

ip 权限顾名思义,就是限制 ip 地址的访问权限。

把节点的权限设置给指定的 ip 地址后,其他 ip 将无法访问该节点。

package main

import("fmt""log""time""github.com/go-zookeeper/zk")funcFailOnError(msg string, err error){if err !=nil{
        log.Fatalf("%v : %v\n", msg, err)}}funcmain(){
    hosts :=[]string{"192.168.17.95:2181"}
    conn,_, err := zk.Connect(hosts, time.Second*5)FailOnError("Failed to Connected to zookeeper", err)defer conn.Close()//获取/ip节点的状态信息_, state, err := conn.Get("/ip")FailOnError("Failed to Get node info", err)

    acl := zk.ACL{
        Scheme:"ip",
        Perms:  zk.PermAll,
        ID:"192.168.17.1",}//为用户授权_, err = conn.SetACL("/ip",[]zk.ACL{acl}, state.Aversion)FailOnError("Failed to SetACL", err)
    fmt.Println("节点[/ip]已对用户 192.168.17.1 授权")//获取以下节点的acl权限
    acls,_, err := conn.GetACL("/ip")FailOnError("Failed to Get node info", err)
    fmt.Println("acl 信息:")for i :=0; i <len(acls); i++{
        fmt.Println("scheme =", acls[0].Scheme)
        fmt.Println("id =", acls[0].ID)
        fmt.Println("permissions =", acls[0].Perms)}}

这里我用的是VMware的虚拟机的zookeeper然后用本地Windows去连接zookeeper发送的消息这个过程会经过虚拟路由转发,所以这里授权的ip地址是VMware虚拟网卡的地址,不然的话会报没有权限的错误。

在这里插入图片描述

在这里插入图片描述

6、watch机制

5.1、watch的事件类型

watch 用来实现发布/订阅功能,能够让多个订阅者同时监听某一个主题对象,当这个主题对象自身状态发生变化时,会通知所有订阅者。

每个 watch 仅有一次触发的机会,一旦触发会立即失效,想要持续监听,就需要一直注册。

go-zookeeper监听的事件类型分为五种:

  • zk.EventNodeCreated: 节点创建事件,需要watch一个不存在的节点,当节点被创建时触发,此watch通过conn.ExistsW(path string)设置
  • zk. EventNodeDeleted :节点删除事件,需要watch一个已存在的节点,当节点被移除时触发,此watch通过conn.ExistsW(path string)设置
  • zk. EventNodeDataChanged: 节点数据变化事件,此watch通过conn.GetW(path string) 以及 conn.ExistsW(path string) 设置
  • zk. EventNodeChildrenChanged : 子节点改变事件(数量改变),此watch通过conn.ChildrenW(path string)设置, 当path 下面增删子节点时触发(修改path下的子节点的内容时,不会触发通知)。
  • zk.EventNoWatching: watch移除事件,服务端出于某些原因不再为客户端watch节点时触发。

5.2、监听的方式

方式一、全局监听

全局监听的方式会在有监听事件发生时会执行监听器的回调函数

package main

import("fmt""log""time""github.com/samuel/go-zookeeper/zk")funcmain(){
    callbackOption := zk.WithEventCallback(callback)
    hosts :=[]string{"192.168.17.95:2181"}
    conn,_, err := zk.Connect(hosts, time.Second*5, callbackOption)if err !=nil{
        fmt.Println("Failed to Connected to zookeeper")}//注册一个监听事件
    exists, state,_, err := conn.ExistsW("/global")if err !=nil{
        log.Println(err)}//如果节点不存在则创建if!exists {//创建一个临时的global节点_, err = conn.Create("/global",[]byte("globaltest"), zk.FlagEphemeral, zk.WorldACL(zk.PermAll))if err !=nil{
            log.Println(err)}//在注册一个监听事件监听/global节点的删除_, state,_, err = conn.ExistsW("/global")if err !=nil{
            log.Println(err)}}
    err = conn.Delete("/global", state.Version)if err !=nil{
        log.Println(err)}defer conn.Close()}// 监听事件的回调函数funccallback(event zk.Event){
    fmt.Println("###########################")
    fmt.Println("path: ", event.Path)
    fmt.Println("type: ", event.Type.String())
    fmt.Println("state: ", event.State.String())
    fmt.Println("---------------------------")}

在这里插入图片描述

测试结果

在这里插入图片描述

方式二、局部监听

package main

import("fmt""log""time""github.com/samuel/go-zookeeper/zk")// 输出错误信息的函数funcFailOnError(msg string, err error){if err !=nil{
        log.Printf("%v : %v\n", msg, err)}}funcmain(){
    hosts :=[]string{"192.168.17.95:2181"}
    conn,_, err := zk.Connect(hosts, time.Second*5)if err !=nil{
        fmt.Println("Failed to Connected to zookeeper")}//先等待连接上再启动监听携程
    time.Sleep(5* time.Second)gowatchNodeCreated("/partial", conn)gowatchNodeDataChanged("/partial", conn)gowatchNodeChildrenChanged("/partial", conn)gowatchNodeDeleted("/partial", conn)defer conn.Close()//等待操作结束
    time.Sleep(1* time.Hour)}// 监听节点的创建事件funcwatchNodeCreated(path string, conn *zk.Conn){
    log.Printf("watching node[%v] Create\n", path)for{//利用channel通信机制将Event数据传递给ch//ch:=make(chan Event)_,_, ch, err := conn.ExistsW(path)//当err为nil时ch才会有数据否者会阻塞协程if err ==nil{
            e :=<-ch
            if e.Type == zk.EventNodeCreated {
                log.Printf("Node[%v] Created\n", path)}}else{FailOnError("Failed to watchNodeCreated", err)}}}// 监听节点数据修改事件funcwatchNodeDataChanged(path string, conn *zk.Conn){
    log.Printf("watching node[%v] Data Change\n", path)for{_,_, ch, err := conn.GetW(path)if err ==nil{
            e :=<-ch
            if e.Type == zk.EventNodeDataChanged {
                log.Printf("Node[%v] Data Changed", path)}}}}// 监听节点子节点的修改事件funcwatchNodeChildrenChanged(path string, conn *zk.Conn){
    log.Printf("watching node[%v] Children Change", path)for{_,_, ch, err := conn.ChildrenW(path)if err ==nil{
            e :=<-ch
            if e.Type == zk.EventNodeChildrenChanged {
                log.Printf("Node[%v] Children Changed", path)}}}}// 监听节点的删除事件funcwatchNodeDeleted(path string, conn *zk.Conn){
    log.Printf("watching node[%v] Delete", path)for{_,_, ch, err := conn.ExistsW(path)if err ==nil{
            e :=<-ch
            if e.Type == zk.EventNodeDeleted {
                log.Printf("Node[%v] Deleted", path)}}else{FailOnError("Failed to watchNodeDeleted", err)}}}

启动程序之后在虚拟机上运行客户端程序执行以下命令

在这里插入图片描述

程序会输出以下结果

在这里插入图片描述

7、go-zookeeper实现分布式锁

zookeeper的分布式锁可以利用每个节点的唯一性来完成,但所有服务监听一个节点对于分布式系统来说完全是资源浪费。而zookeeper可以利用临时顺序节点来创建一个有序的临时节点列表来完成分布式锁:
  1. 客户端获取锁时,在lock节点下创建临时顺序节点。
  2. 然后获取lock下面的所有子节点,客户端获取到所有的子节点之后,如果发现自己创建的子节点序号最小,那么就认为该客户端获取到了锁。使用完锁之后,将该节点删除。
  3. 如果发现自己创建的子节点并非所有子节点中最小的,说明自己没有获取到锁,此时客户端需要找到比自己小的那个节点,同时对其注册事件监听器,监听删除事件。
  4. 如果发现比自己小的那个节点删除,则客户端的Watcher会收到相应的同支,此时再次判断自己创建的节点是否时lock子节点中序号最小的,如果是则获取到锁,如果不是则重复以上步骤继续获取到比自己小的一个节点并注册监听。

假如服务 A 创建了节点 a,此时节点 a 的前面没有节点,所以服务 A 可以执行。此时服务 B 创建了节点 b,节点 b 是节点 a 的下一个节点,那么服务 B 只需要监听节点 a 即可。

也就是说,因为临时有序节点列表是有序的,所以每个服务只需要监听自己创建的节点的前一个节点即可。

我们利用golang的goroutine来模拟客户端实现分布式锁的过程,以下是50个goroutine进行抢锁的示例:
package main

import("fmt""sync""time""github.com/go-zookeeper/zk")funcmain(){
    hosts :=[]string{"192.168.17.95:2181"}
    conn,_, err := zk.Connect(hosts, time.Second*5)if err !=nil{
        fmt.Println("Failed to Connected to zookeeper")}var wg sync.WaitGroup

    for i :=0; i <50; i++{
        wg.Add(1)gofunc(n int){defer wg.Done()//新建一个锁
            lock := zk.NewLock(conn,"/root/lock", zk.WorldACL(zk.PermAll))//加锁
            err = lock.LockWithData([]byte("it is a lock"))if err !=nil{panic(err)}
            fmt.Println("第", n,"个 goroutine 获取到了锁")
            time.Sleep(time.Second)// 1 秒后释放锁//解锁
            lock.Unlock()}(i)}//等待协程运行结束
    wg.Wait()}

运行结果如下(只截取了一部分)

在这里插入图片描述

标签: docker golang zookeeper

本文转载自: https://blog.csdn.net/m0_52530105/article/details/131318761
版权归原作者 明明就他比较温柔 所有, 如有侵权,请联系我们删除。

“zookeeper的介绍和用docker搭建zookeeper集群,以及Go语言使用zookeeper”的评论:

还没有评论