ZooKeeper 是一个开源的分布式协调服务,目前由 Apache 进行维护。ZooKeeper 可以用于实现分布式系统中常见的发布/订阅、负载均衡、命令服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。 它具有以下特性:
1)简单的数据模型:
2)可配置 Cluster:
上图中每一个 Server 代表一个安装 ZooKeeper 服务的服务器,组成 ZooKeeper 服务的服务器都会在内存中维护当前的服务器状态,并且每台服务器间都保持着通信。并通过 Zab
协议来保持数据的一致性。
3)顺序访问:
4)高性能高可用
Cluster 角色:
角色 | 作用 |
---|---|
Leader |
提供读写服务,并维护集群状态(经过选举产生) |
Follower |
提供读写服务,并定期向 Leader 汇报自己的节点状态(同时也参加 过半写成功 的策略和 Leader 的选举) |
OBServer |
提供读写服务,并定期向 Leader 汇报自己的节点状态(因为不参加策略和选举,所以可以在不影响写性能的情况下提升集群的读性能) |
当 Client 通过 TCP 长连接 连接到 ZooKeeper 服务器时,Session 便开始建立连接,并通过 tickTime
(心跳检测)机制来保持有效的会话状态。通过这个连接,Client 可以发送请求并接收响应,同时也可以接收到 Watch 事件的通知。
另外,当由于网络故障或者 Client 主动断开等原因,导致连接断开,此时只要在会话超时时间之内重新建立连接,则之间创建的会话依然有效。(这个取决于 tickTime
配置)
ZooKeeper 数据模型是由一系列基本数据单元 ZNode(数据节点)组成的节点树,其中根节点为 /
(每个节点上都会保存自己的数据和节点信息);ZooKeeper 中的节点可以分为两大类:
ZooKeeper 中一个常用的功能是 Watcher(事件监听器),它允许用户在指定节点上针对感兴趣的事件注册监听,当事件发生时,监听器会被触发,并将事件推送到客户端。该机制是 ZooKeeper 实现分布式协调服务的重要特性。
ZooKeeper 采用 ACL(Access Control Lists)策略来进行权限控制,类似于 Unix 文件系统的控制权限:
命令 | 作用 |
---|---|
create |
可以进行创建操作 |
read |
可以进行查看操作 |
write |
可以对创建的内容进行写入操作 |
delete |
可以进行删除操作 |
admin |
可以进行配置权限操作 |
ZooKeeper Atomic Broadcast
原子广播)协议是为分布式协调服务 ZooKeeper 专门设计的一种 支持崩溃恢复的原子广播协议;准备工作:
主机名 | 操作系统 | IP 地址 |
---|---|---|
ZooKeeper | CentOS 7.4 | 192.168.1.1 |
安装 JDK:下载地址(需要创建 Oracle 账号)
[root@ZooKeeper ~]# ls
anaconda-ks.cfg jdk-8u181-linux-x64.tar.gz
[root@ZooKeeper ~]# tar zxf jdk-8u181-linux-x64.tar.gz
[root@ZooKeeper ~]# ls
anaconda-ks.cfg jdk1.8.0_181 jdk-8u181-linux-x64.tar.gz
[root@ZooKeeper ~]# mv jdk1.8.0_181 /usr/local/java
[root@ZooKeeper ~]# cat <<END >> /etc/profile
export JAVA_HOME=/usr/local/java
export PATH=$PATH:$JAVA_HOME/bin
END
[root@ZooKeeper ~]# source /etc/profile
[root@ZooKeeper ~]# java -version
[root@ZooKeeper ~]# wget http://dlcdn.apache.org/zookeeper/zookeeper-3.6.3/apache-zookeeper-3.6.3-bin.tar.gz
[root@ZooKeeper ~]# ls
anaconda-ks.cfg apache-zookeeper-3.6.3-bin.tar.gz jdk-8u181-linux-x64.tar.gz
[root@ZooKeeper ~]# tar zxf apache-zookeeper-3.6.3-bin.tar.gz
[root@ZooKeeper ~]# mv apache-zookeeper-3.6.3-bin /usr/local/zookeeper
[root@ZooKeeper ~]# mkdir /usr/local/zookeeper/data
[root@ZooKeeper ~]# cat <<END >> /usr/local/zookeeper/conf/zoo.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/usr/local/zookeeper/data
clientPort=2181
END
注解:
tickTime
:Client 和服务器间的通信会话限制(相当于健康检查,tickTime
的时间为 ms (1s = 1000ms)
)initLimit
:Leader 和 Follower 间初始通信限制。syncLimit
:Leader 和 Follower 间同步通信限制(当响应时间超于 syncLimit * tickTime
时,Leader 便会将 Follower 进行移除)dataDir
:此目录用于存放保存在内存数据库中的快照信息(当未配置 dataLogDir
参数时,日志信息也会存放到此目录)clientPort
:ZooKeeper 监听的端口,用于客户端连接使用。启动 ZooKeeper
[root@ZooKeeper ~]# /usr/local/zookeeper/bin/zkServer.sh start # 启动
[root@ZooKeeper ~]# /usr/local/zookeeper/bin/zkServer.sh status # 查看状态
[root@ZooKeeper ~]# /usr/local/zookeeper/bin/zkCli.sh -server 127.0.0.1:2181
Welcome to ZooKeeper!
JLine support is enabled
WATCHER::
WatchedEvent state:SyncConnected type:None path:null
[zk: 127.0.0.1:2181(CONNECTED) 0]
Welcome to ZooKeeper!
等信息。[root@ZooKeeper ~]# git clone https://github.com/samuel/go-zookeeper.git
[root@ZooKeeper ~]# mv go-zookeeper /usr/local/go/src/
package main
import (
"fmt"
"time"
"go-zookeeper/zk"
)
func main() {
Hosts := []string{
"192.168.1.1:2181"}
conn, _, err := zk.Connect(Hosts,time.Second * 5)
defer conn.Close()
if err != nil {
fmt.Println(err)
return
}
}
通过 Golang 实现对 ZooKeeper 的增删改查:
package main
import (
"fmt"
"time"
"go-zookeeper/zk"
)
var (
path = "/Zzz"
)
//增
func add(conn *zk.Conn) {
var data = []byte("Hello ZooKeeper")
// flags 的四种取值方式:
// 0 (永久.除非手动删除)
// zk.FlagEphemeral = 1 (短暂. session 断开则该节点也被删除)
// zk.FlagSequence = 2 (会自动在节点后面添加序号)
// 3 (Ephemeral 和 Sequence. 即短暂且自动添加序号)
var flags int32 = 0
// 获取访问控制权限
acls := zk.WorldACL(zk.PermAll)
create, err := conn.Create(path,data,flags,acls)
if err != nil {
fmt.Printf("创建失败: %v\n",err)
return
}
fmt.Printf("创建: %v 成功\n",create)
}
// 查
func get(conn *zk.Conn) {
data, _, err := conn.Get(path)
if err != nil {
fmt.Printf("查询 %s 失败,err: %v\n",path,err)
return
}
fmt.Printf("%s 的值为 %s\n",path,string(data))
}
// 删除与增加不同在于其函数中的 Version 参数. 其中 Version 使用 CAS 支持 (可以通过此种方式保证原子性)
// 改
func modify(conn *zk.Conn) {
new_data := []byte("This is ZooKeeper")
_, sate, _ := conn.Get(path)
_, err := conn.Set(path,new_data,sate.Version)
if err != nil {
fmt.Printf("数据修改失败: %v\n",err)
return
}
fmt.Println("数据修改成功")
}
// 删
func del(conn *zk.Conn) {
_, sate, _ := conn.Get(path)
err := conn.Delete(path,sate.Version)
if err != nil {
fmt.Printf("数据删除失败: %v\n",err)
return
}
fmt.Println("数据删除成功")
}
func main() {
hosts := []string{
"192.168.1.1:2181"}
conn, _, err := zk.Connect(hosts,time.Second * 5)
defer conn.Close()
if err != nil {
fmt.Println(err)
return
}
/* 增删改查 */
add(conn)
get(conn)
modify(conn)
get(conn)
del(conn)
}
主机名 | 操作系统 | IP 地址 |
---|---|---|
ZooKeeper-2 | CentOS 7.4 | 192.168.1.2 |
ZooKeeper-3 | CentOS 7.4 | 192.168.1.3 |
1)将 Java 和 ZooKeeper 传给新的服务器:
[root@ZooKeeper ~]# scp -r /usr/local/java root@192.168.1.2:/usr/local/
[root@ZooKeeper ~]# scp -r /usr/local/zookeeper root@192.168.1.2:/usr/local/
2)在新的服务器上启动 ZooKeeper:
[root@ZooKeeper ~]# cat <<END >> /etc/profile
export JAVA_HOME=/usr/local/java
export PATH=$PATH:$JAVA_HOME/bin
END
[root@ZooKeeper ~]# source /etc/profile
[root@ZooKeeper ~]# /usr/local/zookeeper/bin/zkServer.sh start
3)配置 Cluster 集群(三台服务器上操作一样)
[root@ZooKeeper ~]# cat <<END >> /usr/local/zookeeper/conf/zoo.cfg
server.1=192.168.1.1:2888:3888
server.2=192.168.1.2:2889:3889
server.3=192.168.1.3:2890:3890
END
4)创建 myid
文件
[root@ZooKeeper ~]# echo "1" > /usr/local/zookeeper/data/myid
[root@ZooKeeper-2 ~]# echo "2" > /usr/local/zookeeper/data/myid
[root@ZooKeeper-2 ~]# echo "3" > /usr/local/zookeeper/data/myid
myid
文件中数字不同,并且和自己所在机器的 zoo.cfg
中 server.id=host:port:port
的 id
值一样。id
的范围是 1 ~ 255
。5)重启 ZooKeeper 服务
[root@ZooKeeper ~]# /usr/local/zookeeper/bin/zkServer.sh restart # 三台服务器都要重启