zookeeper从入门到熟练使用
zookeeper是一种分布式协调服务,用于管理大型主机。zk通过其架构和API解决了分布式环境中协调和管理服务中的问题。让开发人员不再担心应用程序的分布式特性,专注于应用的逻辑。
一、zookeeper的应用场景
1.分布式协调组件:通过nginx做负载均衡然后冗余部署2个相同的服务,两个服务中都有个flag标记,当A服务中的flag变成false的时候,两个服务中的数据就不一致了,通过zookeeper监听到A服务flag标记变了,通知服务b也把flag改为false,zookeeperwatch机制保证了数据的一致性。
2.无状态化的实现:冗余部署3个登录系统,登录信息保存在数据中心zookeeper中,对于一个登录系统不需要关心登录状态了,登录状态在zookeeper中维护了,这样实现了这几个系统的无状态话。
3.分布式锁:强一致性(顺序一致性)。分布式锁:放在zookeeper中,要去zookeeper中拿锁才能执行业务。
二、搭建zookeeper服务器
1.https://zookeeper.apache.org/ 进入到官网,选这个发行版

2.下载一个最新版的

然后在linux中随便创建一个文件夹将刚才的压缩包丢进去,tar -zvxf 文件名解压,解压完之后把压缩文件删除了
3.进入conf目录,将zoo_sample.cfg改名为zoo.cfg,再vim编辑zoo.cfg

修改一下zookeeper数据文件和日志文件保存的位置,第二项客户端端口号不用修改,我是因为装了tomcat才加了第三条(因为tomcat和zookeeper默认端口都是8080,安装了tomcat会导致zookeeper启动不了。)
注意!!!!:集群搭建的时候不要第三句,想要tomcat的话可以去该tomcat的端口号,在阿里云上面搭建的话要特别注意,ip地址不是本机网卡(每个配置文件加上quorumListenOnAllIPs=true)
:wq保存退出
cd ../bin进入bin目录
./zkServer.sh start ../conf/zoo.cfg 指定配置文件启动
./zkServer.sh status ../conf/zoo.cfg查看zk服务器状态
./zkServer.sh stop ../conf/zoo.cfg关闭zk服务器
发现启动不了,没有开端口号
1、开启防火墙
systemctl start firewalld
2、开放指定端口
firewall-cmd --zone=public --add-port=8543/tcp --permanent
命令含义:
--zone #作用域
--add-port=8543/tcp #添加端口,格式为:端口/通讯协议
--permanent #永久生效,没有此参数重启后失效
3、重启防火墙
firewall-cmd --reload
4. 查看端口号开放情况
firewall-cmd --list-ports
再次启动搞定,./zkCli.sh进入客户端 ls / 查看根目录下目录列表,输入help命令会给出提示可以操作的命令,安装完成之后暂时先这样了,因为zookeeper都是搭配其他组件使用的,之后会写kafka中会使用。
ls -R /test递归查询
三、zookeeper内部的数据模型
1.zk中的数据是保存在节点上的,默认根节点是/,zk的数据模型类似树结构,树由节点组成,节点叫znode
创建节点:create /test
在节点中创建数据:create /test abc 拿数据:get /test 获取节点详细信息(就是stat元数据):get -s /test
2.znode结构
data:保存数据
acl:权限
c创建、w写、r读、d删、a:admin权限,允许对该节点进行acl权限设置
stat:当前znode的元数据
child:当前节点的子节点
3.zk中节点znode的类型
通过zk客户端在zk服务器中创建节点
持久节点:create /test一直存在即使会话结束,用于保存数据。zk服务器会返回一个永久的sessionid给zk客户端,
持久序号节点:create -s /test创建出的节点,会根据先后顺序,在节点后带上一个事务序号,越后执行数值越大,单调递增,高并发场景下,谁先谁后,适用于分布式锁的应用场景
临时节点:create -e /test在会话结束后,会被自动删除。zk服务器会返回一个有失效时间的sessionid给zk客户端,持续会话会不断的ping维持心跳,增加续约sessionid的时间,会话断开就不会去ping消息续约了,zk服务器会周期性自动删除没有续约的sessionid对应的临时节点。服务提供者通过创建临时节点,实现服务的注册于发现,提供者断开连接,临时连接被删除,消费者就不能找到服务提供者调用服务了。
临时序号节点 :create -e -s /test
Container节点:create -c /test当容器中没有任何子节点,该容器节点会被zk定期删除(60s)
TTL节点:直接节点到期时间,到期后被zk定时删除,通过系统配置zookeeper.extendedTypesEnable=true开启
4.zk的数据持久化
zk的数据是运行在内存中,zk提供两种持久化机制:
事务日志:zk把执行的命令以日志的形式保存在dataLogDir指定的文件中,当前没有指定,则保存在dataDir指定的路径中。
数据快照:zk会在一定时间内做一次内存数据的快照,把该时刻的内存数据保存在快照文件中

zk在恢复时先恢复快照文件中的数据到内存中,再用日志文件中的数据做增量恢复,恢复速度快。
四、zookeeper客户端的使用
1.多节点类型的创建略
2.查询节点 ls /ls -R ,获取数据get,详细查询get -s ,往节点里面存数据set
3.删除节点 :
普通删除:delete
乐观锁删除:delete -v 版本号 节点
4.权限设置:
注册当前会话的账号和密码:addauth digest mj:999
创建节点并设置权限: create /test 666 auth:mj:999:cdwra
在另外一个会话中必须先使用账号密码(addauth digest mj:999),才能够拥有操作该节点的权限。
五、Curator客户端的使用
curator是Netflix公司开源的一套zookeeper客户端框架,封装了大部分zookeeper的功能,如选举、分布式锁等。
1.引入依赖
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j</artifactId>
<version>2.14.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
2.配置文件中配置curator的属性集
curator.retryCount=5
curator.elapsedTimeMs=5000
curator.connectString=121.43.37.22:2181
curator.sessionTimeoutMs=6000
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配置类
@Configuration
public class CuratorConfig {
@Autowired
WrapperZK wrapperZK;
@Bean(initMethod = "start")
public CuratorFramework curatorFramework(){
return CuratorFrameworkFactory.newClient(
wrapperZK.getConnectString(),
wrapperZK.getSessionTimeoutMs(),
wrapperZK.getConnectionTimeoutMs(),
new RetryNTimes(wrapperZK.getRetryCount(),wrapperZK.getElapsedTimeMs())
);
}
}
5.测试一些基本的增删改查
@Slf4j
@SpringBootTest
class ZkcuratorApplicationTests {
@Autowired
CuratorFramework curatorFramework;
@Test
void createNode ()throws Exception {
//添加持久节点
String path = curatorFramework.create().forPath("/curator-node2");
//添加临时序号节点
String path1 = curatorFramework.create().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath("/curator-node", "some-data".getBytes());
System.out.println(String.format("curator create node :%s successfully.",path));
System.in.read();
}
@Test
public void testGetData () throws Exception {
byte[] bytes = curatorFramework.getData().forPath("/curator-node");
System.out.println(new String(bytes));
}
@Test
public void setData() throws Exception {
curatorFramework.setData().forPath("/curator-node2","大力3".getBytes());
byte[] bytes = curatorFramework.getData().forPath("/curator-node2");
System.out.println(new String(bytes));
}
@Test
public void testCreateWithParent() throws Exception {
String pathWithParent="/node-parent/sub-node-1";
String path=curatorFramework.create().creatingParentsIfNeeded().forPath(pathWithParent);
System.out.println(String.format("curator create node :%s successfully",path));
}
@Test
public void testDelete() throws Exception {
String parentWithParent="/node-parent";
curatorFramework.delete().guaranteed().deletingChildrenIfNeeded().forPath(parentWithParent);
}
}
六、zk实现分布式锁
1.zk中锁的种类:读锁(共享锁):都可以读,上读锁的前提是没有上写锁。
写锁:只有得到写锁的才能写,上写锁的前提是没有上任何锁。(举一个栗子:读锁:小王没有结婚,大家都可以找她约会,写锁:小王要跟小明结婚了,就不能跟其他人约会了,只能跟小明约会了)
2.zk上读锁:
创建一个临时序号节点,节点的数据是read,表示读锁
获取当前zk中序号比自己小的所有节点
判断最小节点是否是读锁:如果是读锁,则上锁成功。
如果是写锁,则上锁失败,为最小节点设置监听,阻塞等待,zk的watch机制会当最小节点发生变化时通知当前节点,再执行第二步的流程
3.zk上写锁:
创建一个临时序号节点,节点的数据是write,表示是写锁
获取zk中所有的子节点
判断自己是否是最小的节点:如果是最小节点,则上锁成功
如果不是,说明前面还有锁,则上锁失败,监听最小的节点,如果最小节点有变化,则再执行第二步。
4.羊群效应:如果用上述的上锁方式,只要有一个节点发生变化,就会触发其他节点的监听时间,这样的话对zk的压力非常大。可以调成链式监听,并发是顺位的,只需要监听前一位即可。(curator源码帮我们解决掉了这个问题)
七、zk的watch机制:
1.watch机制
客户端监听某个节点的变化,也就是调用了create、delete、setdata方法的时候,就会触发znode上注册的对应时间,请求watch的客户端会收到zk的异步通知,
get -w /test 监听节点——只能触发一次,在拿数据时用get -w /test,就可以一直监听。监听父节点,子节点发生变化不会触发监听。
客户端使用了NIO的通信模式监听服务端的调用
2.zkCli客户端使用watch
get -w:一次性监听节点内容变化
ls -w:监听目录,创建和删除一级子节点会收到通知
ls -R -w:监听所有层级子节点的目录
3.curator客户端使用watch
@Test
public void addNodeListener() throws Exception {
NodeCache nodeCache = new NodeCache(curatorFramework, "/curator-node");
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
log.info("{}path nodeChanged:","/curator-node");
printNodeData();
}
});
nodeCache.start();
System.in.read();
}
public void printNodeData() throws Exception {
byte[] bytes = curatorFramework.getData().forPath("/curator-node");
log.info("data:{},",new String(bytes));
}
4.curator实现读写锁
@SpringBootTest
@Slf4j
public class TestReadWriteLock {
@Autowired
private CuratorFramework client;
@Test
public void testGetReadLock() throws Exception {
//读写锁
InterProcessReadWriteLock interProcessReadWriteLock = new InterProcessReadWriteLock(client, "/lock");
//获取读锁对象
InterProcessMutex readLock = interProcessReadWriteLock.readLock();
System.out.println("等待获取读锁对象");
//获取锁
readLock.acquire();//尝试去拿锁
System.out.println("拿到锁咯");
for (int i = 0; i < 100; i++) {
Thread.sleep(1000);
System.out.println(i);
}
//释放锁
readLock.release();
System.out.println("释放锁杀果");
}
@Test
public void testGetReadLock2() throws Exception {
//读写锁
InterProcessReadWriteLock interProcessReadWriteLock = new InterProcessReadWriteLock(client, "/lock");
//获取读锁对象
InterProcessMutex readLock = interProcessReadWriteLock.readLock();
System.out.println("等待获取读锁对象");
//获取锁
readLock.acquire();//尝试去拿锁
System.out.println("拿到锁咯");
for (int i = 0; i < 100; i++) {
Thread.sleep(1000);
System.out.println(i);
}
//释放锁
readLock.release();
System.out.println("释放锁杀果");
}
@Test
public void testGetWriteLock() throws Exception {
//读写锁
InterProcessReadWriteLock interProcessReadWriteLock = new InterProcessReadWriteLock(client, "/lock");
//获取读锁对象
InterProcessMutex writeLock = interProcessReadWriteLock.writeLock();
System.out.println("等待获取写锁对象");
//获取锁
writeLock.acquire();//尝试去拿锁
System.out.println("拿到锁咯");
for (int i = 0; i < 100; i++) {
Thread.sleep(1000);
System.out.println(i);
}
//释放锁
writeLock.release();
System.out.println("释放锁杀果");
}
}
八、zookeeper集群
1.zookeeper集群中的节点有三种角色。
Leader:处理集群中所有事务请求(既能写,又能读),集群中只有一个Leader。
Follower:只能处理读请求,参与Leader选举。
Observer:只能处理读请求,提升集群读的性能,不参与Leader选举。
2.集群搭建:搭建4个节点,其中一个节点为ObServer
1)创建4个节点的myid,并设置值编辑myid按顺序写入1、2、3、4

2)去conf目录复制zoo.cfg,进去编辑,注意如果是在云服务器上搭建,必须要加上第三行,因为云服务器的ip地址不是本机网卡,云服务器好像没有网卡,这个可以去百度研究下。

server的第一个端口号是集群之间通信的端口,第二列端口是选举的端口。

3)开放端口

4)启动./zkServer.sh start ../conf/zoox.cfg,按顺序启动所有的集群节点,启动后使用
./zkServer.sh status ../conf/zoox.cfg观察,发现第二台是Leader

5)zkCli连接集群

九、ZAB协议
1.ZAB:zookeeper集群部署中会以一主多从的方式进行,为了保证数据的一致性,采用了ZAB协议,这个协议解决了zookeeper的崩溃恢复和主从数据同步的问题。
2.ZAB协议定义的四种节点状态:
Looking:选举状态
Following:Follower节点所处的状态
Leading:Leader节点所处的状态
Observing:观察者节点所处的状态
3.集群上线的选举过程:
4台集群配置为例(一般出去observer奇术个数较好,容易满足过半的要求):启动第一台它的状态为Looking,当第二台启动的时候就开始进行选举。
选票的格式(myid,zxid),zxid为事务id,这个服务器上发生的增删改都会使zxid+1。选票大小:优先zxid大的,然后才是myid大的
选举:每个节点生成一张自己的选票,将选票投给其他节点,举个列子,第一台的选票为(1,0)第二台的选票为(2,0),然后第一台将选票投给第二台,第二台投给第一台,
第一台里就有(1,0)(2,0)第二台(2,0)(1,0),将各自最大的选票投给自己的投票箱,这时集群中有4台机器,除开observer有3台,而投票箱里只有一票,不满足半数以上,
开始第二轮选票:将上一轮最大的选票更新为自己的选票,并投给其他节点,所以都是(2,0) (2,0),将(2,0)投到自己的投票箱,这时候选票箱就有2张(2,0)了,2号机票数过半,
Leader为第二台,选举结束,所以说按顺序启动,始终都是第二台是Leader。
第三台启动发现集群已经选举出了Leader,于是把自己作为Follower。
4.崩溃恢复时的Leader选举
集群建立连接后,Leader会发送ping格式的空数据维持心跳(也是BIO),集群中Follower会周期性的去和Leader建立的socket连接里面去读取ping格式的空数据,读取不到数据时Leader就挂掉了,就会从Follower状态重新进入Looking状态,
其他节点也如此,然后重新进入选举状态。此时Leader还没选举出来,不能对外提供服务。
5.主从服务器之间的数据同步
客户端向主节点写数据的情况:1.主节点先把数据先到自己的数据文件中,并给自己返回一个ACK
2.Leader把数据广播给Follower,Follower将数据写到本地的数据文件中
3.从节点返回ACK给Leader
4.Leader收到超过集群半数的ACK就广播commit给Follower
5.从节点收到commit后将数据文件中的数据写到内存中(二阶段提交,先到数据文件再到内存中)
6.zookeeper中的NIO和BIO的应用
NIO:
用于被客户端连接的2181端口,使用的是NIO模式与客户端建立连接,多个客户端的请求都放在一个队列里面,不会进行阻塞,zookeeper一个一个处理这些请求,实现多路复用。
客户端开启Watch时,也使用NIO等待zookeeper服务器的回调。一个客户端监听多个znode,同理。
BIO:
集群选举时,多个节点之间的投票通信端口使用BIO通信
十、CAP理论:一个分布式系统最多只能同时满足CAP中三项中的2项,即CP或者AP。
P:分区容错性(必须满足)
C:一致性 A:可用性 C和A只能满足其一。举个列子,小王往银行系统存了500块,银行系统是冗余部署的2个,通过负载均衡访问,小王往A系统存了500,数据要往B里面同步,这个时候因为银行系统网络波动,系统下线又上线,又要进行数据同步,
在同步的过程中,整个银行系统是否允许对外提供服务,提供服务的话可能查不到这500快,满足可用性不满足一致性。等数据同步完成后再提供服务的话,满足一致性不满足可用性。
BASE理论:
基本可用:电商大促时,为了应对访问激增,部分用户可能会被引导到降级页面,服务层也可能只提供降级服务,这就是损失部分可用性的体现。
软状态:允许系统存在中间状态,而中间状态不会影响系统整体可用性。分布式存储中一般一份数据至少会有3个副本,允许不同节点间副本数据同步的延时就是软状态的体现。
最终一致性:系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。最终一致性是一种特殊的弱一致性,强一致性和弱一致性相反。
zookeeper在数据同步时,追求的不是强一致性(半数以上的ACK就commit),追求的是顺序一致性(事务id的单挑递增实现),最终都会同步成功,同步后事务id会发生变化。

浙公网安备 33010602011771号