目录
1.基本概念
2.Zookeeper常用命令
3.Zookeeper的数据结构
保存数据
组成部分
zk中节点znode的类型
Zk的数据持久化
4.Zookeeper客户端(zkCli)的使用
节点操作
Stat结构体(数据元数据)
权限设置
5.Curator客户端使用(Java使用客户端)
Curator CURD API:
6.Zookeeper分布式锁
1)zk如何上读锁
2)zk如何上写锁
3)watch机制
Curator客户端使用watch
Curator实现读写锁
7.Zookeeper集群
1)集群搭建,连接
8.ZAB协议
Leader选举过程
主从服务器数据同步
8.CAP理论
BASE理论
Zookeeper 是一个开源的分布式的,为分布式应用提供协调服务的 Apache 项目。
Zookeeper 从设计模式角度来理解:是一个基于观案者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper 就将负责通知已经在 Zookeeper 上注册的那些观察者做出相应的反应。
Zookeeper是基于Java编写的,同时也需要Java依赖,安装的话需要访问 Zookeeper官网
下载完后将压缩包进行解压,如果是windows系统还需要配置环境.
配置Zookeeper的config文件,在conf文件夹里有一个配置好的模板,copy拿过来直接用,进行部分修改就行了.
# The number of milliseconds of each tick
#毫秒为单位,表示连接最大延迟时间为两秒
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
#从新连接次数
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
#数据文件存储路径
dataDir=F:\zookeeper\apache-zookeeper-3.8.0-bin\data
#日志存储路径
dataLogDir=F:\zookeeper\apache-zookeeper-3.8.0-bin\log
# the port at which the clients will connect
#zookeeper服务端口
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# https://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1
## Metrics Providers
#
# https://prometheus.io Metrics Exporter
#metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider
#metricsProvider.httpHost=0.0.0.0
#metricsProvider.httpPort=7000
#metricsProvider.exportJvmInfo=true
配置好Zookeeper后,启动zkServer(服务端),然后再启动zkCli(客户端),顺序不对客户端会报错,问题也不大.
ls /:用在客户端的命令.用来查看当前连接的服务端所有节点
creat /test1:创建一个节点
create /test2 abc:创建一个节点,并且保存一个abc的数据
get /test2:拿test2中保存的数据.
delete /test:删除指定节点
ZooKeeper 数据模型的结构与 Unix 文件系统很类似,整体上可以看作是一棵树,每个节点称做一个 ZNode。每一个 ZNode 默认能够存储 1 MB 的数据,每个 ZNode 都可以通过其路径唯一标识。
zk中的znode,包含四个部分:
data:保存数据
acl:权限,定义了什么样的用户能够操作这个节点,能够进行什么样的操作
stat:描述当前znode的元数据,可以通过get -s /test2进行查看存储数据的元数据
child:当前节点的子节点
持久节点:创建出的节点,会话结束后依然会存在,保存数据 (节点名不能重复)
持久化序号节点:create -s /test1 创建出的节点,根据先后顺序.会在节点之后带上一个数值,越靠后数值越大,适用于分布式锁的应用场景. (节点名可以重复)
临时节点:create -e /test1 当前会话结束后会自动删除,zk可以通过这个特性,具有服务注册与发现的效果,类似cloud的注册中心,服务给注册中心发送心跳原理一样.
临时序号节点:create -e -s /test1 跟持久序号节点一样,而且会结束后自动删除,使用于临时的分布式锁.
Container节点:create -c /test1 Container容器节点,当容器没有任何子节点,会被zk定期删除(60s)
TTL节点:可以指定节点的到期时间,到期后会被定时删除,只能通过系统配置zookeeper.extendedTypesEnable=true开启.
zk的数据是运行在内存中的,zk提供了两种持久化机制.
事务日志
zk把执行的命令以日志的形式保存在dataLogDir指定的路径中
数据快照
zk会在一定的时间间隔内做一次内存数据快照,把时刻的内存数据保存在快照文件中.
zk通过两种形式的持久化,在恢复时先恢复快照文件中的数据到内存,再用日志文件中的数据做增量恢复,这样恢复速度更快.
zookeeperapache-zookeeper-3.8.0-bindata(保存的快照文件 snapshot.0)
zookeeperapache-zookeeper-3.8.0-binlog(保存的日志文件log.1)
查询节点:
ls -R /test1:递归查询,查询结果
get -s /test1:查询节点的元数据
删除节点:
delete /test:删除指定节点,如果节点下有子节点则会失败.
deleteall /test:删除该节点包括子节点.
delete -v 1 /test:(乐观锁删除)根据版本号匹配删除数据,根据dataversion来判断是否一样.
乐观锁删除:乐观的认为现在线程不是很多,举个例子:
当前我根据版本号去删,如果没有其他线程进行操作,版本号是0,删除成功.
当有线程进行操作,版本号为1,删除失败,则加1继续进行删除,如果又有线程则可以继续加1删除.
czxid: 创建节点的事务 zxid
每次修改 ZooKeeper 状态都会收到一个 zxid 形式的时间戳,也就是 ZooKeepe r事务 ID。
事务 ID 是 ZooKeeper 中所有修改总的次序。每个修改都有唯一的 zxid,若 zxid1 小于 zxid2,那么 zxid1 在 zxid2 之前发生。
ctime: znode 被创建的毫秒数(从 1970 年开始)
mzxid: znode 最后更新的事务 zxid
mtime: znode 最后修改的毫秒数(从 1970 年开始)
pZxid: znode 最后更新的子节点 zxid
cversion : znode 子节点变化号,znode 子节点修改次数
dataversion: znode 数据变化号
aclVersion: znode 访问控制列表的变化号
ephemeralOwner: 如果是临时节点,这个是 znode 拥有者的 session id。如果不是临时节点则是 0。
dataLength: znode 的数据长度
numChildren: znode 子节点数量
登录用户名和密码:addauth digest Vermouth:123
创建节点时设置用户和相应权限:create /test abc auth:Vermouth:123:cdrwa
当节点被设置权限后,拥有相应权限才能进行相应操作,其他用户无权进行额外操作,除非登录拥有当前节点权限的账户.
1.引入依赖
org.apache.zookeeper
zookeeper
3.7.0
org.apache.curator
curator-recipes
5.2.0
org.apache.curator
curator-framework
5.2.0
2.配置文件
#重试次数
curator.retryCount=5
#超时时间
curator.elapsedTimeMs=5000
#服务端连接地址
curator.connectString=localhost:2181
#会话超时时间
curator,sessionTimeoutMs=60000
#连接超时时间
curator.connectionTimeoutMs=5000
3.读取配置文件到程序中
/**
* 读取配置文件类
*/
@Data
@Component
@ConfigurationProperties(prefix = "curator")
public class WrapperZk {
private int retryCount;
private int elapsedTimeMs;
private String connectString;
private int sessionTimeoutMs;
private int connectionTimeoutMs;
}
4.配置Curator
/**
* 配置Curator
*/
@Configuration
public class CuratorConfig {
/**
* 拿到配置值
*/
@Autowired
private WrapperZk wrapperZk;
/**
* 配置Curator的参数
* 同时还会调用它的初始化方法
*
* @return
*/
@Bean(initMethod = "start")
public CuratorFramework curatorFramework() {
return CuratorFrameworkFactory.newClient(
wrapperZk.getConnectString(),
wrapperZk.getSessionTimeoutMs(),
wrapperZk.getConnectionTimeoutMs(),
new RetryNTimes(wrapperZk.getRetryCount(), wrapperZk.getElapsedTimeMs())
);
}
}
5.注入Curator,进行功能调用
@RestController
public class MyTest {
@Autowired
private CuratorFramework curatorFramework;
@RequestMapping("create")
public String createZnode() throws Exception {
//创建一个临时节点
String path = curatorFramework.create().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath("/Mynode", "some-data".getBytes());
return String.format("curator create node %s successfully", path);
}
}
调用结果:
客户端查看节点:
添加成功了,当我们退出后临时节点就会自动删除.
当然Curator还有很多调用方法可以操作,就不一一演示,具体的直接百度就ok了.
上面是配置文件的方法,还是有点复杂,但是可以通过配置文件进行更改连接,同时我们也可以在程序中直接创建客户端.
不使用配置文件创建:
public CuratorFramework getFramework(){
//重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
//工厂构建
CuratorFramework clientFramework = CuratorFrameworkFactory.builder()
//设置Zookeeper服务地址端口
.connectString(host)
.sessionTimeoutMs(5000) // 会话超时时间
.connectionTimeoutMs(5000) // 连接超时时间
.retryPolicy(retryPolicy) //设置重试策略
.build();
client.start(); //启动客户端
}
================================================
//另一种创建代码如下所示,不推荐使用该方法
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework clientFramework = CuratorFrameworkFactory.newClient("host",5000, 5000, retryPolicy);
Curator全部使用fluent风格进行编程,分别提供了create()、setData()、getData()、getChildren()、delete()方法创建、修改、获取删除节点,都使用forPath()方法传入节点。代码如下所示:
CuratorFramework framework = CuratorFrameworkClient.getFramework();
//创建阶段
framework.create()
.creatingParentContainersIfNeeded() //如果必须创建父节点
.withMode(CreateMode.PERSISTENT) //节点类型
.withACL(null) //权限
.forPath("/createNode/node"); //节点
//修改节点数据
framework.setData() //修改节点
.forPath("/createNode/node", "node1".getBytes());
//获取节点数据
framework.getData()
.forPath("/createNode");
//获取子节点
framework.getChildren()
.forPath("/createNode");
//删除节点
framework.delete()
.deletingChildrenIfNeeded() //如果必须删除子节点
.forPath("/createNode");
故名思意,就是给服务上锁,分别有读锁和写锁,而执行服务的时候就必须要持有锁才能进行操作,读锁可以共享,但是写锁不行,读读相容,读写互斥,写写互斥.
watch机制就是当指定的Znode节点发生变化时,会触发Znode上注册的对应事件,请求watch的客户端会接收到异步通知.
zkCli命令:
get -w /test:监听一次当前节点 (./)
ls -w /test:监听目录,创建和删除子节点会收到通知,子节点中新增节点不会收到通知.(../)
ls -R -w /test:接收子节点中子节点的变化,内容变化不会通知.(../ ./)
例如:
客户端1:get -w /test
客户端2:set /test abc
这时候客户端1会收到watch提示,再次:get -w /test,则可以拿到值,并继续监听
如果节点被删除或者创建,也会收到通知.
其实zk在内部也维护着一个监听列表,当节点发送变化时会记录下来,当被watch的znode被删除,服务端会查找hash表,找到该znode对应的所有watcher,异步通知客户端,并且删除hash表中对应的k-v.
相对于ZkClient的监听器,Curator的监听模式更为强大,不仅可以监听节点,可以监听子节点的变化,还能监听子节点的值的变化。Curator是采用cache的模式对节点进行监听的,如下代码分别是Curator的三种监听方式
原来的NodeCache已经弃用了,所以要监听要使用其他方式Curator新增的CuratorCache应该使用来怎样替代原先的
优雅的替换NodeCache和其他几种节点存储类.
@RequestMapping("lister")
public void listenerZnode() {
//前置条件
//服务端地址
String conncet = "127.0.0.1:2181";
//需要监听的节点
String path = "/test";
//创建客户端连接,开启客户端
CuratorFramework client = CuratorFrameworkFactory.newClient(conncet, new ExponentialBackoffRetry(1000, 3));
client.start();
//优雅的替换NodeCache
CuratorCache curatorCache = CuratorCache.builder(client, path).build();
//设置监听器,当节点发送变化执行监听器的方法
CuratorCacheListener listener = CuratorCacheListener
.builder()
.forNodeCache(new NodeCacheListener() {
public void nodeChanged() throws Exception {
//TODO ...
//这里我们就读取更改的数据就行了,表示我们监听到了
byte[] getMessage = curatorFramework.getData().forPath("/test");
System.out.println(new String(getMessage));
}
})
.build();
//将创建好的监听器放入监听容器中
curatorCache.listenable().addListener(listener);
//开启watch功能
curatorCache.start();
}
显示效果:
监听状况:
这样我们就可以在Java程序中对Znode节点进行监控了,为下面分布式锁做铺垫.
读锁:
@Autowired
private CuratorFramework curatorFramework;
public void readLock() throws Exception {
InterProcessReadWriteLock lock = new InterProcessReadWriteLock(curatorFramework, "/lock");
//拿到读锁
InterProcessMutex readLock = lock.readLock();
System.out.println("获取读锁");
//尝试获取锁
readLock.acquire();
for (int i = 0; i < 100; i++) {
Thread.sleep(3000);
System.out.println(i);
}
//释放锁
readLock.release();
}
写锁:
public void writeLock() throws Exception {
InterProcessReadWriteLock lock = new InterProcessReadWriteLock(curatorFramework, "/lock");
//拿到写锁
InterProcessMutex writeLock = lock.writeLock();
System.out.println("获取写锁");
//尝试去获取锁
writeLock.acquire();
for (int i = 0; i < 100; i++) {
Thread.sleep(3000);
System.out.println(i);
}
//释放锁
writeLock.release();
}
在读写锁的方法中,我们要使用同一个节点来当作我们的锁对象,当读锁被持有的时候不能写,写锁被持有的时候不能读.
Zookeeper集群中的节点有三种角色.
如果需要搭建集群,则要开启多个Zookeeper服务端,并且要相互建立连接,需要修改Zookeeper的配置文件.
伪集群, 是指在单台机器中启动多个zookeeper进程, 并组成一个集群. 以启动3个zookeeper进程为例.
将zookeeper的目录拷贝2份:
|–zookeeper-3.4.10-0
|–zookeeper-3.4.10-1
|–zookeeper-3.4.10-2
1. 配置数据和日志存放路径
与单机配置类似,只是3个zookeeper不能配置同一个目录下,配置如下
修改dataDir部分,添加
# 1 | 2
server.1=localhost:2001:3001
server.2=localhost:2002:3002
server.3=localhost:2003:3003:observer
server.A=B:C:D:其中 A 是一个数字,表示这个是第几号服务器;B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。如果是伪集群的配置方式,由于 B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,所以要给它们分配不同的端口号。
第一排的端口号是用来同步数据的通信端口,第二排的端口是用来选举的选举端口,哪个节点获得半数票就会当选leader.
在之前设置的dataDir中新建myid文件, 写入一个数字, 该数字表示这是第几号server. 该数字必须和zoo.cfg文件中的server.X中的X一一对应.只有dataDir和dataLogDir配置到不同的目录
最后就将clientPort改成不充分的端口,方便客户端能识别访问.
示例:
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=F:\zookeeper\apache-zookeeper-3.8.0-bin\data
dataLogDir=F:\zookeeper\apache-zookeeper-3.8.0-bin\log
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# https://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1
## Metrics Providers
#
# https://prometheus.io Metrics Exporter
#metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider
#metricsProvider.httpHost=0.0.0.0
#metricsProvider.httpPort=7000
#metricsProvider.exportJvmInfo=true
server.1=127.0.0.1:2001:3001
server.2=127.0.0.1:2002:3002
server.3=127.0.0.1:2003:3003:observer
修改好三个配置文件后,用命令启动三台服务器.(如果是windows下运行需要将zookeeper文件夹创建三份分开运行才行)
必须要搭起一主两从.
Zookeeper作为重要的分布式协调组件,需要进行集群部署,集群中会以一主多从的形式部署,为了保证数据的一致性,使用了ZAB(Zookeeper Atomic Broadcast)协议,这个协议解决了Zookeeper的崩溃恢复和主从数据同步的问题.
ZAB协议定义的四种状态
当每个服务节点刚上线的时候都是Looking状态,选举完毕后会进入相应的状态.
当第一台服务上线的时候,它是Looking(巡视状态),并没有找到任何节点可以进行投票或者选举.
当第二台服务上线的时候它也是Looking,但是现在有两台服务节点,就会产生选举.
选票格式:myid+zXid
myid就是当前服务节点的id,而zXid是当前节点每改变一次就+1.
第一轮投票:
第一轮投票结束后,如果票箱内票数未过所有节点的一半,则会进行第二轮选举.
第二轮投票:
选举结束,选出票数最多的服务节点为leader.
崩溃恢复时的Leader选举
Leader服务节点会周期性的向Follower发送ping命令,告诉从节点主节点存活,维持Leader的心跳.
如果一段时间Follower未收到leader的心跳,则Follwer会去周期性的尝试读socket,如果leader真的挂了,那么读取socket会抛出异常,那么Follower就会进入到Looking状态.
然后开始投票,选出新的Leader.
在集群中,通常都是客户端可以访问任意服务节点,但是写的话必须要leader来进行操作.
客户端访问leader写入数据:
如果客户端访问Follower节点写数据,则也会交给主节点来写入.
CAP理论指的是,在一个分布式系统中最多只能同时满足,Consistency(一致性),Availability(可用性),Partition tolerance(分区容错性)这三项中的任意两项.
一致性(C):更新操作成功并且返回给客户端完成后,所有节点在同一时间的数据完全一致.
可用性(A):服务一直可用,而且是正常响应时间.
分区容错性(P):分布式系统在遇到某个节点或某个网络分区故障的时候,任然能够对外提供满足一致性可用性的服务.----避免单点故障,就要进行冗余部署(一个服务备份多个),这样的分区具备容错性.
CAP权衡
通过CAP理论,我们无法同时满足上面的三种特性,所以必须要安装实际情况来取舍.
比如银行这种系统,必须要保证数据的一致性和可用性,既CA,或者也可以通过CP,只读不写,否则将是灾难.
而电商或者用户不在意数据,只在意服务的,就只要保证分区容错性和可用性,保证一定有服务来第一时间响应用户,但是用户的数据可能会丢失,影响体验.
Base理论是对CAP理论的延申,核心思想是即使无法做到强一致,但应用可用采用合适的方式达到最终一致性.
基本可用(Basically Available)
基本可用是指分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用.
例如在双11的时候,部分退款,评论,部分客服会被降级处理,等到高峰期过去才会开启.
软状态(Soft State)
软状态是指允许系统存在中间状态,而中间状态不会影响系统整体的可用性,也可以理解为异步存储,允许不同节点间副本同步的延时.
最终一致性(Eventual Consistency)
最终一致性指系统中的所有数据副本经过一定时间后,最终达到一致的状态,弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况.
Zookeeper在数据同步时,追求的不是强一致性,而是顺序一致性(事务id/*zXid*/的单调递增)
因为Zookeeper在写入数据时只要收到半数以上的ACK时就会写入数据到内存中,用户就可以读取,这就不是强一致性(强一致性必须要所有节点数据同步后才能写入,但是响应时间就延长了),而数据变化的节点事务id会递增,每变化一次递增一次,未成功写入的则不会变化,但当收到数据后依然会写入本地数据进行同步,并且事务id也会变化,最终如果事务id和leader一样就能保证数据的最终一致性.