目录
1.软件架构的发展
2.了解zookeeper
2.1概述
2.2zookeeper的应用场景
2.3安装zookeeper
2.4zookeeper客户端命令
3.zookeeper简单操作
3.1zookeeper的数据结构
3.2节点的分类
3.3java代码操作zookeeper节点
3.4zookeeper的watch机制
3.4.1介绍
3.4.2NodeCache
3.4.3PathChildrenCache
3.4.4TreeCache
4.集群
4.1了解zookeeper集群
4.2架构图
4.3搭建集群
4.4leader选举
1.软件架构的发展
软件架构的发展经历了由单体架构、垂直架构、分布式架构到流动计算架构的演进过程,如下图:
单一应用架构
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
垂直应用架构
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
分布式服务架构
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。
流动计算架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。
什么是SOA
SOA全称为Service-OrientedArchitecture,即面向服务的架构。它可以根据需求通过网络对松散耦合的粗粒度应用组件(服务)进行分布式部署、组合和使用。一个服务通常以独立的形式存在于操作系统进程中。
站在功能的角度,把业务逻辑抽象成可复用、可组装的服务,通过服务的编排实现业务的快速再生,目的:把原先固有的业务功能转变为通用的业务服务,实现业务逻辑的快速复用。
通过上面的描述可以发现SOA有如下几个特点:分布式、可重用、扩展灵活、松耦合。
原来的单体项目如何改为SOA架构?
原来的单体工程项目大多分为三层:表现层(Controller)、业务层(Service)、持久层(Dao),要改为SOA架构,其实就是将业务层提取为服务并且独立部署即可,表现层通过网络和业务层进行通信,如下图:
2.了解zookeeper
2.1概述
ZooKeeper从字面意思理解,【Zoo - 动物园,Keeper - 管理员】动物园中有很多种动物,这里的动物就可以比作分布式环境下多种多样的服务,而ZooKeeper做的就是管理这些服务。 Apache ZooKeeper的系统为分布式协调是构建分布式应用的高性能服务。 ZooKeeper 本质上是一个分布式的小文件存储系统。提供基于类似于文件系统的目录树方式的数据存储,并且可以对树中的节点进行有效管理。从而用来维护和监控你存储的数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理。 ZooKeeper 适用于存储和协同相关的关键数据,不适合用于大数据量存储。
2.2zookeeper的应用场景
注册中心
分布式应用中,通常需要有一套完整的命名规则,既能够产生唯一的名称又便于人识别和记住,通常情况下用树形的名称结构是一个理想的选择,树形的名称结构是一个有层次的目录结构。通过调用Zookeeper提供的创建节点的API,能够很容易创建一个全局唯一的path,这个path就可以作为一个名称。 阿里巴巴集团开源的分布式服务框架Dubbo中使用ZooKeeper来作为其命名服务,维护全局的服务地址列表。
配置中心
数据发布/订阅即所谓的配置中心:发布者将数据发布到ZooKeeper一系列节点上面,订阅者进行数据订阅,当数据有变化时,可以及时得到数据的变化通知,达到动态获取数据的目的。
ZooKeeper 采用的是推拉结合的方式。
1、推push: 服务端会推给注册了监控节点的客户端 Wathcer 事件通知。
2、拉pull: 客户端获得通知后,然后主动到服务端拉取最新的数据。
分布式锁
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
分布式队列
在传统的单进程编程中,我们使用队列来存储一些数据结构,用来在多线程之间共享或传递数据。分布式环境下,我们同样需要一个类似单进程队列的组件,用来实现跨进程、跨主机、跨网络的数据共享和数据传递,这就是我们的分布式队列。
负载均衡
负载均衡是通过负载均衡算法,用来把对某种资源的访问分摊给不同的设备,从而减轻单点的压力。
上图中左侧为ZooKeeper集群,右侧上方为工作服务器,下面为客户端。每台工作服务器在启动时都会去ZooKeeper的servers节点下注册临时节点,每台客户端在启动时都会去servers节点下取得所有可用的工作服务器列表,并通过一定的负载均衡算法计算得出一台工作服务器,并与之建立网络连接。
2.3安装zookeeper
下载地址:http://zookeeper.apache.org
1.安装完成后在zookeeper路径下创建一个data文件夹,用于存放数据。
2.conf路径中复制一份zoo_sample.cfg,改名为 zoo.cfg
3.zoo.cfg配置文件中指定保存数据的目录:data目录
4.启动zookeeper,打开bin目录,双击zkServer.cmd
5.启动客户端,bin目录下,双击zkCli.cmd
2.4zookeeper客户端命令
输入help命令即可查看:
3.zookeeper简单操作
3.1zookeeper的数据结构
ZooKeeper 的数据模型是层次模型。层次模型常见于文件系统。例如:我的电脑可以分为多个盘符(例如C、D、E等),每个盘符下可以创建多个目录,每个目录下面可以创建文件,也可以创建子目录,最终构成了一个树型结构。通过这种树型结构的目录,我们可以将文件分门别类的进行存放,方便我们后期查找。而且磁盘上的每个文件都有一个唯一的访问路径。
层次模型和key-value 模型是两种主流的数据模型。ZooKeeper 使用文件系统模型主要基于以下两 点考虑:
-
文件系统的树形结构便于表达数据之间的层次关系。
-
文件系统的树形结构便于为不同的应用分配独立的命名空间(namespace)。
ZooKeeper 的层次模型称作data tree。Datatree 的每个节点叫作znode(Zookeeper node)。不同于文件系统,每个节点都可以保存数据。每个节点都有一个版本(version)。版本从0 开始计数。
如图所示,data tree中有两个子树,用于应用1( /app1)和应用2(/app2)。每个客户端进程pi 创建一个znode节点 p_i 在 /app1下, /app1/p_1就代表一个客户端在运行。
3.2节点的分类
持久节点和临时节点:一个znode可以是持久性的,也可以是临时性的。
-
持久性的znode[PERSISTENT],这个znode一旦创建不会丢失,无论是zookeeper宕机,还是client宕机。
-
临时性的znode[EPHEMERAL],如果zookeeper宕机了,或者client在指定的timeout时间(5s)内没有连接server,都会被认为丢失。
顺序节点:znode也可以是顺序性的,每一个顺序性的znode关联一个唯一的单调递增整数。这个单调递增整数是znode名字的后缀。
-
持久顺序性的znode(PERSISTENT_SEQUENTIAL):znode 处理具备持久性的znode的特点之外,znode的名称具备顺序性
-
临时顺序性的znode(EPHEMERAL_SEQUENTIAL):znode处理具备临时性的znode特点,znode的名称具备顺序性。
3.3java代码操作zookeeper节点
导入依赖
<!--zookeeper的依赖-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.7</version>
</dependency>
<!--zookeeper CuratorFramework
是Netflix公司开发一款连接zookeeper服务的框架,
通过封装的一套高级API 简化了ZooKeeper的操作,
提供了比较全面的功能,除了基础的节点的操作,
节点的监听,还有集群的连接以及重试。-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curatorframework</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curatorrecipes</artifactId>
<version>4.0.0</version>
</dependency>
<!--测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
//RetryPolicy:失败的重试策略公共接口.
//参数1:初始化的sleep时间,用于计算之后的每次重试的sleep时间.
//参数2:最大的重试次数.
创建客户端
CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181",3000,1000,retryPolicy);
//参数1:连接zookeeper服务器的IP端口.
//参数2:会话超时毫秒数.
//参数3:连接超时毫秒数.
//参数4:重试策略.
开启客户端
client.start();
//会阻塞到会话连接成功为止.
创建节点
//1.创建一个空节点(a)(只能创建一层节点)
client.create().forPath("/a"); // 此节点默认会有一个ip值
//2.创建一个有内容的b节点(只能创建一层节点)
client.create().forPath("/b", "hello zookeeper".getBytes());
//3.创建多层节点
client.create().creatingParentsIfNeeded(). //是否需要递归创建节点
withMode(CreateMode.PERSISTENT). //创建持久性节点
forPath("/c1/c2/c3");
//4.创建带有的序号的节点
client.create().creatingParentsIfNeeded(). //是否需要递归创建节点
withMode(CreateMode.PERSISTENT_SEQUENTIAL). //创建持久并带有序号的节点
forPath("/d");
//5.创建临时节点(客户端关闭,节点消失;客户端不关闭,默认5秒后节点消失)
client.create().creatingParentsIfNeeded().
withMode(CreateMode.EPHEMERAL). //创建临时节点
forPath("/e");
//6.创建临时带序号节点(客户端关闭,节点消失;客户端不关闭,默认5秒后节点消失)
client.create().creatingParentsIfNeeded().
withMode(CreateMode.EPHEMERAL_SEQUENTIAL). //创建临时带序号节点
forPath("/f");
修改节点内容
client.setData().forPath("/a", "hello zookeeper".getBytes());
获取节点内容
byte[] bytes = client.getData().forPath("/a");
删除节点
//删除单层节点 若c1节点下有其他节点,则会产生异常
client.delete().forPath("/c1");
//删除节点并递归删除其子节点 若c1节点下有其他节点,则会一并删除
client.delete().deletingChildrenIfNeeded().forPath("/c1");
关闭客户端
client.close();
3.4zookeeper的watch机制
3.4.1介绍
zookeeper作为一款成熟的分布式协调框架,订阅-发布功能是很重要的一个。所谓订阅发布功能,其实说白了就是观察者模式。观察者会订阅一些感兴趣的主题,然后这些主题一旦变化了,就会自动通知到这些观察者。
zookeeper的订阅发布也就是watch机制,是一个轻量级的设计。因为它采用了一种推拉结合的模式。一旦服务端感知主题变了,那么只会发送一个事件类型和节点信息给关注的客户端,而不会包括具体的变更内容,所以事件本身是轻量级的,这就是所谓的“推”部分。然后,收到变更通知的客户端需要自己去拉变更的数据,这就是“拉”部分。watche机制分为添加数据和监听节点。
Curator在这方面做了优化,Curator引入了Cache的概念用来实现对ZooKeeper服务器端进行事件监听。Cache是Curator对事件监听的包装,其对事件的监听可以近似看做是一个本地缓存视图和远程ZooKeeper视图的对比过程。而且Curator会自动的再次监听,我们就不需要自己手动的重复监听了。
Curator中的cache共有三种
-
NodeCache 只监测当前节点
-
PathChildrenCache 只检测自己的子节点(不包含孙子节点)
-
TreeCache 监测当前节点和所有的子节点(包含孙子节点)
3.4.2NodeCache
//创建重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,1);
//创建客户端
CuratorFramework client =CuratorFrameworkFactory.newClient("127.0.0.1:2181", 1000, 1000,retryPolicy);
//开启客户端
client.start();
System.out.println("连接成功");
//创建节点数据监听对象
final NodeCache nodeCache = new NodeCache(client, "/hello");
//开始缓存
/**
* 参数为true:可以直接获取监听的节点
* 参数为false:不可以获取监听的节点
*/
nodeCache.start(true);
System.out.println(nodeCache.getCurrentData());
//添加监听对象
nodeCache.getListenable().addListener(new NodeCacheListener() {
//如果节点数据有变化,会回调该方法
public void nodeChanged() throws Exception {
String data = new String(nodeCache.getCurrentData().getData());
System.out.println("数据Watcher:路径=" + nodeCache.getCurrentData().getPath() + ":data=" + data);
}
});
3.4.3PathChildrenCache
//创建重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,1);
//创建客户端
CuratorFramework client =CuratorFrameworkFactory.newClient("127.0.0.1:2181", 1000, 1000,retryPolicy);
//开启客户端
client.start();
//监听指定节点的子节点变化情况包括hello新增子节点 子节点数据变更 和子节点删除
PathChildrenCache childrenCache = new PathChildrenCache(client,"/hello",true);
/**
* NORMAL: 普通启动方式, 在启动时缓存子节点数据
* POST_INITIALIZED_EVENT:在启动时缓存子节点数据,提示初始化
* BUILD_INITIAL_CACHE: 在启动时什么都不会输出
*/
childrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
//添加监听
childrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
if(event.getType() == PathChildrenCacheEvent.Type.CHILD_ADDED){
System.out.println(event.getData().getPath()+"子节点添加:"+event.getData().getData());
}else if (event.getType() ==PathChildrenCacheEvent.Type.CHILD_REMOVED){
System.out.println(event.getData().getPath()+"子节点移除:"+event.getData().getData());
}else if(event.getType() ==PathChildrenCacheEvent.Type.CHILD_UPDATED){
System.out.println(event.getData().getPath()+"子节点修改:"+event.getData().getData());
}else if(event.getType() == PathChildrenCacheEvent.Type.INITIALIZED){
System.out.println("初始化完成");
}else if(event.getType()==PathChildrenCacheEvent.Type.CONNECTION_SUSPENDED){
System.out.println("连接过时");
}else if(event.getType()==PathChildrenCacheEvent.Type.CONNECTION_RECONNECTED){
System.out.println("重新连接");
}else if(event.getType()==PathChildrenCacheEvent.Type.CONNECTION_LOST){
System.out.println("连接过时一段时间");
}
}
});
3.4.4TreeCache
//创建重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,1);
//创建客户端
CuratorFramework client =CuratorFrameworkFactory.newClient("127.0.0.1:2181", 1000, 1000,retryPolicy);
//开启客户端
client.start();
//监听指定节点的所有子节点变化情况
TreeCache treeCache = new TreeCache(client,"/hello");
treeCache.start();
System.out.println(treeCache.getCurrentData("/hello"));
//配置监听器
treeCache.getListenable().addListener(new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {
if(event.getType() == TreeCacheEvent.Type.NODE_ADDED){
System.out.println(event.getData().getPath() + "节点添加");
}else if (event.getType() == TreeCacheEvent.Type.NODE_REMOVED){
System.out.println(event.getData().getPath() + "节点移除");
}else if(event.getType() == TreeCacheEvent.Type.NODE_UPDATED){
System.out.println(event.getData().getPath() + "节点修改");
}else if(event.getType() == TreeCacheEvent.Type.INITIALIZED){
System.out.println("初始化完成");
}else if(event.getType()==TreeCacheEvent.Type.CONNECTION_SUSPENDED){
System.out.println("连接过时");
}else if(event.getType()==TreeCacheEvent.Type.CONNECTION_RECONNECTED){
System.out.println("重新连接");
}else if(event.getType() ==TreeCacheEvent.Type.CONNECTION_LOST){
System.out.println("连接过时一段时间");
}
}
});
4.集群
4.1了解zookeeper集群
集群介绍
Zookeeper 集群搭建指的是 ZooKeeper 分布式模式安装。 通常由 2n+1台 servers 组成。 这是因为为了保证 Leader 选举(基于 Paxos 算法的实现) 能过得到多数的支持,所以 ZooKeeper 集群的数量一般为奇数。
集群模式
ZooKeeper集群搭建有两种方式:
-
伪分布式集群搭建:
所谓伪分布式集群搭建ZooKeeper集群其实就是在一台机器上启动多个ZooKeeper,在启动每个ZooKeeper时分别使用不同的配置文件zoo.cfg来启动,每个配置文件使用不同的配置参数(clientPort端口号、dataDir数据目录.在zoo.cfg中配置多个server.id,其中ip都是当前机器,而端口各不相同,启动时就是伪集群模式了。这种模式和单机模式产生的问题是一样的。也是用在测试环境中使用。
-
完全分布式集群搭建:
多台机器各自配置zoo.cfg文件,将各自互相加入服务器列表,每个节点占用一台机器
4.2架构图
Leader:Zookeeper(领导者): 集群工作的核心 事务请求(写操作) 的唯一调度和处理者,保证集群事务处理的顺序性; 集群内部各个服务器的调度者。 对于 create, setData, delete 等有写操作的请求,则需要统一转发给leader 处理, leader 需要决定编号、执行操作,这个过程称为一个事务。
Follower(跟随者)处理客户端非事务(读操作) 请求,转发事务请求给 Leader;参与集群 Leader 选举投票 2n-1台可以做集群投票。
Observer:观察者角色 针对访问量比较大的 zookeeper 集群, 还可新增观察者角色。观察 Zookeeper 集群的最新状态变化并将这些状态同步过来,其对于非事务请求可以进行独立处理,对于事务请求,则会转发给 Leader服务器进行处理。 不会参与任何形式的投票只提供非事务服务,通常用于在不影响集群事务处理能力的前提下提升集群的非事务处理能力。
4.3搭建集群
1.准备3台虚拟机
虚拟机上需要安装jdk环境。
虚拟机的网络连接方式使用桥接模式,因为三台服务器之间需要进行通信。
关闭防火墙。
2.安装zookeeper
命令:cd usr/local (进入local路径)
命令:tar -zxvf apache-zookeeper-3.5.5.bin.tar.gz -C /usr/local(解压)
命令:mv apache-zookeeper-3.5.5.bin zookeeper (重命名文件夹)
3.配置zookeeper
(1)创建data目录。
命令:cd zookeeper (进入zookeeper目录)
命令:mkdir data
(2)设置conf/zoo.cfg
命令:cd conf (进入conf目录)
命令:cp zoo_sample.cfg zoo.cfg(复制zoo_sample.cfg,文件名为zoo.cfg)
(3)修改zoo.cfg
#zookeeper保存数据的位置
dataDir=/usr/local/zookeeper/data
#当前zookeeper端口号
clientPort=2181
#集群机器配置
server.1=192.168.174.128:2182:3182
server.2=192.168.174.129:2182:3182
server.3=192.168.174.130:2182:3182
2182:端口号是自己定义的,用来选举领导。
3182:端口号是自己定义的,用来服务器之间通信。
(4) 在data目录创建文件myid
命令:cd .. (退出conf目录)
命令: cd data (进入data目录)
命令:vim myid(修改myid文件,分别设置为1,2,3)或使用 cat 1 >> myid 命令
4.启动zookeeper
命令:cd .. (退出data目录)
命令:cd bin(进入bin路径)
命令: ./zkServer.sh start
5.查看zookeeper状态
命令:./zkServer.sh status
4.4leader选举
在领导者选举的过程中,如果某台ZooKeeper获得了超过半数的选票,则此ZooKeeper就可以成为Leader了。
(1)服务器1启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器1的状态一直属于Looking(选举状态)。
(2)服务器2启动,给自己投票,同时与之前启动的服务器1交换结果, 每个Server发出一个投票由于是初始情况,1和2都会将自己作为Leader服务器来进行投票,每次投票会包含所推举的服务器的myid和ZXID,使用(myid, ZXID)来表示,此时1的投票为(1,0),2的投票为(2, 0),然后各自将这个投票发给集群中其他机器。 接受来自各个服务器的投票集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票、是否来自LOOKING状态的服务器
处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK,PK规则如下
-
优先检查ZXID。ZXID比较大的服务器优先作为Leader。
-
如果ZXID相同,那么就比较myid。myid较大的服务器作为Leader服务器
由于服务器2的编号大,更新自己的投票为(2, 1),然后重新投票,对于2而言,其无须更新自己的投票,只是再次向集群中所有机器发出上一次投票信息即可,此时集群节点状态为LOOKING。 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息
(3)服务器3启动,进行统计后,判断是否已经有过半机器接受到相同的投票信息,对于1、2、3而言,已统计出集群中已经有3台机器接受了(3, 0)的投票信息,此时投票数正好大于半数,便认为已经选出了Leader,
改变服务器状态。一旦确定了Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为FOLLOWING,如果是Leader,就变更为LEADING所以服务器3成为领导者,服务器1,2成为从节点。
ZXID: 即zookeeper事务id号。ZooKeeper状态的每一次改变, 都对应着一个递增的Transactionid, 该id称为zxid