目录
一、zookeeper入门
1.1 zookeeper工作机制
zookeeper从设计模式角度来理解:是一个基于观察者模式设计的分布式服务器管理框架,它负责存储和管理大家都关系的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,zookeeper就将负责通知已经zookeeper上注册的那些观察者做出相应的反应。
zookeeper = 文件系统 + 通知机制
1.2 zookeeper特点
1)一个leader,多个follower组成的集群;
2)集群中主要有半数以上节点存活,zookeeper集群就能正常服务(一般按照奇数台服务器);
3)全局数据一致:每个server保存一份相同的数据副本,client无论连接到哪个server,数据都是一致的;
4)更新请求顺序执行,来自同一个client的更新请求按其发送顺序依次执行;
5)数据更新原子性:一次数据更新要么成功,要么失败;
6)实时性:在一定时间范围内,client能读取到最新的数据。
1.3 数据结构
zookeeper数据模型的结构与Unix文件系统很类似,整体上可以看做是一棵树,每个节点称作一个ZNode。每一个ZNode默认能够存储1MB的数据,每个ZNode都可以通过其路径唯一标识。
1.4 应用场景
提供的服务包括:统一命名服务、统一配置管理、统一集群管理、服务器节点动态上下线、软负载均衡等。
二、zookeeper操作
2.1 zookeeper服务启动等命令
先进入zookeeper安装目录
1)启动zookeeper
bin/zkServer.sh start
2)查看状态
bin/zkServer.sh status
3)启动客户端
bin/zkCli.sh
4)退出客户端
quit
5)停止zookeeper
bin/zkServer.sh stop
2.2 客户端命令行操作
命令基本语法 | 功能描述 |
help | 显示所有操作命令 |
ls path | 使用 ls 命令来查看当前znode的子节点【可监听】 -w 监听子节点变化 -s 附加次级信息 |
create | 普通创建 -s 含有序列 -e 临时(重启或者超时消失) |
get path | 获得节点的值【可监听】 -w 监听节点内容变化 -s 附加次级信息 |
set | 设置节点的具体值 |
stat | 查看节点状态 |
delete | 删除节点 |
deleteall | 递归删除节点 |
2.3 客户端 Golang API 操作
2.3.1 curd操作
package main
import (
"fmt"
"github.com/samuel/go-zookeeper/zk"
"time"
)
var (
host = []string{"127.0.0.1:2181"}
)
func main() {
conn, _, err := zk.Connect(host, 5*time.Second)
if err != nil {
panic(err)
}
//增
if _, err := conn.Create("/test_tree2", []byte("tree_content"),
0, zk.WorldACL(zk.PermAll)); err != nil {
fmt.Println("create err", err)
}
//查
nodeValue, dStat, err := conn.Get("/test_tree2")
if err != nil {
fmt.Println("get err", err)
return
}
fmt.Println("nodeValue", string(nodeValue))
//改
if _, err := conn.Set("/test_tree2", []byte("new_content"),
dStat.Version); err != nil {
fmt.Println("update err", err)
}
//删除
_, dStat, _ = conn.Get("/test_tree2")
if err := conn.Delete("/test_tree2", dStat.Version); err != nil {
fmt.Println("Delete err", err)
//return
}
//验证存在
hasNode, _, err := conn.Exists("/test_tree2")
if err != nil {
fmt.Println("Exists err", err)
//return
}
fmt.Println("node Exist", hasNode)
//增加
if _, err := conn.Create("/test_tree2", []byte("tree_content"),
0, zk.WorldACL(zk.PermAll)); err != nil {
fmt.Println("create err", err)
}
//设置子节点
if _, err := conn.Create("/test_tree2/subnode", []byte("node_content"),
0, zk.WorldACL(zk.PermAll)); err != nil {
fmt.Println("create err", err)
}
//获取子节点列表
childNodes, _, err := conn.Children("/test_tree2")
if err != nil {
fmt.Println("Children err", err)
}
fmt.Println("childNodes", childNodes)
}
2.3.2 监听操作
zk.go
package zookeeper
import (
"fmt"
"github.com/samuel/go-zookeeper/zk"
"time"
)
type ZkManager struct {
hosts []string
conn *zk.Conn
pathPrefix string
}
func NewZkManager(hosts []string) *ZkManager {
return &ZkManager{hosts: hosts, pathPrefix: "/gateway_servers_"}
}
//连接zk服务器
func (z *ZkManager) GetConnect() error {
conn, _, err := zk.Connect(z.hosts, 5*time.Second)
if err != nil {
return err
}
z.conn = conn
return nil
}
//关闭服务
func (z *ZkManager) Close() {
z.conn.Close()
return
}
//获取配置
func (z *ZkManager) GetPathData(nodePath string) ([]byte, *zk.Stat, error) {
return z.conn.Get(nodePath)
}
//更新配置
func (z *ZkManager) SetPathData(nodePath string, config []byte, version int32) (err error) {
ex, _, _ := z.conn.Exists(nodePath)
if !ex {
z.conn.Create(nodePath, config, 0, zk.WorldACL(zk.PermAll))
return nil
}
_, dStat, err := z.GetPathData(nodePath)
if err != nil {
return
}
_, err = z.conn.Set(nodePath, config, dStat.Version)
if err != nil {
fmt.Println("Update node error", err)
return err
}
fmt.Println("SetData ok")
return
}
//创建临时节点
func (z *ZkManager) RegistServerPath(nodePath, host string) (err error) {
ex, _, err := z.conn.Exists(nodePath)
if err != nil {
fmt.Println("Exists error", nodePath)
return err
}
if !ex {
//持久化节点,思考题:如果不是持久化节点会怎么样?
_, err = z.conn.Create(nodePath, nil, 0, zk.WorldACL(zk.PermAll))
if err != nil {
fmt.Println("Create error", nodePath)
return err
}
}
//临时节点
subNodePath := nodePath + "/" + host
ex, _, err = z.conn.Exists(subNodePath)
if err != nil {
fmt.Println("Exists error", subNodePath)
return err
}
if !ex {
_, err = z.conn.Create(subNodePath, nil, zk.FlagEphemeral, zk.WorldACL(zk.PermAll))
if err != nil {
fmt.Println("Create error", subNodePath)
return err
}
}
return
}
//获取服务列表
func (z *ZkManager) GetServerListByPath(path string) (list []string, err error) {
list, _, err = z.conn.Children(path)
return
}
//watch机制,服务器有断开或者重连,收到消息
func (z *ZkManager) WatchServerListByPath(path string) (chan []string, chan error) {
conn := z.conn
snapshots := make(chan []string)
errors := make(chan error)
go func() {
for {
snapshot, _, events, err := conn.ChildrenW(path)
if err != nil {
errors <- err
}
snapshots <- snapshot
select {
case evt := <-events:
if evt.Err != nil {
errors <- evt.Err
}
fmt.Printf("ChildrenW Event Path:%v, Type:%v\n", evt.Path, evt.Type)
}
}
}()
return snapshots, errors
}
//watch机制,监听节点值变化
func (z *ZkManager) WatchPathData(nodePath string) (chan []byte, chan error) {
conn := z.conn
snapshots := make(chan []byte)
errors := make(chan error)
go func() {
for {
dataBuf, _, events, err := conn.GetW(nodePath)
if err != nil {
errors <- err
return
}
snapshots <- dataBuf
select {
case evt := <-events:
if evt.Err != nil {
errors <- evt.Err
return
}
fmt.Printf("GetW Event Path:%v, Type:%v\n", evt.Path, evt.Type)
}
}
}()
return snapshots, errors
}
main.go
package main
import (
"fmt"
"log"
"os"
"os/signal"
"syscall"
)
var addr = "127.0.0.1:2002"
func main() {
//获取zk节点列表
zkManager := zookeeper.NewZkManager([]string{"127.0.0.1:2181"})
zkManager.GetConnect()
defer zkManager.Close()
zlist, err := zkManager.GetServerListByPath("/real_server")
fmt.Println("server node:")
fmt.Println(zlist)
if err != nil {
log.Println(err)
}
//动态监听节点变化
chanList, chanErr := zkManager.WatchServerListByPath("/real_server")
go func() {
for {
select {
case changeErr := <-chanErr:
fmt.Println("changeErr")
fmt.Println(changeErr)
case changedList := <-chanList:
fmt.Println("watch node changed")
fmt.Println(changedList)
}
}
}()
//关闭信号监听
quit := make(chan os.Signal)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
}
三、面试
3.1 选举机制
半数机制,超过半数的投票通过,即通过。
1)第一次启动选举规则:
投票过半数时,服务器 id 大的胜出
2)第二次启动选举规则:
①EPOCH 大的直接胜出
②EPOCH 相同,事务 id 大的胜出
③事务 id 相同,服务器 id 大的胜出
EPOCH:每个Leader任期的代号。没有Leader时同一轮投票过程中的逻辑时钟值是相同的。每投完一次票这个数就会增加。
事务id:用来标识一次服务器状态的变更。在某一时刻,集群中的每台机器的事务id不一定完全一致,这和zookeeper服务器对于客户端“更新请求”的处理逻辑有关。
服务器id:用来唯一标识一台zookeeper集群中的机器,每台机器不能重复,和myid一致。
3.2 生产集群安装多少 zk 合适?
安装奇数台
生产经验:
10 台服务器:3 台 zk;
20 台服务器:5 台 zk;
100 台服务器:11 台 zk;
200 台服务器:11 台 zk
服务器台数多:好处,提高可靠性;坏处:提高通信延时
3.3 常用命令
ls、get、create、delete