java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起

咱们在这一篇文章中大概了解了三高、架构、四个问题、相应解决方案…,会发现分布式系统架构有一个最核心的问题就是如何解决分布式环境一致性。围绕这个问题,分布式一致性问题的工业解决方案——开源框架 ZooKeeper脱颖而出。【zookeeper有挂的时候,但是挂了可以很快就恢复,快速恢复Leader就体现了zookeeper的可靠性,就因为这哥们恢复的很快,所以经常用来做分布式协调组件】

首先,整理笔记之前,特此感谢一下Zookeeper的官方文档,因为里面好多内容都是和官方文档离不开的。
PART1:Zookeeper概念篇

  • Zookeeper是Apache Hadoop项目下的一个子项目【ZooKeeper 是 Hadoop 生态系统的一员】,是一个树形目录服务数据模型和Unix的文件系统目录树很类似,拥有一个层次化结构
    • Zookeeper 是一个开源的,是用于维护配置信息,命名,提供分布式同步和提供组服务的集中式服务。
      java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第1张图片
    • 但是Unix的目录下是不能直接存数据的,数据必须要写在文件中,然后目录结构中存文件;而Zookeeper中是可以直接存数据的。咱们可以向这个节点中写入数据,也可以在节点下面创建子节点。
    • ZooKeeper 是一个开源的分布式协调服务,用于将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语【原语指的是操作系统或计算机网络用语范畴。是由若干条指令组成的,用于完成一定功能的一个过程。具有不可分割性·即原语的执行必须是连续的,在执行过程中不允许被中断。】集,一个通用的无单点问题的分布式协调框架,并以一系列简单易用的接口提供给用户使用。
  • Zookeeper数据模型(Data model):层次化的多叉树形结构中的 每一个节点都被称为ZNode,每个节点上都会保存自己的数据和父子节点信息
    • Zookeeper 的数据结构:ZooKeeper 数据模型采用层次化的多叉树形结构,每个节点上都可以存储数据,这些数据可以是数字、字符串或者是二级制序列。并且。每个节点还可以拥有 N 个子节点,最上层是根节点以“/”来代表
      • ZooKeeper 提供的名称空间与标准文件系统的名称空间非常相似。名称是由斜杠(“ /”)分隔的一系列路径元素ZooKeeper每个数据节点在 ZooKeeper 中被称为 znode,ZooKeeper每个被称为znode的数据节点是 ZooKeeper 中数据的最小单元。ZooKeeper 命名空间中的每个 znode 均由一个唯一的路径标识每个 znode 都有一个父对象,其路径是 znode 的前缀,元素少一个;此规则的例外是 root(“ /”),它没有父项。此外,与标准文件系统完全一样,如果 znode 有子节点,则无法删除它
      • ZooKeeper 与标准文件系统之间的主要区别在于每个 znode 都可以具有与之关联的数据(每个文件也可以是目录,反之亦然),并且 znode 限于它们可以拥有的数据量。ZooKeeper 旨在存储协调数据:状态信息,配置,位置信息等。这种元信息通常以千字节(如果不是字节)来度量。ZooKeeper 具有1M的内置完整性检查,以防止将其用作大型数据存储,但是通常,它用于存储小得多的数据。 ZooKeeper 不适合保存大量数据
    • 节点可以拥有子节点,同时也允许少量(1MB)数据存储在该节点之下
      • ZooKeeper 主要是用来协调服务的,而不是用来存储业务数据的,所以不要放比较大的数据在 znode 上,ZooKeeper 给出的上限是每个结点的数据大小最大是 1M
    • Znode里面都存储了什么:
      java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第2张图片
      • ZooKeeper 将数据保存在内存中,性能是非常棒的。 在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景)。
      • Znode包含了 存储数据(data)、访问权限(acl)、子节点引用(child)、节点状态信息(stat)【对应于每个 znode,ZooKeeper 都会为其维护一个叫作 Stat 的数据结构,Stat 中记录了这个 znode 的三个相关的版本:】
        java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第3张图片
        • Stat类中包含了一个数据节点的所有状态信息的字段,包括事务 ID-cZxid、节点创建时间-ctime 和子节点个数-numChildren 等等。
          • zookeeper有顺序一致性,所以你来了很多写操作到主节点时,人家会按顺序处理。顺序会体现在cZxid上【单机上维护一个递增器很方便,自己说话就能决策】。前64位表示你是第几个leader【leader纪元】,后32表示事务ID,即Zxid
        • ACL(权限控制):ZooKeeper 采用 ACL(AccessControlLists)策略来进行权限控制,类似于 UNIX 文件系统的权限控制。
          java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第4张图片
          • 对于 znode 操作的权限,ZooKeeper 提供了以下 5 种:
            • CREATE : 能创建子节点【CREATE 和 DELETE 这两种权限都是针对 子节点 的权限控制。】
            • READ :能获取节点数据和列出其子节点【CREATE 和 DELETE 这两种权限都是针对 子节点 的权限控制。】
            • WRITE : 能设置/更新节点数据
            • DELETE : 能删除子节点
            • ADMIN : 能设置节点 ACL 的权限
    • Znode节点可以分为四种类型
      • PERSISTENT持久节点一旦创建就一直存在即使 ZooKeeper 集群宕机也存在直到将其删除
        • 持久节点(persistent node)节点会被持久。持久节点:如 create /test/a “hello”,通过 create参数指定为持久节点
          • data: znode存储的业务数据信息
          • acl: 记录客户端对znode节点的访问权限,如IP等。
          • child: 当前节点的子节点引用
          • stat: 包含Znode节点的状态信息,比如事务id、版本号、时间戳等等。
      • EPHEMERAL:临时节点
        • 临时节点(ephemeral node):临时节点的生命周期是与客户端会话(session) 绑定的,客户端断开连接后,相当于会话消失则节点消失 ,ZooKeeper 会自动删除临时节点
          • 会话(Session):zookeeper有session这个概念,没有连接池这个概念。
            • Session 可以看作是 ZooKeeper 服务器与客户端的之间的一个 TCP 长连接
              • 通过这个连接,客户端能够通过心跳检测与ZooKeeper服务器保持有效的会话
              • 通过这个连接,客户端也能够向 ZooKeeper 服务器发送请求并接受响应
              • 通过这个连接,同时还能够通过该连接接收来自服务器的 Watcher 事件通知
            • Session 有一个属性叫做:sessionTimeout ,sessionTimeout 代表会话的超时时间。【当由于服务器压力太大、网络故障或是客户端主动断开连接等各种原因导致客户端连接断开时,只要在sessionTimeout规定的时间内能够重新连接上集群中任意一台服务器,那么之前创建的会话仍然有效。】
            • 另外,在为客户端创建会话之前,服务端首先会为每个客户端都分配一个 sessionID。由于 sessionID是 ZooKeeper 会话的一个重要标识,许多与会话相关的运行机制都是基于这个 sessionID 的,因此,无论是哪台服务器为客户端分配的 sessionID,都务必保证sessionID全局唯一
              • 换个角度来看,zookeeper是统一视图,通过session
            • zookeeper 中,会话还有对应的事件,比如 CONNECTION_LOSS 连接丢失事件 、SESSION_MOVED 会话转移事件 、SESSION_EXPIRED 会话超时失效事件
        • 通过 create -e参数指定为顺序节点
        • 临时节点只能做叶子节点 ,不能创建子节点
      • PERSISTENT_SEQUENTIAL:持久顺序节点
        • 除了具有持久(PERSISTENT)节点的特性之外, 子节点的名称还具有顺序性【顺序节点(sequential node),每次创建顺序节点时,ZooKeeper 都会在路径后面自动添加上10位的数字,从1开始,最大是2147483647 (2^32-1)】。比如 /node1/app0000000001 、/node1/app0000000002 。
        • 持久顺序节点:通过 create -s参数指定为顺序节点
      • EPHEMERAL_SEQUENTIAL临时顺序节点:-es。
        • 临时顺序节点:通过 create -s -e参数指定为临时及顺序节点
        • 除了具备临时(EPHEMERAL)节点的特性之外,子节点的名称还具有顺序性【顺序节点(sequential node),每次创建顺序节点时,ZooKeeper 都会在路径后面自动添加上10位的数字,从1开始,最大是2147483647 (2^32-1)】。
  • Zookeeper有哪些特性或者说特点呢?
    java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第5张图片
    • 顺序一致性: 从 同一客户端发起的事务请求,最终将 会严格地按照顺序 被应用到 ZooKeeper 中去。
      • 牵涉到序号,那跟Czxid事务id是分不开的
    • 原子性: 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用
    • 单一系统映像 : 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的
      • 客户端跟zookeeper建立连接时会产生一个代表客户端信息的sessionid,而这个sessionid信息会被共享,使得会话保持分布式一致
    • 可靠性: 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。【可靠性这块redis是比不上zookeeper】
      • zookeeper有挂的时候,但是挂了可以很快就恢复,快速恢复Leader就体现了zookeeper的可靠性
      • 并且最终一致性也体现了可靠性(不用强一致性就是因为强一致性会影响高可用,强一致性其实就指的是全部通过,全部节点参与决策,而不是过半),zookeeper的最终一致性的过程中节点是否对外提供服务,未过半的节点要shutdown不能对外提供服务了
  • Zookeeper提供的主要功能包括:(ZooKeeper 为我们提供了高可用、高性能、稳定的分布式数据一致性解决方案,可以基于 Zookeeper 实现诸如数据发布/订阅【也就是Zookeeper可以作为注册中心】、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。)
    java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第6张图片
    • 配置管理
      java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第7张图片
      • 比如说咱们有很多客户端(服务器),这些服务器在启动的时候需要很多配置,或者运行时我zookeeper要知道哪些配置被变更了,不就用上zookeeper了嘛。把所有(客户端或者服务器)配置数据存到zookeeper中去,所有客户端只需要get或者watch这些zookeeper中的配置数据,有变动则回调函数
    • 选主:因为 Zookeeper 的强一致性,能够很好地在保证 在高并发的情况下保证节点创建的全局唯一性 (即无法重复创建同样的节点)。用这个特性,我们可以让多个客户端创建一个指定的节点 ,创建成功的就是 master。但是,如果这个 master 挂了怎么办???所以我们可以利用 临时节点、节点状态 和 watcher 来实现选主的功能,临时节点主要用来选举,节点状态和watcher 可以用来判断 master 的活性和进行重新选举
      • 但是zookeeper不会利用强一致性(因为强一致性会影响高可用性),会按照最终一致性去和各个从节点进行数据同步。所以zookeeper还有一个特点就是在特定范围内数据会一致而不是实时性的一致
    • 集群管理和注册中心:
      • 集群管理:需要了解整个集群中有多少机器在工作,我们想对集群中的每台机器的运行时状态进行数据采集,对集群中机器进行上下线操作等等。而 zookeeper 天然支持的 watcher 和 临时节点能很好的实现这些需求。我们 可以为每条机器创建临时节点,并监控其父节点,如果子节点列表有变动(我们可能创建删除了临时节点),那么我们可以使用在其父节点绑定的 watcher 进行状态监控和回调
        java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第8张图片
      • 注册中心:让 服务提供者 在 zookeeper 中创建一个临时节点并且将自己的 ip、port、调用方式 写入节点,当 服务消费者 需要进行调用的时候会 通过注册中心找到相应的服务的地址列表(IP端口什么的) ,并缓存到本地(方便以后调用)当消费者调用服务时,不会再去请求注册中心,而是直接通过负载均衡算法从地址列表中取一个服务提供者的服务器调用服务。当服务提供者的某台服务器宕机或下线时,相应的地址会从服务提供者地址列表中移除。同时,注册中心会将新的服务地址列表发送给服务消费者的机器并缓存在消费者本机(当然你可以让消费者进行节点监听)。
        java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第9张图片
    • 命名服务 :可以通过 ZooKeeper 的顺序节点生成全局唯一 ID
      • 给一个对象设置ID可以用UUID,但是UUID太长了。zookeeper 是通过 树形结构 来存储数据节点的,那也就是说,对于每个节点的 全路径,它必定是唯一的,我们可以使用节点的全路径作为命名方式了。而且更重要的是,路径是我们可以自己定义的,这对于我们对有些有语意的对象的ID设置可以更加便于理解。
    • 数据发布/订阅 :通过 Watcher 机制 可以很方便地实现数据发布/订阅当你将数据发布到 ZooKeeper 被 Watcher 机制监听的节点上,其他机器可通过监听 ZooKeeper 上节点的变化来实现配置的动态更新
    • 分布式锁:【通过创建唯一节点获得分布式锁,当获得锁的一方执行完相关代码或者是挂掉之后就释放锁】原来为了防止并发访问产生的线程安全问题,可以用synchronized或者lock单独加锁,但是在分布式中你A服务加的锁对B服务肯定是没作用的(有可能就没在一个电脑上,怎么可能有作用?【分布式指的是很多的节点在不同的物理位置,他们之间要相互通信传递数据】),所以搞个分布式锁,通过创建唯一节点获得分布式锁,当获得锁的一方执行完相关代码或者是挂掉之后就释放锁,其他方才能获得锁继续执行
      java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第10张图片
      • 分布式锁实现:资源在谁那,分布式锁就加到哪里(别加到代理中介那里去了)【分布式锁的实现方式有很多种,比如 Redis 、数据库 、zookeeper 等】
        • 在我们进行单机应用开发,涉及并发同步的时候,我们往往采用synchronized或者Lock的方式来解决多线程间的代码同步问题,这时多线程的运行都是在同一个JVM之下, 没有任何问题。但当我们的应用是分布式集群工作的情况下,属于多JVM下的工作环境,跨JVM之间已经无法通过多线程的锁解决同步问题。那么就需要一种更加高级的锁机制, 处理种跨机器的进程之间的数据同步问题 一这就是分布式锁。
        • 分布式锁实现思路中需要考虑的几个点:其实咱们单机中实现锁基本上思路也是差不多,你该tryLock还是得tryLock,你该释放还得释放呀,通知呀、防止死锁等肯定是重要的几个点
          • 第一点:怎样争抢锁,保证只有一个节点获得锁
          • 第二点:获得锁之后若此节点挂了有可能造成死锁,所以我们使用临时节点【临时节点是伴随着客户端的session,客户端和zookeeper连接上之后就会产生一个session】,用完删除即可解决死锁问题
          • 第三点:获得锁的节点成功了,用完了要怎样释放锁
          • 第四点:锁被释放或者被删除,别的节点怎么知道
            • 主动轮询,心跳。弊端就是有时间延迟,并且如果节点很多,轮询处理压力很大
            • zookeeper的watch机制,可以解决延迟。但是弊端是通信上有压力,你回调之后,剩余的很多个节点依旧会一拥而上抢锁,所以压力依旧很大所以考虑序列节点+watch两机制混合使用
              • watch父节点,如果第一个得到锁的节点释放了锁,那么父节点会产生children事件,发给所有的子节点,压力很大的问题还是没有解决
              • watch前一个人,最小的获得锁,第二个watch第一个,第三个watch第二个...。一旦最小的释放锁,zookeeper只给第二个发事件,回调,此时的资源消耗是最小的
        • 当然,有很多前辈们也写了很多经典的zookeeper实现分布式锁的思路和代码,咱们可以参考学习:
          • https://cloud.tencent.com/developer/article/2060617
          • https://cloud.tencent.com/developer/article/1907772#:~:text=Zookeepe,%E9%94%81%E5%AE%9A%E8%AF%A5%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E3%80%82
          • https://www.runoob.com/w3cnote/zookeeper-locks.html
        • zk在高并发的情况下保证节点创建的全局唯一性。在 分布式的情况下实现分布式锁。可以利用临时节点的创建来实现。 创建节点的唯一性,我们可以让多个客户端同时创建一个临时节点,创建成功的就说明获取到了锁 。然后没有获取到锁的客户端也像上面选主的非主节点创建一个 watcher 进行节点状态的监听,如果这个互斥锁被释放了(可能获取锁的客户端宕机了,或者那个客户端主动释放了锁)可以调用回调函数重新获得锁
          java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第11张图片
          • redis这些实现分布式锁很麻烦,开了持久化又得扛着IO磁盘操作。
          • zk 中不需要向 redis 那样考虑锁得不到释放的问题了,因为当客户端挂了,节点也挂了,锁也释放了
      • Zookeeper分布式锁原理:核心思想是当客户端要获取锁,则创建节点,使用完锁,则删除该节点。
        • 1.客户端获取锁时,在lock节点下创建临时顺序节点。
          • (创建一个临时的,以保证可以删除啊,你搞成持久化节点,万一获取锁那个节点万一宕机了,这个节点就没办法删除了,只有删除了才能保证这个锁被释放。搞成临时的哪怕宕机了他也会自动删除
          • 顺序的,是因为要找到序号最小的节点呀,所以得排个顺序呀
        • 2.然后获取lock 下面的所有子节点,客户端获取到所有的子节点之后,如果发现自己创建的子节点序号最小(找到序号最小的那个就是锁),那么就认为该客户端获取到了锁。使用完锁后,将该节点删除。
        • 3.如果发现自己创建的节点并非lock所有子节点中最小的,说明自己还没有获取到锁,此时客户端需要找到比自己小的那个节点,同时对其注册事件监听器,监听删除事件。
        • 4.如果发现比自己小的那个节点被删除,则客户端的Watcher会收到相应通知,此时再次判断自己创建的节点是否是lock子节点中序号最小的,如果是则获取到了锁,如果不是则重复以上步骤继续获取到此自己小的一个节点并注册监听。
        • 5.用完锁之后就删除
      • 使用 zookeeper 同时实现 共享锁和独占锁:让 读请求监听比自己小的最后一个写请求节点,写请求只监听比自己小的最后一个节点
        java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第12张图片
    • 集群管理
      java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第13张图片
    • Zookeeper集群:
      • 为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样 只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的通常 3 台服务器就可以构成一个 ZooKeeper 集群了
        java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第14张图片
        java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第15张图片
        • 最典型集群模式: Master/Slave 模式(主备模式)。在这种模式中,通常 Master 服务器作为主服务器提供写服务,其他的 Slave 服务器从服务器通过异步复制的方式获取 Master 服务器最新的数据提供读服务
      • Zookeeper的系统架构:
        java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第16张图片
        • ZooKeeper 分为服务器端(Server) 和客户端(Client),客户端可以连接到整个 ZooKeeper 服务的任意服务器上(除非 leaderServes 参数被显式设置,leader 不允许接受客户端连接),客户端使用并维护一个 TCP 连接,通过这个连接发送请求、接受响应、获取观察的事件以及发送信息。
          • 组成 ZooKeeper 服务的服务器必须彼此了解。它们维护一个内存中的状态图像,以及持久存储中的事务日志和快照,只要大多数服务器可用,ZooKeeper 服务就可用
        • Zookeeper 建议集群节点个数为奇数,只要超过一半的机器能够正常提供服务,那么整个集群都是可用的状态。为啥要奇数台?
          • zookeeper有两种运行状态:
            • 可用状态:不可用状态恢复到可用状态越快越好,官网说的恢复时间不超过200ms,也就是有Leader时算是可用状态
            • 不可用状态:Leader没有过半范围了,不能过半了,相当于Leader不能在进行决策来实现各种写操作了,没和Leader建立连接,相当于此节点对应的进程虽然在运行,但是此节点对外不提供服务
          • ZooKeeper 集群在宕掉几个 ZooKeeper 服务器之后,如果剩下的 ZooKeeper 服务器个数 大于 宕掉的个数的话整个 ZooKeeper 才依然可用。假如我们的集群中有 n 台 ZooKeeper 服务器,那么也就是剩下的服务数必须大于 n/2,也就是说 最大坏掉的机器数必须小于n/2。先说一下结论,2n 和 2n-1 的容忍度是一样的,比如假如我们有 3 台,那么最大允许宕掉 1 台 ZooKeeper 服务器,如果我们有 4 台的的时候也同样只允许宕掉 1 台。 假如我们有 5 台,那么最大允许宕掉 2 台 ZooKeeper 服务器,如果我们有 6 台的的时候也同样只允许宕掉 2 台。综上,何必增加那一个不必要的 ZooKeeper 呢,所以就是最大坏掉的机器数必须小于n/2
        • 在ZooKeeper集群中服务(Server)有三个角色【在 ZAB 协议中对这三个角色的总称为zkServer】【在 ZooKeeper 中没有选择传统的 Master/Slave 概念,选择这种传统的主从模式只有两个角色呀】:Leader、Follower 和 Observer(将 server 分为三种是为了避免太多的从节点参与过半写的过程,导致影响性能,这样 Zookeeper 只要使用一个几台机器的小集群就可以实现高性能了,如果要横向扩展的话,只需要增加 Observer 节点即可。)
          java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第17张图片
          • Leader领导者:负责投投票【能够发起投票(投票也是为了进行写请求)】的发起与决议,更新系统状态【Leader 负责处理数据更新等操作【Leader 既可以为客户端提供写服务又能提供读服务,集群中唯一的写请求处理者。】,一个更新操作成功的标志是当且仅当大多数 Server 在内存中成功修改数据。每个 Server 在内存中存储了一份数据】,写数据【Leader 既可以为客户端提供写服务又能提供读服务
            • 1.处理事务请求【zookeeper跟redis还有相似的地方就是:leader节点提供读写操作,而Follow和Observer只提供读操作。这不就是读写分离zookeeper嘛
              • 缺陷依旧,只有一个主节点的集群,不管是redis还是zookeeper,都要考虑单点故障等问题,从而会影响高可用问题。leader挂了之后,跟redis相似,但是redis过半时不知道哪些节点过半,而zookeeper可以知道哪些台节点过半了【配置文件中记录了都有哪些节点】
              • 当然也有优点,优点就是写操作来了由他一个人说了算,处理速度肯定快得很。
            • 2.集群内部各服务器的调度者
          • Follower跟随者:用于接收客户端请求并用来返回结果【Follower 和 Observer 都只能提供读服务】,在选主过程中参与投票
            • 1.为客户端提供读服务【能够接收客户端的请求,如果是读请求则可以自己处理,如果是写请求则要转发给 Leader】,如果是写服务则转发给 Leader
            • 2.在选举过程中会参与投票,有选举权和被选举权 【只有Follow才能参加选举,成为Leader】
            • Follower 和 Observer 唯一的区别在于 Observer 机器不参与 Leader 的选举过程,也不参与写操作的“过半写成功”策略,因此 Observer 机器可以在不影响写性能的情况下提升集群的读性能。
          • Observer观察者:【就是没有选举权和被选举权的 Follower】以接受客户端连接,将写请求转发给leader节点,但是不参与投票过程,只同步leader状态主要存在目的就是为了提高读取效率【在不影响写性能的情况下提升集群的读性能。此角色于 ZooKeeper3.3 系列新增的角色。】Follower 和 Observer 都只能提供读服务
            • 1.为客户端提供读服务,如果是写服务则转发给 Leader。【Observer放大了系统的查询/读能力】
            • 2.不参与选举过程中的投票,也不参与“过半写成功”策略。
        • Leader选举:【当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,就会进入 Leader 选举过程。ZooKeeper 集群中的所有机器启动时通过一个 Leader 选举过程来选定一台称为 “Leader” 的机器】在Leader选举的过程中,如果某台ZooKeeper获得了超过半数的选票,则此ZooKeeper就可以成为Leader了。
          • Serverid:服务器ID比如有三台服务器,编号分别是1,2,3.编号越大在选择算法中的权重越大。
          • Zxid:数据ID,服务器中存放的最大数据ID.值越大说明数据越新。在选举算法中数据越新权重越大。
            • Zookeeper 如何识别请求的先后顺序
              在这里插入图片描述
          • Leader 选举过程整个过程:
            • 1.Leader election(选举阶段):节点在一开始都处于选举阶段,只要有一个节点得到超半数节点的票数,它就可以当选准 leader。
            • 2.Discovery(发现阶段) :在这个阶段,followers 跟准 leader 进行通信,同步 followers 最近接收的事务提议。
            • 3.Synchronization(同步阶段) :同步阶段主要是利用 leader 前一阶段获得的最新提议历史,同步集群中所有的副本。同步完成之后 准 leader 才会成为真正的 leader。
            • 4.Broadcast(广播阶段) :到了这个阶段,ZooKeeper 集群才能正式对外提供事务服务,并且 leader 可以进行消息广播。同时如果有新的节点加入,还需要对新节点进行同步。
          • Zookeeper初始化是如何进行Leader选举的:选举过程是怎样
            在这里插入图片描述
            • 集群初始化阶段只有两台以以上的 ZK 启动才会发生leader选举,选举过程如下:
              • (1) 每个 Server 发出一个投票。初始选举 ZK1 和 ZK2 都会将自己作为 Leader 服务器来进行投票,每次投票会包含所推举的服务器的(myid, ZXID),此时 ZK1 的投票为(1, 0),ZK2 的投票为(2, 0),然后各自将这个投票发给集群中其他机器。
                • 3888选举端口造成两两通信
                • 只要任何人投票,都会触发那个准leader发起自己的投票,然后其他节点就开始推选准leader
              • (2) 收到投票。集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票、是否来自 LOOKING 状态的服务器。
                • ZooKeeper 集群中的服务器状态:
                  • LOOKING :寻找 Leader。
                  • LEADING :Leader 状态,对应的节点为 Leader。
                  • FOLLOWING :Follower 状态,对应的节点为 Follower。
                  • OBSERVING :Observer 状态,对应节点为 Observer,该节点不参与 Leader 选举
              • (3) 处理投票。每个发起投票的服务器需要将别人的投票和自己的投票进行比较,规则如下:【如果zxid相同,再比较myid,如果过半的话【如果不过半的话就会进入不可用状态,对外停止服务,保证不能向外提供脏数据】,通过通信选一个myid最大的作为Leader】
                • 优先检查 ZXID。ZXID 比较大的服务器优先作为 Leader
                  • 【zxid小的肯定不能被选为leader】
                • 如果 ZXID 相同,那么就比较 myid。myid 较大的服务器作为Leader服务器
                  • 【大部分时候zxid都相同】。基本上就两种情况:情况一就是第一次启动集群时,达到过半后,因为集群中节点是两两连接的,所以通过通信选一个myid最大的作为Leader情况二就是如果集群重启,Leader挂了。经过过程中来回来回的投票投票…,最终各个节点内存中都形成了一个状态就是所有人都投的myid最大的了一票
                  • zookeeper集群中的各个节点有自己的myid,然后有着各自myid的每个节点都存着一份事务id为Zxid的事务操作版本号(一般没进入过半范围的那个,事务id一般比人家过半的节点低一点,人家之间相互有照应,过半通过的数据才是真的有效数据,数据会一致,你被排除在过半外的节点的决策已经不算数了)
              • (4) 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于 ZK1、ZK2 而言,都统计出集群中已经有两台机器接受了(2, 0)的投票信息,此时便认为已经选出 ZK2 作为Leader。
              • (5) 改变服务器状态。一旦确定了 Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为 FOLLOWING,如果是 Leader,就变更为 LEADING。当新的 Zookeeper 节点 ZK3 启动时,发现已经有 Leader 了,不再选举,直接将直接的状态从 LOOKING 改为 FOLLOWING。
          • 选举 leader 后是怎么进行数据同步的:(proposal : 其实就是将请求中的一些信息如请求头,请求体以及 ZXID 等信息封装到 proposal对象当中)
            java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第18张图片
            • 四种同步方式:
              • 差异化同步:触发条件:minZXID < lastSyncZXID < maxZXID
                • 1).leader 向 Observer 和 follower 发送 DIFF 指令,之后就开始差异化同步
                • 2).然后把差异数据 提议 proposal 发送给 Observer 和 follower , Observer 和 follower 返回ACK表示已经完成了同步
                • 3).只要集群中过半的 Observer 和 follower 响应了 ACK 就发送一个 UPTODATE 命令
                • 4).leader 返回 ACK,同步流程结束
              • 回滚同步:触发条件 maxZXID < lastSyncZXID
                • 直接回滚到 maxZXID
                • 举个例子:a,b,c三台服务服务器 a是leader,此时队列里面最大的 ZXID 为100,a 收到请求,该 ZXID 为101,还没来得及发送同步数据 a 就挂了,b 变为leader,然后 a 恢复了,此时就需要 a 先将之前 ZXID 为101的数据回滚
              • 回滚+差异化同步:触发条件:如果Leader刚生成一个proposal,还没有来得及发送出去,此时Leader宕机,重新选举之后作为Follower,但是新的Leader没有这个proposal数据
                • 1).Observer 和 follower 将数据回滚
                • 2).进行差异化同步
                  • 举个例子:a,b,c三台服务服务器 a是leader,此时队列里面最大的 ZXID 为100,a 收到请求,该 ZXID 为101,还没来得及发送同步数据 a 就挂了,b 变为leader,b 又处理了3个请求,则 b 队列中最大的 ZXID 为103,然后 a 恢复了,此时就需要 a 先将之前 ZXID 为101的数据回滚,再进行同步
              • 全量同步:同步过程:leader 向 Observer 和 follower 发送SNAP命令,进行数据全量同步
                • 1).lastSyncZXID < minZXID
                • 2).Leader服务器上没有缓存队列,并且lastSyncZXID!=maxZXID
        • 集群搭建:
          • 真实的集群,以IP区分
          • 伪集群,以端口区分
  • ZooKeeper 选举的 过半机制防止脑裂
    • 集群脑裂:
      • 对于一个集群,通常多台机器会部署在不同机房,来提高这个集群的可用性。保证可用性的同时,会发生一种机房间网络线路故障,导致机房间网络不通,而集群被割裂成几个小集群。这时候子集群各自选主导致“脑裂”的情况
      • 比如现在有一个由 6 台服务器所组成的一个集群,部署在了 2 个机房,每个机房 3 台。正常情况下只有 1 个 leader,但是当两个机房中间网络断开的时候,每个机房的 3 台服务器都会认为另一个机房的 3 台服务器下线,而选出自己的 leader 并对外提供服务。若没有过半机制,当网络恢复的时候会发现有 2 个 leader。仿佛是 1 个大脑(leader)分散成了 2 个大脑,这就发生了脑裂现象。脑裂期间 2 个大脑都可能对外提供了服务,这将会带来数据一致性等问题
      • 过半机制是如何防止脑裂现象产生的?
        • ZooKeeper 的过半机制导致不可能产生 2 个 leader,因为少于等于一半是不可能产生 leader 的,这就使得不论机房的机器如何分配都不可能发生脑裂
  • ZAB(ZooKeeper Atomic Broadcast) 原子广播协议:Zookeeper 的数据一致性是依靠ZAB协议完成的【一篇关于ZAB的好文章】
    • Paxos 算法应该可以说是 ZooKeeper 的灵魂了。但是,ZooKeeper 并没有完全采用 Paxos 算法 ,而是使用ZAB(ZooKeeper Atomic Broadcast) 原子广播协议作为其保证数据一致性的核心算法。另外,在 ZooKeeper 的官方文档中也指出,ZAB(ZooKeeper Atomic Broadcast) 原子广播协议并不像 Paxos 算法那样,是一种通用的分布式一致性算法,ZAB(ZooKeeper Atomic Broadcast) 原子广播协议是一种特别为 Zookeeper 设计的崩溃可恢复的原子消息广播算法
      • 这里,ctrl +F搜索“Paxos”可以了解更详细的Paxos
      • Paxos是一个基于消息传递的分布式一致性算法,Paxos有个前提就是没有拜占庭将军问题【拜占庭将军问题其实指的就是信任所有的通信环节,https://www.douban.com/note/208430424/?_i=9603837TYVT9W5】
      • 其中涉及到了两阶段来解决分布式通信过程中有可能出现的问题:
        • 第一步先写日志,给所有节点发提议,发起者等待过半的回复;保证数据操作的可靠性
        • 第二步,收到过半回复后,进行数据操作的实现
    • Zookeeper 会有数据不一致的情况发生吗?
      • 还是会有的,因为 Zookeeper 采用的是过半写机制,意味着3台服务器只要有两台写成功就代表整个集群写成功,如果刚好有请求打在这台还未写的服务器上就查询不到该数据,就会有数据不一致的情况产生。
    • ZAB(ZooKeeper Atomic Broadcast 原子广播) 协议是为 ZooKeeper 特殊设计的一种支持崩溃恢复的原子广播协议。在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性
      • zookeeper在分布式情况下对Paxos协议做了一个更简单的实现,就是ZAB协议(原子广播协议,原子指的是要么成功要么失败,没有中间状态。此处指的是队列+2PC;广播在分布式中代表全部节点知道,广播强调过半;队列具有顺序行,因为他是FIFO
        java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第19张图片
      • ZAB协议作用在可用状态下,也就是有Leader时
    • ZAB 协议包括有两种模式,分别是 崩溃恢复和消息广播。【在 ZAB 协议中对 zkServer(即三个角色的总称:ZAB 中三个主要的角色,Leader 领导者、Follower跟随者、Observer观察者 。) 还有两种模式的定义,分别是 消息广播 和 崩溃恢复】
      • 崩溃恢复:当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进人恢复模式并选举产生新的 Leader 服务器。当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该 Leader 服务器完成了状态同步【所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和 Leader 服务器的数据状态保持一致。】之后,ZAB 协议就会退出恢复模式。剩下未同步完成的机器会继续同步,直到同步完成并加入集群后该节点的服务才可用
        • 如果只是 Follower 挂了,而且挂的没超过半数的时候,在 Leader 中会维护队列,所以不用担心后面的数据没接收到导致数据不一致性
        • 如果 Leader 挂了,我们肯定需要先暂停服务变为 Looking 状态然后进行 Leader 的重新选举。这个就要分为两种情况了,分别是 确保已经被Leader提交的提案最终能够被所有的Follower提交跳过那些已经被丢弃的提案
          • 确保已经被Leader提交的提案最终能够被所有的Follower提交
            java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第20张图片
          • 跳过那些已经被丢弃的提案
            java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第21张图片
        • 【当系统出现崩溃影响最大应该是 Leader 的崩溃,因为我们只有一个 Leader ,所以当 Leader 出现问题的时候我们势必需要重新选举 Leader】Leader 选举可以分为两个不同的阶段
          • 第一个是 Leader 宕机需要重新选举
          • 第二则是当 Zookeeper 启动时需要进行系统的 Leader 初始化选举
            • 假设我们集群中有3台机器,那也就意味着我们需要两台以上同意(超过半数)。比如这个时候我们启动了 server1 ,它会首先 投票给自己 ,投票内容为服务器的 myid 和 ZXID ,因为初始化所以 ZXID 都为0,此时 server1 发出的投票为 (1,0)。但此时 server1 的投票仅为1,所以不能作为 Leader ,此时还在选举阶段所以整个集群处于 Looking 状态
            • 接着 server2 启动了,它首先也会将投票选给自己(2,0),并将投票信息广播出去(server1也会,只是它那时没有其他的服务器了),server1 在收到 server2 的投票信息后会将投票信息与自己的作比较。首先它会比较 ZXID ,ZXID 大的优先为 Leader,如果相同则比较 myid,myid 大的优先作为 Leader。所以此时server1 发现 server2 更适合做 Leader,它就会将自己的投票信息更改为(2,0)然后再广播出去,之后server2 收到之后发现和自己的一样无需做更改,并且自己的 投票已经超过半数 ,则 确定 server2 为 Leader,server1 也会将自己服务器设置为 Following 变为 Follower。整个服务器就从 Looking 变为了正常状态。
            • 当 server3 启动发现集群没有处于 Looking 状态时,它会直接以 Follower 的身份加入集群
              • 举个例子:
                java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第22张图片
        • 如果Leader挂了,进入崩溃恢复,怎么选举Leader?
          在这里插入图片描述
          • 1.变更状态。Leader 挂后,余下的非 Observer 服务器都会讲自己的服务器状态变更为 LOOKING,然后开始进入 Leader 选举过程。
          • 2.每个非 Observer 的 Server 会发出一个投票。和启动过程一致。
          • 3.接收来自各个服务器的投票。与启动时过程相同。
          • 4.处理投票。与启动时过程相同。
          • 5.统计投票。与启动时过程相同。
          • 6.改变服务器的状态。与启动时过程相同。
      • 消息广播:就是来 定义ZAB 协议是如何处理写请求的
        • 上面我们不是说只有 Leader 能处理写请求嘛?那么我们的 Follower 和 Observer 是不是也需要 同步更新数据 呢?总不能数据只在 Leader 中更新了,其他角色都没有得到更新吧?所以我们得在整个集群中保持数据的一致性
          • 第一步肯定需要 Leader 将写请求 广播 出去呀,让 Leader 问问 Followers 是否同意更新,如果超过半数以上的同意那么就进行 Follower 和 Observer 的更新(和 Paxos 一样)
        • 当集群中已经有过半的 Follower 服务器完成了和 Leader 服务器的状态同步,那么整个服务框架就可以进人消息广播模式了。当一台同样遵守 ZAB 协议的服务器启动后加人到集群中时,如果此时集群中已经存在一个 Leader 服务器在负责进行消息广播,那么新加人的服务器就会自觉地进人数据恢复模式:找到 Leader 所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。ZooKeeper 设计成只允许唯一的一个 Leader 服务器来进行事务请求的处理。Leader 服务器在接收到客户端的事务请求后,会生成对应的事务提案并发起一轮广播协议;而如果集群中的其他机器接收到客户端的事务请求,那么这些非 Leader 服务器会首先将这个事务请求转发给 Leader 服务器
          • ZAB 需要让 Follower 和 Observer 保证顺序性 。
            java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第23张图片
          • 除此之外,在 ZAB 中还定义了一个 全局单调递增的事务ID ZXID【定义这个的原因也是为了顺序性,每个 proposal 在 Leader 中生成后需要 通过其 ZXID 来进行排序 ,才能得到处理。】 ,它是一个64位long型,其中高32位表示 epoch 年代,低32位表示事务id
            • 高32位表示 epoch 年代:epoch 是会根据 Leader 的变化而变化的,当一个 Leader 挂了,新的 Leader 上位的时候,年代(epoch)就变了。
            • 低32位表示事务id:低32位可以简单理解为递增的事务id。

PART2:Zookeeper实践篇

  • Zookeeper命令操作:javaGuide老师关于ZooKeeper实战的好文章,很赞
    • 服务端常用命令:
      • 启动ZooKeeper服务: ./zkServer.sh start【zookeeper安装启动加载的配置文件为zoo.cfg】
        java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第24张图片
        • 不管是安装启动zookeeper集群,还是实际项目中使用,要学会看leader和follow之间数据同步、消息发送的日志
      • 查看ZooKeeper服务状态: ./zkServer.sh status
      • 停止ZooKeeper服务: ./zkServer.sh stop
      • 重启ZooKeeper服务: ./zkServer.sh restart
    • 客户端常用命令:
      java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第25张图片
//通过运行get命令验证数据是否与znode关联
[zkshell: 12] get /zk_test
my_data
cZxid = 5 //节点被创建的事务ID
ctime = Fri Jun 05 13:57:06 PDT 2009 //创建时间
mZxid = 5 //最后一次被更新的事务ID
mtime = Fri Jun 05 13:57:06 PDT 2009 //修改时间
pZxid = 5 //子节点列表最后一次被更新的事务ID
cversion = 0 //子节点的版本号
dataVersion = 0 //数据版本号
aclVersion = 0 //权限版本号
ephemeralOwner = 0 //用于临时节点,代表临时节点的事务ID,如果为持久节点则为0
dataLength = 7 //节点存储的数据的长度
numChildren = 0 //当前节点的子节点个数
  • 常见的ZooKeeper Java API :
    • 原生Java API,举个简单的例子,看看咋用Zookeeper
      psvm(){//形参中有watcher就说明你可以跟下面这个形式一样new Watcher(){写自己的回调逻辑就行},相当于把某个节点给watch住了
      Zookeeper zk = new Zookeeper("zookeeper集群的连接列表对应的字符串,用逗号隔开","session的超时时间,如3s",new Watcher(){
          //写watch的回调方法,依赖事件回调的,被回调的是这个process方法
          public void process(WatchedEvent event){
              //传回事件    
              Event.KeeperState state = event.getState();
              Event.KeeperType type = event.getType();    
              String path = event.getPath();
          
          switch(state){
              case 
              ...            
          }
          
          switch(type){
               case Node:
                   break;
               case NodeCreated:
                   break;
               case NodeDeleted:
                   break;
               case NodeDataChanged:
                   break;
               case NodeChildrenChanged:
                   break;                                                                                                                                    
          }                                                    
      }    
      });
      
      Zookeeper.States state = zk.getState();
      switch(state){
          case CONNECTING:
              ...
              break;
          ...
      }
      
      //有两种create方法重载,同步阻塞和异步模型
      zk.create(...);
      zk.getData(...);//有四个重载形式
      }
      
    • ZkClient
    • Curator:Curator是Apache Zookeeper的Java客户端库,Curator项目的目标是简化Zookeeper客户端的使用
      • Curator API常用操作:
        • 建立连接(虽然复杂,但是写一次就行了)
          java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第26张图片
          //1.第一种方式
          CuratorFrameworkFactory.newClient();
          //2.第二种方式
          CuratorFrameworkFactory.builder();
          
        • 添加节点:
          • //基本创建,如果创建节点没有指定数据,则默认将当前客户端的ip作为数据存储:String path = client.create(). forPath(“/app1”);
          • //创建节点带有数据,如果创建节点,没有指定数据,则默认将当前客户端的ip作为数据存储:String path = client.create(). forPath( path: “/app2” , “hehe” . getBytes());
          • //设置节点的类型,默认类型:持久化:String path = client . create() . withMode(CreateMode . EPHEMERAL) . forPath(“/app3”);
          • //创建多级节点 /app1/p1,creatingParentsIfNeeded() :如果父节点不存在,则创建父节点:String path = client .create() .creatingParentsIfNeeded() . forPath(“/app4/p1”);
        • 删除节点
          • // 1.删除单个节点:client . delete(). forPath( “/app1”);
          • //2.删除带有子节点的节点:client. delete() . deletingChildrenIfNeeded() . forPath( “/app4”);
          • //3.必须成功的删除:client.delete() .guaranteed(). forPath(“/app2”);
          • //回调
            java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第27张图片
            • callback回调利用reactive响应式编程,更充分的压榨OS、HW资源和性能。zookeeper因为有watch和callback才能存活于世,因为他确实跟redis很像。或者说要用zookeeper的目的,就是zookeeper的回调机制很牛,不需要无轮询【不管是redis还是DB,都需要周期性的看看数据变动了没】,zookeeper如果watch住他了,他的修改就一定会回调我的回调方法,那我是不是第一时间就知道我的东西被修改了。下面的异步代码:
              java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第28张图片
              • 节点的数据被改动,也就是zk.setData(…)时会触发事件,然后执行事件绑定的watcher
        • 修改节点
          • 修改数据:client. setData(). forPath( path:“ /app1" ," itcast ”. getBytes());
          • 根据版本号修改:
            Stat status = new Stat();
            client.getData().storingStatIn(status).forPath("/app1");
            int version = status.getVersion();//查询出来的Version
            client.setData().withVersion(version).forPath( path:"/app1" ,"haha".getBytes());
            
        • 查询节点
          • //查询数据: get:byte[] data = client. getData().forPath(“/app1”);
          • // 查询子节点:ls:List path = client. getChildren() . forPath(“/”);
          • //3.查询节点状态信息: ls -s:Stat status = new Stat();client. getData() .storingStatIn( status) .forPath(“/app1”);
        • Watcher事件监听:ZooKeeper允许用户在指定节点上注册一些Watcher, 并且在一些特定事件触发的时候, ZooKeeper服务端会将事件通知到感兴趣的客户端上去,该机制是ZooKeeper实现分布式协调服务的重要特性。
          java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第29张图片
          • 当没有zookeeper的watchs时,人家两个客户端(或者客户端和服务端)之间也可以用一个线程实现心跳验证,不停的问你好着没,你好着没…,不过这样毕竟麻烦
            • 如果用zookeeper的watch后,每个客户端连接到zookeeper时都会产生一个session来代表这个客户端, session就是在描述我这个用户是谁。然后依托这个session产生zookeeper的临时节点 ,如果我zookeeper在则session就一直在,如果我zookeeper中的节点挂掉了则session就会消失,此时就会产生一个事件event,会回调之前watch的那个节点目录
              • zookeeper的watch实现的比两个客户端之间(或者客户端与服务端)用线程实现的心跳验证时效性更强,比如心跳3秒钟,那么两个客户端之间(或者客户端与服务端)就会有3秒的时间间隔,我刚一跳你挂了,我只能等3秒之后才能继续跳。而人家zookeeper的watch,如果你挂了,人家session只用几毫秒,session没了就产生事件,然后回调
            • 目录上可以产生的事件event类型有:create、delete、change、children子节点事件
          • ZooKeeper中引入了Watcher机制来实现了发布/订阅功能能,能够让多个订阅者同时监听某一个对象, 当一个对象自身状态变化时,会通知所有订阅者
            java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第30张图片
            • ZooKeeper原生支持通过注册Watcher来进行事件监听,但是其使用并不是特别方便需要开发人员自己反复注册Watcher,比较繁琐。所以Curator引入了Cache来实现对ZooKeeper服务端事件的监听
            • 这也就是为什么Zookeeper相比于Redis而言,Zookeeper经常被用作注册中心的一个原因:
              • redis做注册中心服务器时间必需同步,否则出现时间不对被强制过期(删除key)!
              • zookeeper支持监听, redis不支持,因此如果用redis则需要客户端启动多个线程进行订阅监听,对服务器有一压力!
          • 四个特性:
            • 一次性:一旦一个Watcher触发之后,Zookeeper就会将它从存储中移除,如果还要继续监听这个节点,就需要我们在客户端的监听回调中,再次对节点的监听watch事件设置为True。否则客户端只能接收到一次该节点的变更通知
            • 客户端串行:客户端的Watcher回调处理是串行同步的过程,不要因为一个Watcher的逻辑阻塞整个客户端
            • 轻量:Wather通知的单位是WatchedEvent,只包含通知状态、事件类型和节点路径,不包含具体的事件内容,具体的时间内容需要客户端主动去重新获取数据
            • 异步: Zookeeper服务器发送watcher的通知事件到客户端是异步的,不能期望能够监控到节点每次的变化,Zookeeper只能保证最终的一致性,而无法保证强一致性。
          • ZooKeeper提供了三种Watcher:
            • NodeCache:只是监听某一个特定的指定的节点(给指定的一个节点注册监听器)
              java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第31张图片
            • PathChildrenCache :监控一个ZNode的子节点
              java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第32张图片
            • TreeCache:可以监控整个树上的所有节点,类似于PathChildrenCache和NodeCache的组合
              java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第33张图片
  • 上面提到了“ZooKeeper中引入了Watcher机制来实现了发布/订阅功能能,能够让多个订阅者同时监听某一个对象, 当一个对象自身状态变化时,会通知所有订阅者”。如果回归到咱们最原始的Web 实时消息推送,该怎么实现呢?【消息推送一般又分为 Web 端消息推送和移动端消息推送,思路就是只要触发某个事件(主动分享了资源或者后台主动推送消息)。】
    • 通常在服务端会有若干张消息推送表,用来记录用户触发不同事件所推送不同类型的消息,前端主动查询(拉)或者被动接收(推)用户所有未读的消息数。
    • 消息推送常见方案
      • 短轮询:指定的时间间隔,由浏览器向服务器发出 HTTP 请求,服务器实时返回未读消息数据给客户端,浏览器再做渲染显示
        java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第34张图片
      • 长轮询是对上边短轮询的一种改进版本,在尽可能减少对服务器资源浪费的同时,保证消息的相对实时性。长轮询在中间件中应用的很广泛,比如 Nacos 和 Apollo 配置中心,消息队列 Kafka、RocketMQ 中都有用到长轮询。
        • 长轮询其实原理跟轮询差不多,都是采用轮询的方式。不过,如果服务端的数据没有发生变更,会 一直 hold 住请求,直到服务端的数据发生变化,或者等待一定时间超时才会返回。返回后,客户端又会立即再次发起下一次长轮询
      • iframe 流就是在页面中插入一个隐藏的标签,通过在src中请求消息数量 API 接口,由此在服务端和客户端之间创建一条长连接,服务端持续向iframe传输数据。传输的数据通常是 HTML、或是内嵌的JavaScript 脚本,来达到实时更新页面的效果。
      • Websocket :一种实现消息推送的方式,是一种在 TCP 连接上进行全双工通信的协议,建立客户端和服务器之间的通信渠道。浏览器和服务器仅需一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输
        • SpringBoot 整合 Websocket,先引入 Websocket 相关的工具包,和 SSE 相比额外的开发成本。
          java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第35张图片
          • 服务端使用@ServerEndpoint注解标注当前类为一个 WebSocket 服务器,客户端可以通过ws://localhost:7777/webSocket/10086来连接到 WebSocket 服务器端
          • 前端初始化打开 WebSocket 连接,并监听连接状态,接收服务端数据或向服务端发送数据。
      • 服务端向客户端推送消息,其实除了可以用WebSocket这种耳熟能详的机制外,还有一种服务器发送事件(Server-Sent Events),简称 SSE。这是一种服务器端到客户端(浏览器)的单向消息推送。
        • SSE 基于 HTTP 协议的,我们知道一般意义上的 HTTP 协议是无法做到服务端主动向客户端推送消息的,但 SSE 是个例外,它变换了一种思路。
      • MQTT:
        • MQTT (Message Queue Telemetry Transport)是一种基于发布/订阅(publish/subscribe)模式的轻量级通讯协议,通过订阅相应的主题来获取消息,是物联网(Internet of Thing)中的一个标准传输协议。该协议将消息的发布者(publisher)与订阅者(subscriber)进行分离,因此可以在不可靠的网络环境中,为远程连接的设备提供可靠的消息服务,使用方式与传统的 MQ 有点类似
          java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第36张图片
        • TCP 协议位于传输层,MQTT 协议位于应用层,MQTT 协议构建于 TCP/IP 协议上,也就是说只要支持 TCP/IP 协议栈的地方,都可以使用 MQTT 协议
          java基础巩固-宇宙第一AiYWM:为了维持生计,中间件之Zookeeper,引入分布式系统架构之后会产生分布式环境一致性问题,怎么解决呢?不就靠分布式一致性问题的工业解决方案—开源框架 ZK~整起_第37张图片
    • 消息推送无非是推(push)和拉(pull)两种形式

巨人的肩膀:
moon聊技术
B站黑马视频
Zookeeper官方文档
javaGuide老师关于消息推送的文章,很赞
《从 Paxos 到 ZooKeeper 分布式一致性原理与实践》

你可能感兴趣的:(java,中间件,zookeeper)