基本使用
连接至zookeeper
/* 获取一个zk连接
* @return {[type]}
*/
func getConnect(zkList []string) (conn *zk.Conn) {
conn, _, err := zk.Connect(zkList, 10*time.Second)
if err != nil {
fmt.Println(err)
}
return
}
新增
/* 测试连接
* @return
*/
func test1() {
zkList := []string{"localhost:2181"}
conn := getConnect(zkList)
defer conn.Close()
var flags int32 = 0
//flags有4种取值:
//0:永久,除非手动删除
//zk.FlagEphemeral = 1:短暂,session断开则改节点也被删除
//zk.FlagSequence = 2:会自动在节点后面添加序号
//3:Ephemeral和Sequence,即,短暂且自动添加序号
conn.Create("/go_servers", nil, flags, zk.WorldACL(zk.PermAll)) // zk.WorldACL(zk.PermAll)控制访问权限模式
time.Sleep(20 * time.Second)
}
注册方式
var flags int32 = 0
flags有4种取值:
- 0:永久,除非手动删除
- zk.FlagEphemeral = 1:短暂,session断开则改节点也被删除
- zk.FlagSequence = 2:会自动在节点后面添加序号
- 3:Ephemeral和Sequence,即,短暂且自动添加序号
conn.Create(“/go_servers”, nil, flags, zk.WorldACL(zk.PermAll)) // zk.WorldACL(zk.PermAll)控制访问权限模式
更改和删除
//删改与增不同在于其函数中的version参数,其中version是用于 CAS支持
func (c *Conn) Set(path string, data []byte, version int32) (*Stat, error)
func (c *Conn) Delete(path string, version int32) error
demo:
if err = conn.Delete(migrateLockPath, -1); err != nil {
log.Error("conn.Delete(\"%s\") error(%v)", migrateLockPath, err)
}
func main() {
// 连接到 ZooKeeper
servers := []string{"127.0.0.1:2181"} // 这里需要替换为你的 ZooKeeper 实例的地址
conn, _, err := zk.Connect(servers, time.Second) // 连接超时时间
if err != nil {
log.Fatalf("Failed to connect to ZooKeeper: %v", err)
}
defer conn.Close()
// ZooKeeper 操作
path := "/example/message" // 在 ZooKeeper 中的路径
data := []byte("Hello ZooKeeper!") // 要保存的消息内容
// 创建节点并保存消息
flags := int32(0) // 标记:0 表示持久节点,zk.FlagEphemeral 表示临时节点
acl := zk.WorldACL(zk.PermAll) // 权限:全世界可读写
createdPath, err := conn.Create(path, data, flags, acl)
if err != nil {
log.Fatalf("Failed to create node: %v", err)
}
fmt.Printf("Node created: %s\n", createdPath)
// 读取节点数据
storedData, _, err := conn.Get(path)
if err != nil {
log.Fatalf("Failed to get node data: %v", err)
}
fmt.Printf("Stored message: %s\n", string(storedData))
}
监听key的变化
可以通过设置“监听器”(watcher)来监听某个节点(key)的变化。当节点的数据发生变化、节点被删除或子节点发生变化时,监听器会被触发
package main
import (
"github.com/samuel/go-zookeeper/zk"
)
func main() {
// 连接到 ZooKeeper
servers := []string{"127.0.0.1:2181"} // 替换为你的 ZooKeeper 实例的地址
conn, _, err := zk.Connect(servers, time.Second)
if err != nil {
log.Fatalf("Failed to connect to ZooKeeper: %v", err)
}
defer conn.Close()
// 要监听的节点路径
path := "/example/message"
// 获取节点数据并设置监听器
data, _, ch, err := conn.GetW(path)
if err != nil {
log.Fatalf("Failed to get data for node: %v", err)
}
fmt.Printf("Current data: %s\n", string(data))
// 等待事件发生
for {
select {
case event := <-ch:
if event.Type == zk.EventNodeDataChanged {
fmt.Println("Node data changed, fetching new data...")
// 获取新数据
newData, _, newCh, err := conn.GetW(path)
if err != nil {
log.Fatalf("Failed to get updated data for node: %v", err)
}
fmt.Printf("Updated data: %s\n", string(newData))
// 更新监听通道
ch = newCh
} else if event.Type == zk.EventNodeDeleted {
fmt.Println("Node deleted")
return
}
}
}
}
获取所有节点
func test3() {
zkList := []string{"localhost:2181"}
conn := getConnect(zkList)
defer conn.Close()
children, _, err := conn.Children("/go_servers")
if err != nil {
fmt.Println(err)
}
fmt.Printf("%v \n", children)
}
实现分布式锁node
以下是一个简单的示例,展示如何使用 ZooKeeper 实现分布式锁:
go复制代码package main
import (
"fmt"
"log"
"time"
"github.com/samuel/go-zookeeper/zk"
)
type DistributedLock struct {
conn *zk.Conn
lockPath string
lockNode string
ephemeral bool
}
// 创建一个新的分布式锁
func NewDistributedLock(conn *zk.Conn, lockPath string) *DistributedLock {
return &DistributedLock{
conn: conn,
lockPath: lockPath,
}
}
// 获取锁
func (dl *DistributedLock) Lock() error {
// 创建持久顺序节点
nodePath := fmt.Sprintf("%s/lock_", dl.lockPath)
node, err := dl.conn.CreateProtectedEphemeralSequential(nodePath, []byte{}, zk.WorldACL(zk.PermAll))
if err != nil {
return fmt.Errorf("failed to create lock node: %v", err)
}
dl.lockNode = node
// 获取当前锁节点下的所有子节点
children, _, err := dl.conn.Children(dl.lockPath)
if err != nil {
return fmt.Errorf("failed to get children: %v", err)
}
// 获取序号最小的节点
minNode := node
for _, child := range children {
if child < minNode {
minNode = child
}
}
// 如果当前节点是序号最小的节点,获取锁
if node == minNode {
fmt.Println("Lock acquired!")
return nil
}
// 如果不是最小节点,等待前一个节点的删除
prevNode := ""
for _, child := range children {
if child < node && (prevNode == "" || child > prevNode) {
prevNode = child
}
}
// 监听前一个节点的删除事件
prevNodePath := fmt.Sprintf("%s/%s", dl.lockPath, prevNode)
_, _, ch, err := dl.conn.ExistsW(prevNodePath)
if err != nil {
return fmt.Errorf("failed to set watch on previous node: %v", err)
}
// 等待前一个节点被删除
event := <-ch
if event.Type == zk.EventNodeDeleted {
fmt.Println("Lock acquired!")
return nil
}
return fmt.Errorf("failed to acquire lock")
}
// 释放锁
func (dl *DistributedLock) Unlock() error {
if dl.lockNode == "" {
return fmt.Errorf("lock not acquired")
}
err := dl.conn.Delete(dl.lockNode, -1)
if err != nil {
return fmt.Errorf("failed to release lock: %v", err)
}
fmt.Println("Lock released!")
return nil
}
func main() {
servers := []string{"127.0.0.1:2181"} // 替换为你的 ZooKeeper 实例的地址
conn, _, err := zk.Connect(servers, time.Second)
if err != nil {
log.Fatalf("Failed to connect to ZooKeeper: %v", err)
}
defer conn.Close()
lockPath := "/distributed_lock"
lock := NewDistributedLock(conn, lockPath)
err = lock.Lock()
if err != nil {
log.Fatalf("Failed to acquire lock: %v", err)
}
// 在这里执行需要同步的操作
time.Sleep(2 * time.Second)
err = lock.Unlock()
if err != nil {
log.Fatalf("Failed to release lock: %v", err)
}
}
服务注册与发现
目前分布式系统已经很流行了,一些开源框架也被广泛应用,如dubbo、Motan等。对于一个分布式服务,最基本的一项功能就是服务的注册和发现,而利用zk的EPHEMERAL节点则可以很方便的实现该功能。EPHEMERAL节点正如其名,是临时性的,其生命周期是和客户端会话绑定的,当会话连接断开时,节点也会被删除。下边我们就来实现一个简单的分布式server:
server:
服务启动时,创建zk连接,并在go_servers节点下创建一个新节点,节点名为"ip:port",完成服务注册 服务结束时,由于连接断开,创建的节点会被删除,这样client就不会连到该节点
server.go
package main
import (
"github.com/samuel/go-zookeeper/zk"
)
func main() {
go starServer("127.0.0.1:8897")
go starServer("127.0.0.1:8898")
go starServer("127.0.0.1:8899")
a := make(chan bool, 1)
<-a
}
func checkError(err error) {
if err != nil {
fmt.Println(err)
}
}
func starServer(port string) {
tcpAddr, err := net.ResolveTCPAddr("tcp4", port)
fmt.Println(tcpAddr)
checkError(err)
listener, err := net.ListenTCP("tcp", tcpAddr)
checkError(err)
//注册zk节点q
// 链接zk
conn, err := GetConnect()
if err != nil {
fmt.Printf(" connect zk error: %s ", err)
}
defer conn.Close()
// zk节点注册
err = RegistServer(conn, port)
if err != nil {
fmt.Printf(" regist node error: %s ", err)
}
for {
conn, err := listener.Accept()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s", err)
continue
}
go handleCient(conn, port)
}
fmt.Println("aaaaaa")
}
func handleCient(conn net.Conn, port string) {
defer conn.Close()
daytime := time.Now().String()
conn.Write([]byte(port + ": " + daytime))
}
func GetConnect() (conn *zk.Conn, err error) {
zkList := []string{"localhost:2181"}
conn, _, err = zk.Connect(zkList, 10*time.Second)
if err != nil {
fmt.Println(err)
}
return
}
func RegistServer(conn *zk.Conn, host string) (err error) {
_, err = conn.Create("/go_servers/"+host, nil, zk.FlagEphemeral, zk.WorldACL(zk.PermAll))
return
}
func GetServerList(conn *zk.Conn) (list []string, err error) {
list, _, err = conn.Children("/go_servers")
return
}
client:
先从zk获取go_servers节点下所有子节点,这样就拿到了所有注册的server 从server列表中选中一个节点(这里只是随机选取,实际服务一般会提供多种策略),创建连接进行通信 这里为了演示,我们每次client连接server,获取server发送的时间后就断开。主要代码如下:
client.go
package main
import (
"github.com/samuel/go-zookeeper/zk"
)
func checkError(err error) {
if err != nil {
fmt.Println(err)
}
}
func main() {
for i := 0; i < 100; i++ {
startClient()
time.Sleep(1 * time.Second)
}
}
func startClient() {
// service := "127.0.0.1:8899"
//获取地址
serverHost, err := getServerHost()
if err != nil {
fmt.Printf("get server host fail: %s \n", err)
return
}
fmt.Println("connect host: " + serverHost)
tcpAddr, err := net.ResolveTCPAddr("tcp4", serverHost)
checkError(err)
conn, err := net.DialTCP("tcp", nil, tcpAddr)
checkError(err)
defer conn.Close()
_, err = conn.Write([]byte("timestamp"))
checkError(err)
result, err := ioutil.ReadAll(conn)
checkError(err)
fmt.Println(string(result))
return
}
func getServerHost() (host string, err error) {
conn, err := GetConnect()
if err != nil {
fmt.Printf(" connect zk error: %s \n ", err)
return
}
defer conn.Close()
serverList, err := GetServerList(conn)
if err != nil {
fmt.Printf(" get server list error: %s \n", err)
return
}
count := len(serverList)
if count == 0 {
err = errors.New("server list is empty \n")
return
}
//随机选中一个返回
r := rand.New(rand.NewSource(time.Now().UnixNano()))
host = serverList[r.Intn(3)]
return
}
func GetConnect() (conn *zk.Conn, err error) {
zkList := []string{"localhost:2181"}
conn, _, err = zk.Connect(zkList, 10*time.Second)
if err != nil {
fmt.Println(err)
}
return
}
func GetServerList(conn *zk.Conn) (list []string, err error) {
list, _, err = conn.Children("/go_servers")
return
}
参考
https://topgoer.com/%E6%95%B0%E6%8D%AE%E5%BA%93%E6%93%8D%E4%BD%9C/zookeeper/