应用场景
分布式锁:可以做到强一致性
无状态化的实现:数据存在zookeeper中
数据结构
zk的节点znode包含了4个部分
data,保存的数据
acl,权限
stat,描述当前节点的元数据
child,子节点
znode的类型
持久节点:create /test 创建出持久化的节点,会话结束后依旧存在
持久序号节点:创建出的节点根据先后顺序会在节点后面加一个数值,越靠后越大,( create -s /test)
适用 分布式锁
临时节点:会话结束后,节点会被自动删除(create -e /test)
适用服务的注册与发现
手动退出直接触发会话删除,其他情况客户端会话断开,临时节点会超时删除、
临时序号节点:适用于做临时分布式锁 (create -e - s /test)
Container节点:当容器节点没有任何子节点的时候就会定期删除(默认为60s) (create -c /myContainer)
TTL节点:可以给节点设置到期时间,到期后被定时删除
持久化机制
zk是运行在内存中的,提供两种持久化机制
1.事务日志
zk把执行的命令以日志形式记录在dataLogDir指定的路径中
2.数据快照
zk在一定的时间间隔做一次内存数据的快照,把此事的内存数据保存在快照文件中
在数据恢复的时候先恢复快照文件中的数据,然后根据日志文件中的数据做增量恢复
zkCli命令的使用
创建
create
create -s
create -e
create -e -s
create -c
create -t TTL
查询
ls
递归查询ls -R
获得节点数据 get
获取节点详细信息 get - s
删除
delete /test(删除当前节点)
deleteAll /test (删除这个节点,以及所有子节点)
delete -v number /test (如果test的版本号和number相同就能删除,乐观锁)
权限
Curator客户端
引入依赖
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.7.2</version>
</dependency>
<!--zookeeper-client-curator-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
加入配置
curator.retryCount=5
curator.elapsedTimeMs=5000
curator.connectString=192.168.88.130:2181
curator.sessionTimeoutMs=6000
curator.connectionTimeoutMs=5000
注册bean接收请求
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "curator")
public class WrapperZK {
private int retryCount;
private int elapsedTimeMs;
private String connectString;
private int SessionTimeoutMs;
private int connectionTimeoutMs;
}
注册Curator
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
@Configuration
public class CuratorConfig {
@Resource
private WrapperZK wrapperZK;
@Bean(initMethod = "start")
public CuratorFramework getCuratorFramework() {
return CuratorFrameworkFactory.newClient(
wrapperZK.getConnectString(),
wrapperZK.getSessionTimeoutMs(),
wrapperZK.getConnectionTimeoutMs(),
new RetryNTimes(wrapperZK.getRetryCount(), wrapperZK.getElapsedTimeMs()));
}
}
zk的分布式锁
读锁:前提之前没有写锁
写锁:前提之前没有任何锁
羊群效应:所有的节点都监听获取锁的那个节点,当节点释放锁,就会触发其他所有节点去抢占锁
链式监听:因为所有的节点都有序号,所以可以采用链式监听,按照顺序获取锁,这样占有锁的节点只被一个节点监听,当释放锁的时候不会触发大量的抢锁,减少了CPU资源的消耗;
zk的Watch机制
客户端功能,能够监听znode的变化:当znode发生create,delete、setData时,客户端就会接收到异步通知
原理:服务端会维护一个哈希表,当有事件监听的时候,znode对应的节点就会插入watch的客户端列表,
当发生事件的时候,就会获取节点的watch列表进行通知
命令:
get -w /node 监听节点
get -w -R /node 监听节点及所有子节点
ls -w /node 监听目录中下一级子节点是否发生变化
ls -R -w /node 监听所有层级子节点
zk的集群
zk集群中有三种角色:
leader:处理集群中的所有事务请求(可写可读),一个集群只能由一个leader
follower:只能处理读请求,能参与leader的选举
Observer:只能处理读请求,不能参与leader的选举
ZAB协议
ZAB协议用来解决崩溃恢复和主从同步的问题
ZAB协议定义了四种节点状态:looking:选举状态 leading:主节点状态 followin :从节点状态 observering观察者状态
内部选举
在分布式系统中选主最直接的方法是直接选定集群的一个节点为leader,其它的节点为follower,这样引入的一个问题是如果leader节点挂掉,整个集群就挂掉了。需要有一种算法自动选主,如果leader节点挂掉,则从follower节点中选出一个主节点。
1. 选举阶段 Leader election
最大ZXID也就是节点本地的最新事务编号,包含epoch和计数两部分。epoch是纪元的意思,相当于Raft算法选主时候的term,标识当前leader周期,每次选举一个新的Leader服务器后,会生成一个新的epoch
- 所有节点处于Looking状态,各自依次发起投票,投票包含自己的服务器ID和最新事务ID(ZXID)。
- 如果发现别人的ZXID比自己大,也就是数据比自己新,那么就重新发起投票,投票给目前已知最大的ZXID所属节点。
- 每次投票后,服务器都会统计投票数量,判断是否有某个节点得到半数以上的投票。如果存在这样的节点,该节点将会成为准Leader,状态变为Leading。其他节点的状态变为Following。
2. 发现阶段 Discovery
- 为了防止某些意外情况,比如因网络原因在上一阶段产生多个Leader的情况。
- Leader集思广益,接收所有Follower发来各自的最新epoch值。Leader从中选出最大的epoch,基于此值加1,生成新的epoch分发给各个Follower。
- 各个Follower收到全新的epoch后,返回ACK给Leader,带上各自最大的ZXID和历史事务日志。Leader选出最大的ZXID,并更新自身历史日志。
3. 同步阶段 Synchronization
Leader刚才收集得到的最新历史事务日志,同步给集群中所有的Follower。只有当半数Follower同步成功,这个准Leader才能成为正式的Leader。