SpringCloud 14 —— 注册中心之Zookeeper

Zookeeper 简介

ZooKeeper是一个开源的分布式协调服务,由雅虎创建,是GoogleChubby的开源实现。ZooKeeper的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。

ZooKeeper是一个典型的分布式数据一致性的解决方案。分布式应用程序可以基于它实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、分布式锁和分布式队列等功能。ZooKeeper可以保证如下分布式一致性特性。

  • 顺序一致性
    从同一个客户端发起的事务请求,最终将会严格按照其发起顺序被应用到ZooKeeper中。

  • 原子性
    所有事务请求的结果在集群中所有机器上的应用情况是一致的,也就是说要么整个集群所有集群都成功应用了某一个事务,要么都没有应用,一定不会出现集群中部分机器应用了该事务,而另外一部分没有应用的情况。

  • 单一视图
    无论客户端连接的是哪个ZooKeeper服务器,其看到的服务端数据模型都是一致的。

  • 可靠性
    一旦服务端成功地应用了一个事务,并完成对客户端的响应,那么该事务所引起的服务端状态变更将会被一直保留下来,除非有另一个事务又对其进行了变更。

  • 实时性
    通常人们看到实时性的第一反应是,一旦一个事务被成功应用,那么客户端能够立即从服务端上读取到这个事务变更后的最新数据状态。这里需要注意的是,ZooKeeper仅仅保证在一定的时间段内,客户端最终一定能够从服务端上读取到最新的数据状态

Zookeeper 中的角色( Role )

在ZooKeeper中,有三种角色,分别是:

Leader
Follower
Observer

一个ZooKeeper集群同一时刻只会有一个Leader,其他都是Follower或Observer。

ZooKeeper配置很简单,每个节点的配置文件(zoo.cfg)都是一样的,只有myid文件不一样。myid的值必须是zoo.cfg中server.{数值}的{数值}部分。

maxClientCnxns=0
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
dataDir=/opt/zookeeper/data
# the port at which the clients will connect
clientPort=2181
# the directory where the transaction logs are stored.
dataLogDir=/opt/zookeeper/logs
server.1=192.168.0.167:2888:3888
server.2=192.168.0.168:2888:3888
server.3=192.168.0.169:2888:3888
minSessionTimeout=4000
maxSessionTimeout=100000

通过执行zookeeper-server status可以看当前节点的ZooKeeper是什么角色

# zookeeper-server status
JMX enabled by default
Using config: /etc/zookeeper/conf/zoo.cfg
Mode: leader

ZooKeeper默认只有Leader和Follower两种角色,没有Observer角色。Zookeeper为了满足过半选举,集群最少需要3台机器

  1. 为了使用Observer模式,在任何想变成Observer的节点的配置文件中加入:peerType=observer ,并在所有server的配置文件中,配置成Observer模式的server的那行配置追加:observer,例如: server.1:localhost:2888:3888:observer
  2. ZooKeeper集群的所有机器通过一个Leader选举过程来选定一台被称为Leader的机器,Leader服务器为客户端提供读和写服务。
  3. Follower和Observer都能提供读服务,不能提供写服务。两者唯一的区别在于,Observer机器不参与Leader选举过程,也不参与写操作的过半写成功策略,因此Observer可以在不影响写性能的情况下提升集群的读性能

会话(Session)

Session是指客户端会话,在ZooKeeper中,一个客户端连接是指客户端和ZooKeeper服务器之间的TCP长连接。ZooKeeper对外的服务端口默认是2181,客户端启动时,首先会与服务器建立一个TCP连接,从第一次连接建立开始,客户端会话的生命周期也开始了,通过这个连接,客户端能够通过心跳检测和服务器保持有效的会话,也能够向ZooKeeper服务器发送请求并接受响应,同时还能通过该连接接收来自服务器的Watch事件通知。Session的SessionTimeout值用来设置一个客户端会话的超时时间。当由于服务器压力太大、网络故障或是客户端主动断开连接等各种原因导致客户端连接断开时,只要在SessionTimeout规定的时间内能够重新连接上集群中任意一台服务器,之前创建的会话仍然有效。

数据节点(ZNode)

在ZooKeeper中,ZNode可以分为持久节点和临时节点两类

ZooKeeper中的数据节点是指数据模型中的数据单元,称为ZNode
ZooKeeper将所有数据存储在内存中,数据模型是一棵树(ZNode Tree),由斜杠(/)进行分割的路径,就是一个ZNode,如/canal/position,其中canal和position都是ZNode。每个ZNode上都会保存自己的数据内容,同时会保存一系列属性信息。

持久节点: 指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在ZooKeeper上

临时节点: 临时节点的生命周期跟客户端会话绑定,一旦客户端会话失效,那么这个客户端创建的所有临时节点都会被移除。另外,ZooKeeper还允许用户为每个节点添加一个特殊的属性:SEQUENTIAL。一旦节点被标记上这个属性,那么在这个节点被创建的时候,ZooKeeper就会自动在其节点后面追加上一个整型数字,这个整型数字是一个由父节点维护的自增数字

版本( Version )

ZooKeeper的每个ZNode上都会存储数据,对应于每个ZNode,ZooKeeper都会为其维护一个叫作Stat的数据结构,Stat中记录了这个ZNode的三个数据版本,分别是version(当前ZNode的版本)、cversion(当前ZNode子节点的版本)和aversion(当前ZNode的ACL版本)

状态信息( Status)

每个ZNode除了存储数据内容之外,还存储了ZNode本身的一些状态信息。用 get 命令可以同时获得某个ZNode的内容和状态信息

在ZooKeeper中,version属性是用来实现乐观锁机制中的写入校验(保证分布式数据原子性操作)

事务操作( Transaction )

能改变ZooKeeper服务器状态的操作称为事务操作。一般包括数据节点创建与删除、数据内容更新和客户端会话创建与失效等操作。对应每一个事务请求,ZooKeeper都会为其分配一个全局唯一的事务ID,用ZXID表示,通常是一个64位的数字。每一个ZXID对应一次更新操作,从这些ZXID中可以间接地识别出ZooKeeper处理这些事务操作请求的全局顺序

Watcher

Watcher(事件监听器),是ZooKeeper中一个很重要的特性。ZooKeeper允许用户在指定节点上注册一些Watcher,并且在一些特定事件触发的时候,ZooKeeper服务端会将事件通知到感兴趣的客户端上去。

ACL

ZooKeeper采用ACL(Access Control Lists)策略来进行权限控制。ZooKeeper定义了如下5种权限。

  • CREATE: 创建子节点的权限。
  • READ: 获取节点数据和子节点列表的权限。
  • WRITE:更新节点数据的权限。
  • DELETE: 删除子节点的权限。
  • ADMIN: 设置节点ACL的权限。

注意:CREATE 和 DELETE 都是针对子节点的权限控制。

ZAB协议

ZAB协议的核心是定义了对应那些会改变ZooKeeper服务器数据状态的事务请求的处理方式,所有事务请求必须由一个全局唯一的服务器来协调处理,这样的服务器被称为Leader服务器,而剩下的其他服务器则成为Follower服务器。Leader服务器负责将一个客户端事务请求转换成一个事务Proposal(提案)并将该Proposal分发给集群中所有的Follower服务器。之后Leader服务器需要等待所有Follower服务器的反馈,一旦超过半数的Follower服务器进行了正确的反馈后,Leader就会再次向所有的Follower服务器分发Commit消息,要求对刚才的Proposal进行提交

ZAB协议包括两种基本的模式,分别是崩溃恢复和消息广播。在整个ZooKeeper集群启动过程中,或是当Leader服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB协议就会进入恢复模式并选举产生新的Leader服务器。当选举产生了新的Leader服务器,同时集群中有过半的机器与该Leader服务器完成了状态同步之后,ZAB协议就会退出恢复模式。其中,状态同步是指数据同步,用来保证集群中存在过半的机器能够和Leader服务器的数据状态保持一致。

崩溃恢复模式包括两个阶段:Leader选举和数据同步。

当集群中有过半的Follower服务器完成了和Leader服务器的状态同步,那么整个集群就可以进入消息广播模式了。

Zookeeper 和 Eureka的对比

FeatureEuerkaZookeeper
设计原则CPAP
优点数据强一致服务高可用
缺点网络分区会影响Leader选举,超过阈值后集群不可用服务节点间的数据可能不一致;Client-Server间的数据可能不一致
适用场景单机房集群,对数据一致性要求较高云机房集群,跨越多机房部署;对注册中心服务可用性要求较高

Zookeeper 安装部署

官网下载Zookeeper,解压。复制conf目录下的zoo_sample.cfg文件并重命名为zoo.cfg。创建两个文件夹data和log(或者任意名),修改zoo.cfg中的dataDir配置.

# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial 
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between 
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just 
# example sakes.
#dataDir=/tmp/zookeeper
dataDir=D:\eniverment\apache-zookeeper-3.6.1-bin\data
dataLog=D:\eniverment\apache-zookeeper-3.6.1-bin\log
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the 
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1

## Metrics Providers
#
# https://prometheus.io Metrics Exporter
#metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider
#metricsProvider.httpPort=7000
#metricsProvider.exportJvmInfo=true

配置说明:

tickTime:这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 时间就会发送一个心跳。
initLimit:这个配置项是用来配置 Zookeeper 接受客户端(这里所说的客户端不是用户连接 Zookeeper 服务器的客户端,而是 Zookeeper 服务器集群中连接到 Leader 的 Follower 服务器)初始化连接时最长能忍受多少个心跳时间间隔数。当已经超过 10 个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 102000=20 秒
syncLimit:这个配置项标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 5
2000=10 秒
dataDir:顾名思义就是 Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。
clientPort:这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。

进入bin目录,启动运行zkServer.cmd.

Zookeeper 替换Eureka

创建建两个模块,Server和Client,其实也就是Producer和Consumer,通过两个模块我们来演示如何用Zookeeper轻松的替换Eureka。并且模拟Client远程调用 Server端。

Server

新建项目模块register-zookeeper,引入依赖.

    <properties>
        <zookeeper.version>3.5.5</zookeeper.version>
        <ackson-core-asl.version>1.9.13</ackson-core-asl.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>${zookeeper.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-core-asl</artifactId>
            <version>${ackson-core-asl.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>group.group.zhouning</groupId>
            <artifactId>feign-api</artifactId>
        </dependency>
    </dependencies>

应用配置

以下配置了应用的端口及应用名称,其中对Zookeeper的配置我们配置了注册地址、以及服务发现的配置,为了方便查阅,我们让配置根目录为cloud,每个应用最好配置自己的instance-id,不要和其他应用冲突。

server:
  port: 8661
spring:
  application:
    name: register-zookeeper
  cloud:
    zookeeper:
      connectString: 127.0.0.1:2181  #可修改为自己的zk
      discovery:
        register: true
        enabled: true
        instance-id: 1
        root: /cloud

启动类

@EnableDiscoveryClient
@SpringBootApplication
public class RegisterZookeeperApplication {
    public static void main(String[] args) {
        SpringApplication.run(RegisterZookeeperApplication.class, args);
    }
}

对外提供的Controller接口

Server端提供了字符串类型的和实体对象类型的,项目启动之后注册到Zookeeper即可对外提供服务。

@RestController
public class HelloWorldController {

    @GetMapping("/helloWorld")
    public String HelloWorld() {
        return "Hello World!";
    }

    @GetMapping("/user")
    public User getUser() {
        return new User(1L,"zookeeper","寻非 ","yixunfei@outlook.com","演示Zookeeper 替换Eureka");
    }
}

Client 端

新建项目模块cloud-client-zookeeper,引入依赖:

    <dependencies>
        <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.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>${zookeeper.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-core-asl</artifactId>
            <version>${ackson-core-asl.version}</version>
        </dependency>
    </dependencies>

应用配置

server:
  port: 8662
spring:
  application:
    name: client-zookeeper-demo
  cloud:
    zookeeper:
      connect-string: 127.0.0.1:2181
      discovery:
        register: true
        enabled: true
        instance-id: 2
        root: /cloud

启动类

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class ClientZookeeperApplication {
    public static void main(String[] args) {
        SpringApplication.run(ClientZookeeperApplication.class, args);
    }
}

Feign 接口

我们使用Feign来配置远程调用的服务名和调用的接口。

@FeignClient(name = "register-zookeeper")
public interface ZookeeperApi {

    /**
     * 获取字符串信息
     * @return
     */
    @GetMapping("/helloWorld")
    String helloWorld();

    /**
     * 获取用户信息demo
     * @return
     */
    @GetMapping("/user")
    User getUser();

}

定义消费者接口

这里对外提供的接口名称可以自己定义,最终会将请求调用到Server的接口上面,并接收到执行结果返回给客户端。

@RestController
public class ZookeeperController {

    @Autowired
    private ZookeeperApi zookeeperApi;

    /**
     * hello word
     * @return
     */
    @GetMapping("/hello")
    public String hello() {

        return zookeeperApi.helloWorld();

    }

    /**
     * 获取用户信息
     * @return
     */
    @GetMapping("/user")
    public User getUser() {
        return zookeeperApi.getUser();

    }
}

entity

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

    /**
     * 主键Id
     */
    private long id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 昵称
     */
    private String rolename;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 备注
     */
    private String remark;
}

启动并测试
启动Zookeeper(有条件的可以启动Zookeeper的展示页面),Server端,Client端
访问测试

更新时间:2020-05-12 17:03:23

本文由 寻非 创作,如果您觉得本文不错,请随意赞赏
采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
原文链接:https://www.zhouning.group/archives/springcloud14注册中心之zookeeper
最后更新:2020-05-12 17:03:23

评论

Your browser is out of date!

Update your browser to view this website correctly. Update my browser now

×