SpringCloud 学习笔记系列 07----Zookeeper作为注册中心与配置中心

概念

  • 什么是Zookeeper
  • Zookeeper是一个分布式开源框架,提供了协调分布式应用的基本服务,它向外部应用暴露一组通用服务——分布式同步(Distributed Synchronization)、命名服务(Naming Service)、集群维护(Group Maintenance)等,简化分布式应用协调及其管理的难度,提供高性能的分布式服务。
  • ZooKeeper本身可以以单机模式安装运行,不过它的长处在于通过分布式ZooKeeper集群(一个Leader,多个Follower),基于一定的策略来保证ZooKeeper集群的稳定性和可用性,从而实现分布式应用的可靠性。
  • Apache ZooKeeper是由集群(节点组)使用的一种服务,用于在自身之间协调,并通过稳健的同步技术维护共享数据。ZooKeeper本身是一个分布式应用程序,为写入分布式应用程序提供服务。
  • Zookeeper用途
  • 1、Zookeeper是为别的分布式程序服务的
  • 2、Zookeeper本身就是一个分布式程序(只要有半数以上节点存活,zk就能正常服务)
  • 3、Zookeeper所提供的服务涵盖:主从协调、服务器节点动态上下线、统一配置管理、分布式共享锁、统一名称服务等
  • 4、虽然说可以提供各种服务,但是zookeeper在底层其实只提供了两个功能:管理(存储,读取)用户程序提交的数据(类似namenode中存放的metadata);并为用户程序提供数据节点监听服务;
  • Zookeeper设计目的
  • zookeeper是注册中心 zookeeper为什么可以做注册中心_spring


  • 最终一致性:client不论连接到哪个Server,展示给它都是同一个视图,这是zookeeper最重要的性能。
  • 可靠性:具有简单、健壮、良好的性能,如果消息m被到一台服务器接受,那么它将被所有的服务器接受。
  • 实时性:Zookeeper保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息。但由于网络延时等原因,Zookeeper不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用sync()接口。
  • 等待无关(wait-free):慢的或者失效的client不得干预快速的client的请求,使得每个client都能有效的等待。
  • 原子性:更新只能成功或者失败,没有中间状态。
  • 顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息a在消息b前发布,则在所有Server上消息a都将在消息b前被发布;偏序是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面。
  • Zookeeper工作原理
  • Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。
  • 实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。
  • 当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。
  • 为了保证事务的顺序一致性,zookeeper采用了递增的事务id号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上了zxid。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch,标识当前属于那个leader的统治时期。低32位用于递增计数。
  • 每个Server在工作过程中有三种状态:
  • LOOKING:当前Server不知道leader是谁,正在搜寻
  • LEADING:当前Server即为选举出来的leader
  • FOLLOWING:leader已经选举出来,当前Server与之同步

Zookeeper应用场景

数据发布与订阅(配置中心)
  • 发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据发布到ZK节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。例如全局的配置信息,服务式服务框架的服务地址列表等就非常适合使用。
负载均衡
  • 这里说的负载均衡是指软负载均衡。在分布式环境中,为了保证高可用性,通常同一个应用或同一个服务的提供方都会部署多份,达到对等服务。而消费者就须要在这些对等的服务器中选择一个来执行相关的业务逻辑,其中比较典型的是消息中间件中的生产者,消费者负载均衡。
  • 消息中间件中发布者和订阅者的负载均衡,linkedin开源的KafkaMQ和阿里开源的 metaq都是通过zookeeper来做到生产者、消费者的负载均衡。
命名服务(Naming Service)
  • 命名服务也是分布式系统中比较常见的一类场景。在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源或服务的地址,提供者等信息。
  • 被命名的实体通常可以是集群中的机器,提供的服务地址,远程对象等等——这些我们都可以统称他们为名字(Name)。
  • 其中较为常见的就是一些分布式服务框架中的服务地址列表。通过调用ZK提供的创建节点的API,能够很容易创建一个全局唯一的path,这个path就可以作为一个名称。阿里巴巴集团开源的分布式服务框架Dubbo中使用ZooKeeper来作为其命名服务,维护全局的服务地址列表,
分布式通知/协调
  • ZooKeeper中特有watcher注册与异步通知机制,能够很好的实现分布式环境下不同系统之间的通知与协调,实现对数据变更的实时处理。使用方法通常是不同系统都对ZK上同一个znode进行注册,监听znode的变化(包括znode本身内容及子节点的),其中一个系统update了znode,那么另一个系统能够收到通知,并作出相应处理.
  • 1.另一种心跳检测机制:检测系统和被检测系统之间并不直接关联起来,而是通过zk上某个节点关联,大大减少系统耦合。
  • 2.另一种系统调度模式:某系统有控制台和推送系统两部分组成,控制台的职责是控制推送系统进行相应的推送工作。管理人员在控制台作的一些操作,实际上是修改了ZK上某些节点的状态,而ZK就把这些变化通知给他们注册Watcher的客户端,即推送系统,于是,作出相应的推送任务。
  • 3.另一种工作汇报模式:一些类似于任务分发系统,子任务启动后,到zk来注册一个临时节点,并且定时将自己的进度进行汇报(将进度写回这个临时节点),这样任务管理者就能够实时知道任务进度。
  • 总之,使用zookeeper来进行分布式通知和协调能够大大降低系统之间的耦合
集群管理与Master选举
  • 集群机器监控:这通常用于那种对集群中机器状态,机器在线率有较高要求的场景,能够快速对集群中机器变化作出响应。这样的场景中,往往有一个监控系统,实时检测集群机器是否存活。
  • 过去的做法通常是:监控系统通过某种手段(比如ping)定时检测每个机器,或者每个机器自己定时向监控系统汇报“我还活着”。 这种做法可行,但是存在两个比较明显的问题:
  • 1.集群中机器有变动的时候,牵连修改的东西比较多。
  • 2.有一定的延时。
  • 利用ZooKeeper有两个特性,就可以实现另一种集群机器存活性监控系统:
  • 1.客户端在节点 x 上注册一个Watcher,那么如果 x?的子节点变化了,会通知该客户端。
  • 2.创建EPHEMERAL类型的节点,一旦客户端和服务器的会话结束或过期,那么该节点就会消失。
  • 例如,监控系统在 /clusterServers 节点上注册一个Watcher,以后每动态加机器,那么就往 /clusterServers 下创建一个 EPHEMERAL类型的节点:/clusterServers/{hostname}. 这样,监控系统就能够实时知道机器的增减情况,至于后续处理就是监控系统的业务了。
  • Master选举则是zookeeper中最为经典的应用场景了。
  • 在分布式环境中,相同的业务应用分布在不同的机器上,有些业务逻辑(例如一些耗时的计算,网络I/O处理),往往只需要让整个集群中的某一台机器进行执行,其余机器可以共享这个结果,这样可以大大减少重复劳动,提高性能,于是这个master选举便是这种场景下的碰到的主要问题。
  • 利用ZooKeeper的强一致性,能够保证在分布式高并发情况下节点创建的全局唯一性,即:同时有多个客户端请求创建 /currentMaster 节点,最终一定只有一个客户端请求能够创建成功。利用这个特性,就能很轻易的在分布式环境中进行集群选取了。
分布式锁
  • 分布式锁,这个主要得益于 ZooKeeper 为我们保证了数据的强一致性。锁服务可以分为两类,一个是 保持独占,另一个是 控制时序。
  • 1.所谓保持独占,就是所有试图来获取这个锁的客户端,最终只有一个可以成功获得这把锁。通常的做法是把 zk 上的一个 znode 看作是一把锁,通过 create znode 的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。
  • 2.控制时序,就是所有视图来获取这个锁的客户端,最终都是会被安排执行,只是有个全局时序了。做法和上面基本类似,只是这里 /distributelock 已经预先存在,客户端在它下面创建临时有序节点(这个可以通过节点的属性控制:CreateMode.EPHEMERALSEQUENTIAL 来指定)。Zk 的父节点(/distribute_lock)维持一份 sequence, 保证子节点创建的时序性,从而也形成了每个客户端的全局时序。
分布式事务
  • TODO

Zookeeper 安装

  • 1.安装jdk
  • 2.安装Zookeeper. 在官网http://zookeeper.apache.org/下载zookeeper.
  • 3.解压zookeeper-3.4.12至D:\zookeeper-3.4.12
  • 4.在D:\zookeeper-3.4.12\ 新建data及log目录。
  • 5.修改配置文件至\conf 复制 zoo_sample.cfg 并粘贴到当前目录下,命名zoo.cfg。
  • zoo.cfg配置文件中添加:数据和日志存放地址,其他可以采用默认配置
dataDir=D:\\zookeeper-3.4.12\\data
  dataLogDir=D:\\zookeeper-3.4.12\\log
  • 6.安装可视化工具

Zookeeper中的角色

角色

描述

领导者(Leader)

领导者负责进行投票的发起和决议,更新系统状态

学习者(Learner)

跟随者(Follower)

Follower用于接收客户端请求并向客户端返回结果,在选主过程中参与投票

观察者(ObServer)

ObServer可以接收客户端连接,将其请求转发给Leader节点。但ObServer不参加投票过程,只同步leader的状态。ObServer的目的是为了扩展系统,提高读取速度

客户端(Client)

请求发起方

Zookeeper 节点操作流程

Leader工作流程
  • Leader主要有三个功能
  • 恢复数据
  • 维持与Learner的心跳,接收Learner请求并判断Learner的请求消息类型

  • zookeeper是注册中心 zookeeper为什么可以做注册中心_客户端_02


  • 流程
  • 1.在Client向Follwer发出一个写的请求
  • 2.Follwer把请求发送给Leader
  • 3.Leader接收到以后开始发起投票并通知Follwer进行投票
  • 4.Follwer把投票结果发送给Leader
  • 5.Leader将结果汇总后如果需要写入,则开始写入同时把写入操作通知给Leader,然后commit;
  • 6.Follwer把请求结果返回给Client
  • Follower主要有四个功能:
  • 1.向Leader发送请求(PING消息、REQUEST消息、ACK消息、REVALIDATE消息);
  • 2.接收Leader消息并进行处理;
  • 3.接收Client的请求,如果为写请求,发送给Leader进行投票;
  • 4.返回Client结果。

Service Discovery with Zookeeper

Project依赖

  • Maven依赖
  • 如果使用Zookeeper3.4.x,则需要我们自己引入Zookeeper依赖。因为官方是引用的3.5.x(快照版本)
<dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-zookeeper-all</artifactId>
          <exclusions>
              <exclusion>
                  <groupId>org.apache.zookeeper</groupId>
                  <artifactId>zookeeper</artifactId>
              </exclusion>
          </exclusions>
      </dependency>
      <dependency>
          <groupId>org.apache.zookeeper</groupId>
          <artifactId>zookeeper</artifactId>
          <version>3.4.12</version>
          <exclusions>
              <exclusion>
                  <groupId>org.slf4j</groupId>
                  <artifactId>slf4j-log4j12</artifactId>
              </exclusion>
          </exclusions>
      </dependency>
  • Gradle依赖
compile('org.springframework.cloud:spring-cloud-starter-zookeeper-all') {
    exclude group: 'org.apache.zookeeper', module: 'zookeeper'
  }
  compile('org.apache.zookeeper:zookeeper:3.4.12') {
    exclude group: 'org.slf4j', module: 'slf4j-log4j12'
  }
  • 对于web功能,您仍然需要包含org.springframework.boot:spring-boot-starter-web。

Application配置文件

  • 注意
  • If you use Spring Cloud Zookeeper Config, the values shown in the preceding example need to be in bootstrap.yml instead of application.yml.
  • application.yml
spring:
    # server name
    application:
      # zookeeper default service name
      name: hyq-zookeeper-server
    cloud:
      # zookeeper config
      zookeeper:
        # zookeeper url:port
        connect-string: localhost:2181
        discovery:
          # zookeeper root path for project(if want to use Feign client,must keep root is same)
          root: zookeeper-server
          # allowed register
          register: true
          # is disable  Zookeeper Discovery Client
          enabled: true
  server:
    port: 7083
  • zookeeper集群只需要在zoo.conf文件里面添加需要集群的zookeeper服务地址。

Zookeeper 默认实现 Ribbon

  • pring Cloud Zookeeper提供了Ribbon服务器列表的实现。当您使用spring-cloud-starter-zookeeper-discovery时,默认情况下Ribbon会自动配置为使用ZookeeperServerList。

Spring Cloud Zookeeper and Service Registry

  • Spring Cloud Zookeeper实现了ServiceRegistry接口,允许开发人员以编程方式注册任意服务。
  • ServiceInstanceRegistration类提供了一个builder()方法来创建一个service eregistry可以使用的注册对象,如下面的例子所示:
@Autowired
  private ZookeeperServiceRegistry serviceRegistry;

  public void registerThings() {
      ZookeeperRegistration registration = ServiceInstanceRegistration.builder()
              .defaultUriSpec()
              .address("anyUrl")
              .port(10)
              .name("/a/b/c/d/anotherservice")
              .build();
      this.serviceRegistry.register(registration);
  }
  • 如果将 spring.cloud.zookeeper.dependency.enabled 设置为false;我们就可以在配置文件自己配置zookeeper依赖
spring.application.name: yourServiceName
  spring.cloud.zookeeper:
    dependencies:
      newsletter:
        path: /path/where/newsletter/has/registered/in/zookeeper
        loadBalancerType: ROUND_ROBIN
        contentTypeTemplate: application/vnd.newsletter.$version+json
        version: v1
        headers:
          header1:
              - value1
          header2:
              - value2
        required: false
        stubs: org.springframework:foo:stubs
      mailing:
        path: /path/where/mailing/has/registered/in/zookeeper
        loadBalancerType: ROUND_ROBIN
        contentTypeTemplate: application/vnd.mailing.$version+json
        version: v1
        required: true
  • Load Balancer Type
  • STICKY: 一旦选中,实例总是被调用。
  • RANDOM: 随机选择一个实例。
  • ROUND_ROBIN: 一遍又一遍地迭代实例。
  • Dependencies解释:
  • spring.cloud.zookeeper.dependencies:
  • 如果不设置此属性,则不能使用Zookeeper依赖项。
  • spring.cloud.zookeeper.dependency.ribbon.enabled (enabled by default):
  • Ribbon需要显式的全局配置或依赖项的特定配置。通过打开此属性,可以实现运行时负载平衡策略解析,并且可以使用Zookeeper依赖项的loadBalancerType部分。
  • 需要此属性的配置具有LoadBalancerClient的实现,该实现将委托给下一个项目所示的ILoadBalancer。
  • spring.cloud.zookeeper.dependency.ribbon.loadbalancer (enabled by default):
  • 由于这个属性,自定义ILoadBalancer知道传递给Ribbon的URI部分实际上可能是必须在Zookeeper中解析为正确路径的别名。
  • 没有此属性,您无法在嵌套路径下注册应用程序。
  • spring.cloud.zookeeper.dependency.headers.enabled (enabled by default):
  • 此属性注册一个RibbonClient,该客户端会自动将适当的头和内容类型与其版本一起追加,如依赖项配置中所示。
  • 没有这个设置,这两个参数就不能工作。
  • spring.cloud.zookeeper.dependency.resttemplate.enabled (enabled by default):
  • 启用此属性后,将修改@LoadBalanced-annotated RestTemplate的请求头,以便通过依赖项配置中设置的版本传递头和内容类型。
  • 没有这个设置,这两个参数就不能工作。

Distributed Configuration with Zookeeper

  • Zookeeper提供一个层次命名空间,允许客户端存储任意数据,比如配置数据。Spring Cloud Zookeeper Config是配置服务器和客户端的替代品。
  • 配置在特殊的“BootStrap”阶段加载到Spring环境中。
  • 默认情况下,配置存储在/config名称空间中。根据应用程序的名称和活动概要文件创建多个PropertySource实例,以模拟解析属性的Spring Cloud配置顺序。
  • 例如,一个名为testApp并带有dev配置文件的应用程序为它创建了以下属性源:
  • config/testApp,dev
  • config/testApp
  • config/application,dev
  • config/application
  • 最特定的属性源位于顶部,而最不特定的属性源位于底部。配置/应用程序名称空间中的属性适用于所有使用zookeeper进行配置的应用程序。config/testApp名称空间中的属性只对名为testApp的服务实例可用。
  • 配置当前在应用程序启动时读取。向/refresh发送HTTP POST请求会导致重新加载配置。目前还没有实现监视配置名称空间(Zookeeper支持)。

Configuration File 配置文件

  • bootstrap.yml
spring:
    cloud:
      zookeeper:
        config:
          # Setting this value to false disables Zookeeper Config.
          enabled: true
          # Sets the base namespace for configuration values.
          root: config
          # Sets the name used by all applications.
          defaultContext: zookeeper-config
          # Sets the value of the separator used to separate the profile name in property sources with profiles.
          profileSeparator: "-"
          # 开启自动动态配置(默认是TRUE)
          watcher:
            enabled: true
  • application.yml
spring:
    # server name
    application:
      # zookeeper default service name
      name: hyq-zookeeper-config
    cloud:
      zookeeper:
        # Zookeeper URL + Port
        connect-string: localhost:2181
    profiles:
      # active pro file
      active: dev
  server:
    port: 8080
  • Zookeeper创建配置文件
  • 创建目录: /config/zookeeper-config-dev/com.hyq.info
  • config 为bootstrap.yml文件中的 root参数
  • zookeeper-config-dev:
  • zookeeper-config为bootstrap.yml文件中的 defaultContext参数
  • -dev中dev为application.yml文件中的激活变量;"-"为bootstrap.yml文件中的profileSeparator参数
  • com.hyq.info为我们的配置文件内容的Key,其值为Value
  • 如何实现动态刷新配置文件
  • pom.xml引入actuator依赖
<dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-web</artifactId>
          </dependency>
          <!--zookeeper-->
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-zookeeper-config</artifactId>
          </dependency>
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-actuator</artifactId>
          </dependency>
  • 要刷新的配置文件类要加上@RefreshScope 则实现了自动刷新配置文件
@RestController
  @RequestMapping("/zookeeper")
  @RefreshScope // 必须添加,否则不会自动刷新name的值
  public class HelloWorldController {

      @Value("${com.hyq.info}")
      private String info;

      @GetMapping("/info")
      //动态修改配置文件的值。
      public String from() {
          return this.info;
      }
  }
  • 启动类配置:
@SpringBootApplication
  @EnableDiscoveryClient
  public class ZookeeperConfigApplication {

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

  }
  • 重要:
  • zookeeper config的配置文件一定要写到 bootstrap.yml文件中,否则不会生效!

SpringCloud Config 与 SpringCloud Zookeeper 比较

  • SpringCloud Config
  • Spring cloud config就是和git(svn)集成来实现配置中心。
  • Spring cloud config分服务端、客户端和git(svn)三部分。
  • 服务端负责将Git(SVN)中存储的配置文件发布成REST接口。
  • 客户端可以从服务端REST接口获取配置(但客户端并不能主动感知到配置的变化,从而主动去获取新的配置,这需要每个客户端通过POST方法触发各自的/refresh)。
  • 其中通过git本身的属性可以达到配置版本控制的目的。有缓存形式,先把config下载到服务端本地再提供给客户端,提高可靠性。
  • Spring Cloud Config 通过文件系统,git/svn仓库来管理配置文件。包含客户端、服务端和git/svn仓库。通过git/svn特性可以达到版本控制
  • SpringCloud Zookeeper
  • 该项目通过自动配置并绑定到Spring环境,为Spring Boot应用程序提供Zookeeper集成。
  • Zookeeper提供了一个分层命名空间,允许客户端存储任意数据,如配置数据。
  • Spring Cloud Zookeeper Config是Config Server和Client的替代方案。
  • Spring Cloud Zookeeper Config 通过Zookeeper分级命名空间来储存配置项数据,另外Zookeeper可以实时监听节点变化和通知机制。