分布式编程中可能会有一部分失效,zookeeper就是为了解决这种问题的,其特点
1.简单(一个精简的文件系统)
2.富有表现力(可用于实现分布式队列,分布式锁,分布式选举)
3.具有高可用性
4.采用松散耦合交互方式
5.是一个资源库
zookeeper中没有文件和目录,而是统一使用"节点"的概念,成为znode
可以直接在windows下运行
实例
1.创建组
public class CreateGroup implements Watcher {
private static final int SESION_TIMEOUT = 5000;
private ZooKeeper zk;
private CountDownLatch latch = new CountDownLatch(1);
public void connect(String host) throws IOException {
zk = new ZooKeeper(host,SESION_TIMEOUT,this);
}
@Override
public void process(WatchedEvent event) {
if(event.getState() == KeeperState.SyncConnected) {
latch.countDown();
}
}
public void create(String name) throws KeeperException, InterruptedException {
String path = "/"+name;
String p = zk.create(path, null, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("create path="+p);
}
public void close() throws InterruptedException {
zk.close();
}
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
CreateGroup g = new CreateGroup();
g.connect("localhost");
g.create("test");
g.close();
System.out.println("ok");
}
}
2.加入组
public class ConnectionWatcher implements Watcher {
private static final int SESION_TIMEOUT = 5000;
protected ZooKeeper zk;
private CountDownLatch latch = new CountDownLatch(1);
public void connect(String host) throws IOException {
zk = new ZooKeeper(host,SESION_TIMEOUT,this);
}
@Override
public void process(WatchedEvent event) {
if(event.getState() == KeeperState.SyncConnected) {
latch.countDown();
}
}
public void close() throws InterruptedException {
zk.close();
}
}
public class JoinGroup extends ConnectionWatcher {
public void join(String group,String member) throws KeeperException, InterruptedException {
String path = "/"+group+"/"+member;
String p = zk.create(path, null, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
System.out.println("created path="+p);
}
public static void main(String[] args) throws InterruptedException, KeeperException, IOException {
JoinGroup g = new JoinGroup();
g.connect("localhost");
g.join("test", "join");
Thread.sleep(Long.MAX_VALUE);
}
}
3.列出组成员
public class ListGroup extends ConnectionWatcher {
public void list(String group) {
String path = "/"+group;
try {
List<String> list = zk.getChildren(path, false);
if(list.isEmpty()) {
System.out.println("no member");
}
for(String c : list) {
System.out.println(c);
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException, IOException {
ListGroup g = new ListGroup();
g.connect("localhost");
g.list("test");
g.close();
}
}
4.删除组
public class DeleteGroup extends ConnectionWatcher {
public void delete(String group) throws KeeperException, InterruptedException {
String path = "/"+group;
List<String> list = zk.getChildren(path, false);
for(String c : list) {
zk.delete(path+"/"+c, -1);
System.out.println("delete path="+path+"/"+c);
}
}
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
DeleteGroup g = new DeleteGroup();
g.connect("localhost");
g.delete("aa");
g.close();
}
}
命令行工具
zkcli
ZooKeeper -server host:port cmd args
connect host:port
get path [watch]
ls path [watch]
set path data [version]
rmr path
delquota [-n|-b] path
quit
printwatches on|off
create [-s] [-e] path data acl
stat path [watch]
close
ls2 path [watch]
history
listquota path
setAcl path acl
getAcl path
sync path
redo cmdno
addauth scheme auth
delete path [version]
setquota -n|-b val path
Zookeeper服务
zookeeper中使用的都是绝对路径,类似unix的文件系统路径
短暂的znode当客户端连接断开就会消失,并且不能有子节点
持久的znode会永远存在,除非显示的删除
每个节点都会有一个全局的顺序号,用来进行全局排序
znode可以让客户端注册观察事件,实现异步通知
操作:
create 创建一个znode
delete 删除一个znode(不能有任何子节点)
exists 测试一个zonode是否存在并查询其元数据
getAcl,setACl 获取/设置一个znode的ACL
getChildre 后去一个znode的子节点列表
getData,setData 获取/设置一个znode所保存的数据
sync 将客户端的znode视图与zookeeper同步
zookeeper中可以实现异步回调接口StatCallback
设置触发器exists,getChildren,getData,
上面这些操作被create,delete,setData 触发
ACL
依赖于zookeeper的客户端验证机制,zookeeper提供下面几种身份验证机制
1.digest 通过用户名和密码来识别客户端
2.host 通过客户端的主机名来识别客户端
3.ip 通过客户端的IP地址来识别客户端
如:
zk.addAuthInfo("digest","tom:secret".getByes());
创建一个ACL对象:
ACL acl = new ACL(Perms.READ,new Id("host","example.com"));
ACL的权限 允许的操作
CREATE create(子节点)
READ getChildren和getData
WRITE setData
DELETE delete(子节点)
ADMIN setACL
zookeeper中有一些预定义的ACL,比如OPEN_ACL_UNSAFE,将所有权限(除ADMIN)授权给所有人
zookeeper有两种运行模式,独立模式(单机环境),复制模式(生成环境),通常应该有奇数台机器
zookeeper使用了ZAB协议,该协议包括两个可以无限重复的阶段
1.领导的选举
集合体重所有机器通过一个选择过程选出一台被称为"领导者(leader)"的机器,其他机器被称为
跟随者(follower),一旦半数以上的(或者指定数量)的跟随者已经将其状态与领导者同步,则表明
这个阶段已经完成
2.原子广播
所有的写请求都交给领导者,再由领导者更新广播给跟随者,当半数以上的跟随者修改持久化之后,
领导者才会提交这个更新,然后客户端才会收到一个成功的响应。这个设计具有原子性,也类似于
数据库中的两阶段提交。
领导者的选举大概需要200毫秒,所以不会有明星性能降低
zookeeper中的一致性:
1.顺序一致性
2.原子性
3.单一系统映像
4.持久性
5.及时性(任何客户端所看到的系统视图邂逅是有限的)
会话
客户端会尝试连接列表中的所有zookeeper,如果所有连接都失败才会失败
创建连接后zookeeper客户端会自动定期发送心跳包
当一个连接故障导致断开后,会继续连接其他服务端
zookeeper中的滴答(tick time)参数定义了zookeeper中的基本时间周期,如会话超时参数不可以小于
两个滴答,也不可以大于20大滴答,如滴答是2秒则会话时间应该是4-40秒之间。
zookeeper中的服务器越多,会话超时设置应该越大
zookeeper中任何连接在一个时刻只能处于一个状态,所有的状态如下:
ASSOCIATING
AUTH_FAILED
CLOSED
CONNECTED
CONNECTEDREADONLY
CONNECTING
NOT_CONNECTED
zookeeper中的异常
InterruptedException 这个和标准java中的异常一样,表示一个操作被取消了,并不是代表有故障
KeeperException 同城是与服务器存在通讯问题,
KeeperException.Code 中包含了对应了错误类型代码
KeeperException异常分三大类:
1.状态异常,如并发更新导致的状态异常
2.可恢复的异常,比如连接断开可以进行重试
但是需要区分幂等操作和非幂等操作,不能盲目重试
3.不可恢复的异常,如身份验证失败,会话超时等
锁服务
通过创建顺序短暂znode来实现,比如/leader/lock-1, /leader/lock-2, znode顺序号最小的客户端将
会持有锁。申请获取锁的伪代码如下:
1.在锁znode下创建一个名为lock- 的短暂顺序znode,并记住它的实际路径名(create操作返回的值)
2.查询锁znode的子节点并设置一个观察
3.如果步骤1中所创建的znode在步骤2中所返回的所有子节点中具有最小顺序号则获取锁,退出
4.等待步骤2中所有设置观察的通知并转到步骤2
羊群效应:
上面代码会有一些问题,假如有成百上千客户端情况下,所有客户端都尝试获取锁,每个客户端都在znode上设置一个观察,每次锁被释放或者另外一个进程开始申请获取锁时,每个客户端都会收到一个事件通知,这就是羊群效应
羊群效应是指 大量客户端收到同一事件的通知,但实际上只有很少一部分需要处理这一事件
此时优化条件,只有前一个顺序号消失时才通知下个客户端,比如/leader/lock-1,/leader/lock-2,/leader/lock-3,只有当/leader/lock-2消失时才通知/leader/lock-3。
如果碰到了异常,则会创建失败,但是创建操作不是幂等的,否则不断重试会创建很多孤儿znode。
问题在于客户端在重连之后不能判断出它是否已经创建过子节点,解决方案是在ID中再加入一个标识,比如客户端会话ID。重连之后看看子节点中的名称中是否包含其ID便知道是否创建成功了
那么最终的短暂顺序znode就是:
lock-<sessionId>-<sequenceNum> 这样的方式
zookeeper还可以实现屏障barrier,队列,两阶段提交等协议
BookKeeper是一个具有高可用性的日志服务,用来提供预写日志服务
生成环境的zookeeper
zookeeper节点一般是跨机架,电源和交换机来安放服务,这样设备中的任何一个出现故障不会使集合中损失半数以上的服务器,由于需要低延迟连接所以一个集合体只限于一个数据中心内投票的节点安放在一个数据中心内,将观察节点安放在另一个数据中心,可以使zookeeper集群跨多个数据中心
zookeeper的实物日志和数据快照分别保存到不同磁盘驱动器上,默认是两者都保存在dataDir所指的目
录下,通过为dataLogDir设置一个值,可以将日志写到一个专用的设备,提高性能
在复制模式下有两个强制参数:
initLimit:运行所有跟随者与领导者进行连接并同步的时间,如果在设定的时间段内,半数以上的跟随者未能完成同步,领导者便会宣布放弃领导地位,然后进行另外一次领导选举。如果这种情况经常发生(有日志记录)说明参数值设置太小
syncLimit: 允许一个跟随者与领导者进行同步的时间。如果在设定的时间段内,一个跟随者未能完成同步,它将会自动重启。所有关联到这个跟随者的客户端将连接到另一个跟随者