zookeeper
介绍
Zookeeper是一个开源的分布式的,为分布式应用提供协调服务的Apache项目。它是一种集中式服务,用于维护配置信息,命名,提供分布式同步和提供组服务。所有这些类型的服务都以分布式应用程序的某种形式使用。每次实施它们都需要做很多工作来修复不可避免的错误和竞争条件。由于难以实现这些类型的服务,应用程序最初通常会吝啬它们,这使得它们在变化的情况下变得脆弱并且难以管理。即使正确完成,这些服务的不同实现也会在部署应用程序时导致管理复杂性。
特点
- Zookeeper:一个领导级别的存在,监测和管理多个服务。
- 集群中只要有半数以上节点存活,Zookeeper集群就能正常服务。
- 数据一致性:从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。
- 更新请求顺序进行:来自同一个Client的更新请求按照其发送顺序依次执行。
- 原子性:所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。
- 实时性:在一定时间范围,Client能读到最新的数据。
数据结构
Zookeeper数据模型的结构与Unix文件系统很类似,都是树结构。树上有若干个节点,每个节点能够存储1MB的数据,同时每个节点都是通过其路径可以唯一标识的。
企业应用场景
Zookeeper服务包括:统一命名服务、统一配置管理、统一集群管理、软负载均衡等。
-
.统一命名服务
命名服务是分布式系统中较为常见的一类场景,分布式系统中,被命名的实体通常可以是集群中的机器、提供的服务地址或远程对象等。通过命名服务,客户端可以根据指定名字来获取资源的实体、服务地址和提供者信息。例如一般用户都是通过域名来访问应用而不是IP。阿里开源分布式服务框架Dubbo中使用zookeeper来作为其命名服务,维护全局的服务列表。
-
统一配置管理
(1)配置文件同步,集群中所有配置文件的信息都是一致的,对配置文件修改后,快速同步到各个节点上。
(2)Zookeeper实现配置管理。将配置信息写入到Zookeeper上的节点,然后各个客户端服务器监听这个节点,一但节点中的数据发生变化,Zookeeper将通知到各个客户端服务器。
-
统一集群管理
zookeeper的两大特性:节点特性和watcher机制
(1)分布式环境中实时掌握每个节点的状态,并根据节点实时状态做出一定的调整。
(2)在实时监测到节点变化后,将节点的信息写入到Zookeeper上的节点,通过监听该节点来获取它的实时状态变化。
-
服务器动态上下线
-
负载均衡
zookeeperk实现负载均衡就是通过watcher机制和临时节点判断哪些节点宕机来获取可用的节点来实现的,zookeeperk会维护一个树形的数据结构,类似于window的资源管理器目录,其中 EPHEMERAL(临时)节点会随着创建它的客户端端口而被删除,利用这个特性很容易实现软负载均衡。
常用负载均衡策略
1️⃣轮询
经典的负载均衡策略,早期应用广泛。
- 原理:给每个请求标记一个序号,然后将请求依次派发到服务器节点中
- 适用于集群中各个节点提供服务能力等同且无状态的场景。
- 缺点:该策略将节点视为等同,与实际中复杂的环境不符。优化:加权轮询为轮询的一个改进策略,每个节点会有权重属性,但是因为权重的设置难以做到随实际情况变化,仍有一定的不足。
2️⃣随机
- 原理: 与轮询相似,不需要对每个请求进行编号,每次随机取一个。
- 同样,该策略也将后端的每个节点视为等同的。同样也有改进的加权随机的算法。
3️⃣最小响应时间
- 概念: 通过记录每次请求所需的时间,得出平均的响应时间,然后选择最小的响应时间。
- 该策略能较好地反应服务器的状态,但是由于是平均响应时间的关系,时间上有些滞后,无法满足快速响应的要求。因此在此基础之上,会有一些改进版本的策略,如只计算最近若干次的平均时间的策略等。
4️⃣最小并发数
- 概念: 客户端的每一次请求在服务器停留的时间可能会有较大的差异,随着工作时间加长,如果采用简单的轮询或随机均衡算法,每一台服务器上的连接进程可能会产生较大的不同,并没有达到真正的负载均衡。
- 概念:其策略是根据当前记录的时刻,选用当前正在处理的事务数量最少的节点。
- 优点:能够快速地反应服务器的当前状况,合理地将负载分配均匀,适用于对当前系统负载较为敏感的场景。
哈希
在后端节点有状态的情况下,需要使用哈希的方法进行负载均衡,此种情况下情况比较复杂,
软负载均衡
则是通过在服务器上安装的特定的负载均衡软件或是自带负载均衡模块完成对请求的分配派发。
软负载:顾名思义就是靠软件手段来实现的负载均衡。比如各种算法。软负载也通常被称为 4层或 7 层负载!
软负载方面的软件特别多,比如早期阿里章文嵩博士的 LVS,再比如 Nginx 的负载均衡等。通常软负载有这些大的分类技术,http重定向、DNS负载均衡、反向代理负载均衡、IP负载均衡(LVS-NAT)、直接路由(LVS-DR)、IP隧道(LVS-TUN)等技术。
硬负载均衡
硬负载效率比软负载高。
- 原理是把目标 IP 地址改为后台服务器的 ip 地址。
- 硬负载方面,通常有这些负载均衡设备。多链路负载均衡、防火墙负载均衡、服务器负载均衡等。
硬负载的效率非常高。这就好像中医看病一样,通过面相、心率、望闻问切就知道一个大概。软负载就好比,你说肚子疼,医生来就给你抽血化验、B 超、CT 等先搞一套,所以相当来说它效率低一些!
安装与配置
Zookeeper下载
下载地址:https://archive.apache.org/dist/zookeeper/
安装准备
(1)安装Jdk
(2)拷贝Zookeeper安装包到Linux系统下
(3)解压到指定目录
tar -zxvf apache-zookeeper-3.4.10-bin.tar.gz
配置修改
在zookeeper目录中创建data文件夹,用来存储临时文件
修改配置文件名称
cp zoo_sample.cfg zoo.cfg
修改配置文件zoo.conf
启动服务
./zkServer.sh start
ps aux |grep zookeeper // 查看zookeeper进程
查看是否启动成功 jps
查看zookeeper服务的状态
./zkServer.sh status
连接,启动客户端
./zkCli.sh
quit 退出客户端
停止zookeeper
./zkServer.sh stop
配置解读
Zookeeper中的配置文件zoo.cfg中参数含义解读如下
-
tickTime =2000:通信心跳数,Zookeeper服务器与客户端心跳时间,单位毫秒
Zookeeper使用的基本时间,服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个tickTime时间就会发送一个心跳,时间单位为毫秒。
它用于心跳机制,并且设置最小的session超时时间为两倍心跳时间。(session的最小超时时间是2*tickTime)【默认】
-
initLimit =10:LF初始通信时限 【102000(10通信心跳数=初始通信时限)】
集群中的Follower跟随者服务器与Leader领导者服务器之间初始连接时能容忍的最多心跳数(tickTime的数量),用它来限定集群中的Zookeeper服务器连接到Leader的时限。
-
syncLimit =5:LF同步通信时限
集群中Leader与Follower之间的最大响应时间单位,假如响应超过syncLimit * tickTime,Leader认为Follwer死掉,从服务器列表中删除Follwer。
-
dataDir:数据文件目录+数据持久化路径
主要用于保存Zookeeper中的数据。
-
clientPort =2181:客户端连接端口
监听客户端连接的端口。
选举机制
半数机制:集群中半数以上机器存活,集群可用。所以Zookeeper适合安装奇数台服务器。
Zookeeper虽然在配置文件中并没有指定Master和Slave。但是,Zookeeper工作时,是有一个节点为Leader,其他则为Follower,Leader是通过内部的选举机制临时产生的。
以一个简单的例子来说明整个选举的过程。
假设有五台服务器组成的Zookeeper集群,它们的id从1-5,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的。假设这些服务器依序启动,来看看会发生什么,如图所示。
节点类型
持久节点(Persistent):服务端和客户端断开连接后,创建的节点不删除;
持久化目录节点:服务端和客户端断开连接后,该节点仍然存在;
持久化顺序编号目录节点:服务端和客户端断开连接后,该节点仍然存在;只是zookeeper给该节点名称进行顺序编号。
短暂节点(Ephemeral):服务端和客户端断开连接后,创建的节点自己删除;
临时目录节点:客户端与Zookeeper断开连接后,该节点被删除
临时顺序编号目录节点:客户端与Zookeeper断开连接后,该节点被删除,只是zook给该节点名称进行顺序编号。
读写机制
写数据流程
-
以3台服务器的Zookeeper集群为例,一个Leader,两个Follower即server1和server2。
- Client向Zookeeper的server1发送一个写请求,客户端写数据到服务器1上;
- 如果server1不是Leader,那么server1会把接收到的写请求转发给Leader;然后Leader会将写请求转发给每个server;
- server1和server2负责写数据,并且两个Follower的写入数据是一致的,保存相同的数据副本;
- server1和server2写数据成功后,通知Leader;
-
当Leader收到集群半数以上的节点写成功的消息后,说明该写操作执行成功;
-
例如:这里是3台服务器,只要2台Follower服务器写成功就ok
因为client访问的是server1,所以Leader会告知server1集群中数据写成功;
-
-
被访问的server1进一步通知client数据写成功,这时,客户端就知道整个写操作成功了。
流程图:
分布式安装部署
1、先创建分布式集群的文件夹
2、在该文件夹中解压集群中zookeeper的数量
3、分别在zookeeper中创建储存临时数据的文件夹data
4、在各个data中创建myid文本,严格配置标号
5、分别在各个zookeeper中conf/zoo.cfg配置文件中配置临时存储数据路径,客户端端口,服务器所有端口
6、依次启动,可以查看到效果。
常用命令
启动客户端,在zookeeper中bin目录下
./zkCli.sh
显示所有操作命令
help
查看节点中所包含的内容 ls /
查看当前节点详细数据 ls2 /
创建持久节点
create /shuihu "songjiang"
创建子节点
create /shuihu/a1 "aaa"
创建顺序标号节点
create -s /shuihu/jiedian01 "001"
获取节点
get /shuihu
修改节点的值
set /shuihu "shuihu001"
删除节点,想删除父节点必须删除下面所有的子节点
delete /hongloumeng
查看节点详细信息
stat /shuihu
创建临时节点。重新进入zookeeper临时节点就被删了
create -e /linshijiedian "linshi01"
临时节点中不能创建子节点
创建临时顺序目录标号节点
create -e -s /aaa/linshixuhao "临时序号节点01"
zookeeper的API操作zookeeper
pom文件加载依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.offcn</groupId>
<artifactId>zookeeper</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>6</source>
<target>6</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
再resources中配置log4j.properties
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
创建测试类
package cn.test;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.junit.Before;
import org.junit.Test;
import java.util.List;
public class ZookeeperTest {
// 声明zookeeper集群的各个端口
private static String connectionStr = "192.168.146.132:2181,192.168.146.132:2182,192.168.146.132:2183";
// 声明超时时间
private static int sessionTimeout = 60000;
//声明客户端
private ZooKeeper zkClient = null;
@Before
public void init() throws Exception{
zkClient=new ZooKeeper(connectionStr, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent event) {
// 收到事件通知后的回调函数(用户的业务逻辑)
System.out.println(event.getType() + "--" + event.getPath());
// 再次启动监听
try {
zkClient.getChildren("/", true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
//创建节点
@Test
public void testCreateNode(){
try {
// 参数1:要创建的节点的路径; 参数2:节点数据 ; 参数3:节点权限 ;参数4:节点的类型
zkClient.create("/jiedian","zhonggong".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//获取指定目录定的所有子节点
@Test
public void testGetNodes(){
try {
List<String> childrenList = zkClient.getChildren("/shuihu", true);
for (String s : childrenList) {
System.out.println(s);
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 查看节点是否存在
@Test
public void exist() throws Exception {
Stat stat = zkClient.exists("/abc", false);
System.out.println(stat == null ? "not exist" : "exist");
}
}
= zkClient.getChildren("/shuihu", true);
for (String s : childrenList) {
System.out.println(s);
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 查看节点是否存在
@Test
public void exist() throws Exception {
Stat stat = zkClient.exists("/abc", false);
System.out.println(stat == null ? "not exist" : "exist");
}
}