ZooKeeper

1 zookeeper简介

ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。

ZooKeeper包含一个简单的原语集,提供Java和C的接口。

ZooKeeper代码版本中,提供了分布式独享锁、选举、队列的接口,代码在$zookeeper_home\src\recipes。其中分布锁和队列有Java和C两个版本,选举只有Java版本。

ZooKeeper是以Fast Paxos算法为基础的,[Paxos 算法](https://baike.baidu.com/item/Paxos 算法)存在活锁的问题,即当有多个proposer交错提交时,有可能互相排斥导致没有一个proposer能提交成功,而Fast Paxos做了一些优化,通过选举产生一个leader (领导者),只有leader才能提交proposer,具体算法可见Fast Paxos。因此,要想弄懂ZooKeeper首先得对Fast Paxos有所了解。

ZooKeeper的基本运转流程:

1、选举Leader。

2、同步数据。

3、选举Leader过程中算法有很多,但要达到的选举标准是一致的。

4、Leader要具有最高的执行ID,类似root权限

5、集群中大多数的机器得到响应并接受选出的Leader。

选举流程

1、 当leader挂了后,其他非observer的机器变更状态为looking,然后开始选举流程
2、每个server都会发出一个投票信息(myid,zxid)
3、接收来自各个服务器的投票
4、处理投票–判断zxid,大的zxid成为leader,如果zxid相同,就判断myid,myid大的做leader
5、统计投票–半数服务器达成一致意见后,投票结束
6、改变自身的状态–leader为leading,其他服务器为following

2 zookeeper安装与启动

https://www.yiibai.com/zookeeper/zookeeper_installation.html

https://blog.csdn.net/zlbdmm/article/details/109669049

zoo.cfg 配置文件说明:

  • tickTime:这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 时间就会发送一个心跳。
  • initLimit:LF初始通信时限,集群中的follower服务器(F)与leader服务器(L)之间初始连接时能容忍的最多心跳数(tickTime的数量)。
  • syncLimit:LF同步通信时限,集群中的follower服务器与leader服务器之间请求和应答之间能容忍的最多心跳数(tickTime的数量)。
  • dataDir:顾名思义就是 Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。
  • clientPort:客户端连接端口,这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求
  • autopurge.snapRetainCount:保留数量。
  • autopurge.purgeInterval:清理时间间隔,单位:小时。
  • server.N = YYY:A:B,其中N表示服务器编号,YYY表示服务器的IP地址,A为LF通信端口,表示该服务器与集群中的leader交换的信息的端口。B为选举端口,表示选举新leader时服务器间相互通信的端口(当leader挂掉时,其余服务器会相互通信,选择出新的leader)。一般来说,集群中每个服务器的A端口都是一样,每个服务器的B端口也是一样。但是当所采用的为伪集群时,IP地址都一样,只能时A端口和B端口不一样。
3 集成springcloud 服务注册

IDEA使用spring initializr创建服务,勾选下图依赖

在这里插入图片描述

成功后,完整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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.cuixk.microserver.zookeeper</groupId>
    <artifactId>zookeeper-provider</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>zookeeper-provider</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2020.0.0</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
        </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>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>

</project>

服务生产者配置文件

server:
  port: 8000

spring:
  cloud:
    zookeeper:
      connect-string: 192.168.20.134:2181
      discovery:
        enabled: true
  application:
    name: zookeeper-provider


服务消费者配置文件

server:
  port: 8001

spring:
  cloud:
    zookeeper:
      connect-string: ip:2181			#zookeeper服务地址
      discovery:
        enabled: true					#开启服务发现功能
  application:
    name: zookeeper-consumer			#应用ID

服务生产者在启动类上添加@EnableDiscoveryClient

@SpringBootApplication
@EnableDiscoveryClient
public class ZookeeperProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZookeeperProviderApplication.class, args);
    }

}

服务消费者在启动类上添加@EnableDiscoveryClient,@EnableFeignClients

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ZookeeperConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZookeeperConsumerApplication.class, args);
    }

}

启动项目,如下图所示即注册成功

在这里插入图片描述

4 测试

通用pojo

@Data
public class User {
    private Integer id;
    private String name;
    private Integer age;
}

服务生产者

controller代码

@RestController
@RequestMapping("/user")
public class UserController {

    @GetMapping("/{id}")
    public User getUserInfo(@PathVariable Integer id) {
        User user = new User();
        user.setId(id);
        user.setName("provider");
        user.setAge(25);
        return user;
    }
 }

服务消费者

controller代码

@RestController
@RequestMapping("/get")
public class OrderController {

    @Resource
    private UserFeign userFeign;

    @GetMapping("/{id}")
    public String getUserInfo(@PathVariable Integer id) {
        User userInfo = userFeign.getUserInfo(id);
        return userInfo.toString();
    }


}

feign代码

@FeignClient("zookeeper-provider")  //此处填写服务生产者注册的serviceName
public interface UserFeign {

    @GetMapping("/user/{id}")
    User getUserInfo(@PathVariable Integer id);
}

调用消费者接口,若有结果返回,则测试通过。

5 问题记录
5.1zookeeper 启动异常 myid文件缺失导致zookeeper无法启动(myid file is missing)

https://blog.csdn.net/u010842515/article/details/51147016

5.2 按照springcloud官网测试代码,发现无法获取到client信息,始终返回null值。
@Autowired
private DiscoveryClient discoveryClient;

public String serviceUrl() {
    List<ServiceInstance> list = discoveryClient.getInstances("STORES");
    if (list != null && list.size() > 0 ) {
        return list.get(0).getUri().toString();
    }
    return null;
}

原因:

1.discoveryClient.getInstances(“STORES”); 此处入参需改成实际注册的serviceName

2.需开启服务发现注解 @EnableDiscoveryClient

6 zookeeper集群搭建

https://blog.csdn.net/zlbdmm/article/details/109669049

由上述链接集群搭建成功后,修改配置文件

connect-string 配置了所有zk节点,原因并不是为了向所有zk注册信息,而是如果 zk1挂掉之后,转向其他zk注册,保证服务可以注册成功

消费者配置文件
server:
  port: 8001

spring:
  cloud:
    zookeeper:
      connect-string: localhost:12181,localhost:12182,localhost:12183
      discovery:
        enabled: true
  application:
    name: zookeeper-consumer
生产者配置文件
server:
  port: 8081
spring:
  application:
    name: service-provider

  cloud:
    nacos:
      discovery:
        server-addr: 192.168.20.134:8848

进行第 4 节的测试工作。

Zookeeper集群数为单数

1、容错

由于在增删改操作中需要半数以上服务器通过,来分析以下情况。

2台服务器,至少2台正常运行才行(2的半数为1,半数以上最少为2),正常运行1台服务器都不允许挂掉

3台服务器,至少2台正常运行才行(3的半数为1.5,半数以上最少为2),正常运行可以允许1台服务器挂掉

4台服务器,至少3台正常运行才行(4的半数为2,半数以上最少为3),正常运行可以允许1台服务器挂掉

5台服务器,至少3台正常运行才行(5的半数为2.5,半数以上最少为3),正常运行可以允许2台服务器挂掉

6台服务器,至少3台正常运行才行(6的半数为3,半数以上最少为4),正常运行可以允许2台服务器挂掉

通过以上可以发现,3台服务器和4台服务器都最多允许1台服务器挂掉,5台服务器和6台服务器都最多允许2台服务器挂掉

但是明显4台服务器成本高于3台服务器成本,6台服务器成本高于5服务器成本。这是由于半数以上投票通过决定的。

2、防脑裂

一个zookeeper集群中,可以有多个follower、observer服务器,但是必需只能有一个leader服务器。

如果leader服务器挂掉了,剩下的服务器集群会通过半数以上投票选出一个新的leader服务器。

集群互不通讯情况:

一个集群3台服务器,全部运行正常,但是其中1台裂开了,和另外2台无法通讯。3台机器里面2台正常运行过半票可以选出一个leader。

一个集群4台服务器,全部运行正常,但是其中2台裂开了,和另外2台无法通讯。4台机器里面2台正常工作没有过半票以上达到3,无法选出leader正常运行。

一个集群5台服务器,全部运行正常,但是其中2台裂开了,和另外3台无法通讯。5台机器里面3台正常运行过半票可以选出一个leader。
一个集群6台服务器,全部运行正常,但是其中3台裂开了,和另外3台无法通讯。6台机器里面3台正常工作没有过半票以上达到4,无法选出leader正常运行。

通可以上分析可以看出,为什么zookeeper集群数量总是单出现,主要原因还是在于第2点,防脑裂,对于第1点,无非是正本控制,但是不影响集群正常运行。但是出现第2种脑裂的情况,zookeeper集群就无法正常运行了。

7 参考链接

https://docs.spring.io/spring-cloud-zookeeper/docs/current/reference/html/ 官方网址

https://www.apache.org/dyn/closer.lua/zookeeper/zookeeper-3.5.9/apache-zookeeper-3.5.9-bin.tar.gz 下载地址

https://www.yiibai.com/zookeeper/zookeeper_installation.html zookeeper安装与启动

https://cloud.tencent.com/developer/article/1505113 服务注册发现之Zookeeper服务注册

https://blog.csdn.net/zlbdmm/article/details/109669049 Zookeeper下载与安装教程(for windows)

https://blog.csdn.net/CSDN_Stephen/article/details/78856323 用Zookeeper作为Spring cloud的配置中心

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐