文章目录
zookeeper是什么
分布式协调框架
zookeeper主要功能
- 类似于文件系统的节点存储和查询功能,且写入节点保证数据的一致性
- 能够监听节点的数据变化,接收变更的通知
基于以上两点功能,实现负载均衡、分布式锁、master选举、分布式队列、配置中心等
zookeeper的特性
- 顺序一致性:客户端发起的事务,会严格根据顺序逐一执行
- 原子性:所有机器的数据处理结果是一致的,要么都成功,要么都失败
- 单一视图:客户端连接集群中任一节点,数据都是一致的
- 可靠性:数据能够可靠地保存在集群中
- 实时性:一旦事务被成功应用,就能够读取刚刚应用的事务数据(接近实时)
zookeeper的概念
数据模型
- 概念:zookeeper的数据模型与一个文件系统类似,每一个节点称为znode,每个节点上都可保存数据和挂载数据,结构图如下
- 节点种类:
- 持久化节点:创建后会一直存在,除非主动删除
- 持久化有序节点:每个节点都会维护一个顺序
- 临时节点:生命周期与客户端会话一致,客户端会话失效,节点自动清理
- 临时有序节点:多了有序性
- 节点属性:
- cversion = 0 子节点的版本号
- aclVersion = 0 表示acl的版本号,修改节点权限
- dataVersion = 1 表示的是当前节点数据的版本号
- czxid 节点被创建时的事务ID
- mzxid 节点最后一次被更新的事务ID
- pzxid 当前节点下的子节点最后一次被修改时的事务ID
- ctime = Sat Aug 05 20:48:26 CST 2017
- mtime = Sat Aug 05 20:48:50 CST 2017
会话
- 概念:zookeeper的会话是客户端与服务端进行的连接会话,主要的状态过程如下
Watcher
- 概念:类似于java中的监听机制,客户端可以向服务端注册一个watcher监听,当服务端的节点触发指定事件的时候,会通知注册的watcher。
ACL
- 概念:zookeeper提供了节点可设置访问权限的功能,保证节点的安全性
- 权限分类:
- CREATE:增
- READ:查
- WRITE:写
- DELETE:删(对子节点的删除)
- ADMIN:管理
- 身份认证的方式
- world:默认方式,相当于全世界都能访问
- auth:代表已经认证通过的用户(cli中可以通过addauth digest user:pwd 来添加当前上下文中的授权用户)
- digest:即用户名:密码这种方式认证,这也是业务系统中最常用的
- ip:使用Ip地址认证
zookeeper安装
单机
- 下载zookeeper的安装包
http://apache.fayea.com/zookeeper/stable/zookeeper-3.4.10.tar.gz - 解压zookeeper
tar -zxvf zookeeper-3.4.10.tar.gz - cd 到 ZK_HOME/conf , copy一份zoo.cfg
cp zoo_sample.cfg zoo.cfg - sh zkServer.sh
{start|start-foreground|stop|restart|status|upgrade|print-cmd} - sh zkCli.sh -server ip:port
集群
- 修改配置文件
server.id=host:port:port
id的取值范围: 1~255; 用id来标识该机器在集群中的机器序号
2181是zookeeper的端口; //3306
3181表示leader选举的端口
server.1=192.168.11.129:2181:3181
server.2=192.168.11.131:2181:3181
server.3=192.168.11.135:2181:3181
-
创建myid
在每一个服务器的dataDir目录下创建一个myid的文件,文件就一行数据,数据内容是每台机器对应的server ID的数字 -
启动zookeeper
zookeeper常用API
- 原生zookeeper:较为原生,未做封装,不是很好用
- zkclient:异常处理不好
- curator:异常处理较好,监听机制可以永久监听,且封装了常用的工具,如分布式锁,master选举,队列等,引入包如下:
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.8</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
zookeeper使用场景和原理
master选举
- 概念:多个服务启动后,需要选举出一个master(如定时任务的执行,系统高可用,master-slave模式),当master宕机后,需要再次选举新的master
- 原理:多个服务写入同一个临时节点,写入成功的则为master,并监听该临时节点,如果master宕机,临时节点也就删除,则其他服务节点接听到删除事件后,再次写入,再次生成一个master
- 代码实现:
public class LeaderExample {
// 选举的路径
private static final String PATH = "/examples/leader";
public static void main(String[] args) throws Exception {
List<LeaderLatch> leaderExamples = new ArrayList<LeaderLatch>();
List<CuratorFramework> curatorFrameworks = new ArrayList<CuratorFramework>();
for (int i = 0; i < 20; i++) {
// 创建zookeeper客户端
CuratorFramework curatorFramework = CuratorTest.newInstance();
// 创建LeaderLatch
LeaderLatch leaderLatch = new LeaderLatch(curatorFramework, PATH, "Client #" + i);
curatorFrameworks.add(curatorFramework);
leaderExamples.add(leaderLatch);
// 启动
curatorFramework.start();
leaderLatch.start();
}
// 启动需要等待
TimeUnit.SECONDS.sleep(2);
LeaderLatch currentLeader = null;
for (LeaderLatch leaderExample : leaderExamples) {
if (leaderExample == null) {
continue;
}
// 获取领导权的,打印
if (leaderExample.hasLeadership()) {
currentLeader = leaderExample;
System.out.println(leaderExample.getId() + " 作为leader,执行定时任务");
}
}
// 获取领导权的,断开连接
currentLeader.close();
// 启动需要等待
TimeUnit.SECONDS.sleep(2);
// 新的领导权已分配
for (LeaderLatch leaderExample : leaderExamples) {
if (leaderExample == null) {
continue;
}
if (leaderExample.hasLeadership()) {
System.out.println(leaderExample.getId() + " 作为leader,执行定时任务");
}
}
// 关闭
for (LeaderLatch leaderExample : leaderExamples) {
CloseableUtils.closeQuietly(leaderExample);
}
for (CuratorFramework curatorFramework : curatorFrameworks) {
CloseableUtils.closeQuietly(curatorFramework);
}
}
}
分布式锁
- 概念:多个服务对于共享资源的使用需要进行加锁的操作,而这边的锁需要是多个服务共同竞争的分布式锁,原因是进程中的锁无法在服务间共享可见。
- 原理:分布式锁的加锁操作,同时写入一个zookeeper的节点,写入成功,则获取锁成功;释放锁操作,删除zookeeper的节点,删除完成,则释放锁,其他服务监听到删除时,重新竞争锁。
- 代码实现:
// 共享资源
public class SharedResource {
private final AtomicBoolean resource = new AtomicBoolean(false);
// 使用资源
public void use(String clientName) {
// 资源已被修改,则报错
if (!resource.compareAndSet(false, true)) {
throw new RuntimeException("resource has been used by client");
} else {
// 打印成功使用资源的客户端
System.out.println(clientName + " use the resource");
}
try {
// 休眠能够产生同步问题
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
resource.set(false);
}
}
}
public class DistributedLock {
private final InterProcessMutex lock;// 锁
private final String clientName;// 客户端名称
private final SharedResource sharedResource;// 共享资源
public DistributedLock(CuratorFramework curatorFramework, String clientName, SharedResource sharedResource, String pathName) {
// 根据客户端和锁的路径构造锁
this.lock = new InterProcessMutex(curatorFramework, pathName);
this.clientName = clientName;
this.sharedResource = sharedResource;
}
public void doWork(long time, TimeUnit unit) throws Exception {
// 竞争锁失败,则报错
if (!lock.acquire(time, unit)) {
throw new RuntimeException(clientName + " can't acquire the lock");
}
// 获取锁成功,则使用
try {
sharedResource.use(this.clientName);
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
// 释放锁
System.out.println("release lock");
lock.release();
}
}
public static void main(String[] args) throws InterruptedException {
// 共享资源
final SharedResource sharedResource = new SharedResource();
// 锁路径
final String path = "/examples/lock";
for (int i = 0; i < 10; i++) {
final int p = i;
new Thread(new Runnable() {
public void run() {
// 创建客户端
CuratorFramework client = CuratorTest.newInstance();
// 启动客户端
client.start();
try {
// 创建锁
DistributedLock lock = new DistributedLock(client, "客户端 " + p, sharedResource, path);
// 争抢锁
lock.doWork(2, TimeUnit.SECONDS);
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 关闭客户端
CloseableUtils.closeQuietly(client);
}
}
}).start();
}
TimeUnit.SECONDS.sleep(10);
}
}
配置中心
- 概念:分布式服务的配置文件较多,手动维护每个服务的配置成本较大,配置中心可以统一管理所有服务的配置项,配置有变动,服务也可根据推拉模型进行配置的获取
- 原理:配置中心负责写入配置项到zookeeper的节点中,而服务节点则向zookeeper注册关心的节点,并对其进行监听,监听到变动后,查询zookeeper的数据,然后对本地配置项进行更新
负载均衡
- 概念:请求/数据分摊多个计算机单元上。
- 原理:服务生产者启动时,将服务写入zookeeper的临时节点,当服务宕机时,临时节点被删除;服务消费者在使用时,获取临时节点,通过负载均衡算法来获取调用的生产者节点,并监听生产者节点的删除操作,删除时,从本地缓存列表移除节点
zookeeper学习(六)ZooKeeper实现软负载均衡
分布式队列
- 概念:分布式队列是一个服务间共享的队列
- 原理:队列入队,通过写入有序的节点;队列出队,通过查询节点,并对节点进行删除;zookeeper的节点能够存储的数据大小存在限制,为1M,且存储大数据量的节点,会导致性能下降,故不要存储的节点数据量过大
zookeeper集群选举
集群角色
- Leader:集群的核心
- 事务请求的唯一调度者和处理者,保证集群事务处理的顺序性
- 集群内部各个服务器的调度者
- Follower:跟随者
- 处理客户端的非事务请求,并将事务请求转发给leader服务器
- 参与事务请求提议(proposal)的投票(客户端的一个事务请求,需要半数的服务器投票通过后,才能通知Leader commit;leader会发起一个提案,要求follower投票)
- 参与leader选举的投票
- Observer:观察者
- 观察zookeeper集群的最新状态变化,并同步到observer
- 处理客户端的非事务请求(不影响集群的事务请求,提高集群的非事务处理能力)
集群服务器组成
由2n+1台服务器(该服务器不包含Observer)组成,不是偶数台的原因是2n-1和2n台的服务器,2n台的服务器并不能增加高可用,因为2n-1宕掉n台不可用,而2n也是宕掉n台后不可用,且2n台需要n+1台服务器投票通过,才能提交事务请求,效率更慢,故服务器是奇数台。
集群选举过程
目前有5台服务器,每台服务器均没有数据,它们的编号分别是1,2,3,4,5,按编号依次启动,它们的选择举过程如下:
- 服务器1启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器1的状态一直属于Looking。
- 服务器2启动,给自己投票,同时与之前启动的服务器1交换结果,由于服务器2的编号大所以服务器2胜出,但此时投票数没有大于半数,所以两个服务器的状态依然是LOOKING。
- 服务器3启动,给自己投票,同时与之前启动的服务器1,2交换信息,由于服务器3的编号最大所以服务器3胜出,此时投票数正好大于半数,所以服务器3成为领导者,服务器1,2成为小弟。
- 服务器4启动,给自己投票,同时与之前启动的服务器1,2,3交换信息,尽管服务器4的编号大,但之前服务器3已经胜出,所以服务器4只能成为小弟。
- 服务器5启动,后面的逻辑同服务器4成为小弟。