Zookeeper
Zookeeper 是一个开源的分布式的,为分布式应用提供协调服务的 Apache 项目。从设计模式角度来理解:是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化 ,Zookeeper就将负责通知已经在 Zookeeper 上注册的那些观察者做出相应的反应。
Zookeeper = 文件系统 + 通知机制
Zookeeper 维护一个类似文件系统的数据结构,Zookeeper 的命名空间的结构和文件系统很像,一个名字和文件一样使用/的路径表现,Zookeeper 的每个节点都是被路径唯一标识。
选举机制
- 半数机制:集群中半数以上机器存活,集群可用。所以 Zookeeper 适合安装奇数台服务器。
- Zookeeper 虽然在配置文件中并没有指定 Master 和 Slave。但是 Zookeeper 工作时是有一个节点为 Leader,其他则为 Follower,Leader 是通过内部的选举机制临时产生的。
以一个简单的例子来说明整个选举的过程。假设有五台服务器组成的 Zookeeper 集群,它们的 id 从 1-5,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的。假设这些服务器依序启动:
- 服务器 1 启动,发起一次选举。服务器 1 投自己一票。此时服务器 1 票数一票,不够半数以上(3 票),选举无法完成,服务器 1 状态保持为 LOOKING;
- 服务器 2 启动,再发起一次选举。服务器 1 和 2 分别投自己一票并交换选票信息。此时服务器 1 发现服务器 2 的 ID(Zookeeper 中节点有 myid 和 txid,此处指的是 myid)比自己目前投票推举的(服务器 1)大,更改选票为推举服务器 2。此时服务器 1 票数 0 票,服务器 2 票数 2 票,没有半数以上结果,选举无法完成,服务器 1,2 状态保持 LOOKING;
- 服务器 3 启动,发起一次选举。此时服务器 1 和 2 都会更改选票为服务器 3。此次投票结果:服务器 1 为 0 票,服务器 2 为 0 票,服务器 3 为 3 票。此时服务器 3 的票数已经超过半数,服务器 3 当选 Leader。服务器 1,2 更改状态为 FOLLOWING,服务器 3 更改状态为 LEADING;
- 服务器 4 启动,发起一次选举。此时服务器 1,2,3 已经不是 LOOKING 状态,不会更改选票信息。交换选票信息结果:服务器 3 为3 票,服务器 4 为 1 票。此时服务器 4服从多数,更改选票信息为服务器 3,并更改状态为 FOLLOWING;
- 服务器 5 启动,同 4 一样。
原子广播
所有写请求由 Leader 完成,Leader 广播给 Follower,半数以上 Follower 持久化更改后,Leader 提交更新并通知客户端成功。任何 znode 都可以提供读请求。
Zookeeper 与 Kafaka 保持数据一致性的不同点
- zookeeper 的 leader 负责数据的读写,而 follower 只负责数据的读,如果 follower 遇到写操作会提交到 leader ;当 leader 宕机的话,使用选举机制快速选举出新的 leader,节点在一开始都处于选举阶段,只要有一个节点得到超半数节点的票数,它就可以当选准 leader。
- kafka 只有 leader 负责读写,follower 只负责备份,如果 leader 宕机的话,kafka 动态维护了一个同步状态的副本的集合(a set of in-sync replicas),简称ISR。ISR中有 f+1 个节点,就可以允许在 f 个节点都 down 掉的情况下不会丢失消息并正常提供服务。
数据一致性
在数据有多分副本的情况下,如果网络、服务器或者软件出现故障,会导致部分副本写入成功,部分副本写入失败。这就造成各个副本之间的数据不一致,数据内容冲突。
centos7 安装 Zookeeper
Zookeeper 下载地址
在ping通 mac 端与 CentOS 端的连接后使用 scp 传输文件
scp zookeeper-3.4.14.tar.gz root@172.16.0.128:/opt/softwares
解压
tar -zxf zookeeper-3.4.14.tar.gz -C /opt/modules/
如下图所示
在 zookeeper 文件夹中创建一个 data 的文件夹
mkdir data
将 conf 目录下的 zoo_sample.cfg 复制一份并命名为 zoo.cfg
cp zoo_sample.cfg zoo.cfg
修改 zoo.cfg 文件
返回到 bin 文件夹中,使用指令启动 zookeeper 并查看状态
./zkServer.sh start
./zkServer.sh status
如下图所示
Spring Cloud 整合
创建子模块 cloud-provider-payment8004
pom.xml 文件中引入 zookeeper 依赖替换 eureka
<dependencies>
<!--springboot整合zookeeper客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
<!--Springboot整合web组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入自定义的api通用包,可以使用payment支付Entity-->
<dependency>
<groupId>com.hangzhou.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.yml
#8004表示注册到zookeeper服务器的支付服务提供者端口号
server:
port: 8004
#服务别名----注册zookeeper到注册中心名称
spring:
application:
name: cloud-provider-payment
cloud:
zookeeper:
connect-string: 172.16.0.128:2181
PaymentMain8004.class
@SpringBootApplication
@EnableDiscoveryClient //该注解用于想使用consul或者zookeeper作为注册中心时注册服务
public class PaymentMain8004 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8004.class,args);
}
}
controller 层
@RestController
@Slf4j
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/payment/zk")
public String paymentzk(){
return "springcloud with zookeeper:"+serverPort+"\t"+ UUID.randomUUID().toString();
}
}
./zkCli.sh 启动 zookeeper 客户端
启动8004注册进 zookeeper,控制台报错
是因为依赖中的 zookeeper 版本与 CentOS 中的版本冲突
修改 pom 依赖,排除3.5.3版本的,并引入 Cent OS 中 zookeeper 的3.4.14版本
<!--springboot整合zookeeper客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--先排除自带的zookeeper3.5.3-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加zookeeper3.4.14版本-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
</dependency>
zookeeper 还会遇到 slf4j 和 log4j 冲突的问题,此时在 zookeeper 再去掉slf4j的依赖
pom.xml
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
启动成功后服务模块入驻 zookeeper
微服务作为一个 znode 节点放到 zookeeper 服务器上,图中的这一大串就是在 zookeeper 上的基本信息的 json 串,就能看到 zookeeper 上8004微服务提供者的相关信息
并且能访问相应 url
此时的微服务在 zookeeper 上是一个临时节点。当服务提供者出现故障的时候,zookeeper 也是在一定时间内不会剔除服务,超时才剔除,所以 zookeeper 具备的服务节点是临时的。当再启动8004时,zookeeper 还会自动监听服务状态,但是id是一个新的id。
创建子module cloud-consumerzk-order80
pom.xml
<dependencies>
<!--springboot整合zookeeper客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--先排除自带的zookeeper3.5.3-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加zookeeper3.4.14版本-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--Springboot整合web组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入自定义的api通用包,可以使用payment支付Entity-->
<dependency>
<groupId>com.hangzhou.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.yml
server:
port: 80
spring:
application:
name: cloud-consumerzk-order
cloud:
#注册到zookeeper地址
zookeeper:
connect-string: 172.16.0.128:2181
config
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
controller 层
@RestController
@Slf4j
public class OrderZKController {
public static final String INVOKE_URL = "http://cloud-provider-payment";
@Resource
private RestTemplate restTemplate;
@GetMapping(value = "/consumer/payment/zk")
public String paymentInfo(){
String result = restTemplate.getForObject(INVOKE_URL + "/payment/zk", String.class);
return result;
}
}
启动 order80 后查看zk端入驻的服务
服务也能访问