zookeeper

This commit is contained in:
xiongraorao 2018-08-01 12:45:08 +08:00
parent 749455acd7
commit e90aa5acb3
10 changed files with 615 additions and 68 deletions

View File

@ -1,57 +1,59 @@
<!-- GFM-TOC -->
* [Java技术体系](#1-java-技术体系)
* [JVM](#jvm)
* [java自动内存管理机制](#2-java-自动内存管理机制)
* [jvm垃圾收集](#3-jvm-垃圾收集)
* [判断对象是否可回收](#判断对象是否可回收)
* [引用计数法](#1-引用计数法)
* [可达性分析算法](#2-可达性分析算法)
* [引用类型](#3-引用类型)
* [回收方法区](#4-回收方法区)
* [finalize](#5-finalize)
* [垃圾收集算法](#垃圾收集算法)
* [标记-清除](#1-标记-清除)
* [标记-整理](#2-标记-整理)
* [复制](#3-复制)
* [分代收集](#4-分代收集)
* [垃圾收集器](#垃圾收集器)
* [Serial 收集器](#1-serial-收集器)
* [ParNew 收集器](#2-parnew-收集器)
* [Parallel Scavenge 收集器](#3-parallel-scavenge-收集器)
* [Serial Old 收集器](#4-serial-old-收集器)
* [Parallel Old 收集器](#5-parallel-old-收集器)
* [CMS 收集器](#6-cms-收集器)
* [G1 收集器](#7-g1-收集器)
* [收集器比较](#8-收集器比较)
* [内存分配与回收策略](#内存分配与回收策略)
* [Minor GC 和 Full GC](#1-minor-gc-和-full-gc)
* [内存分配策略](#2-内存分配策略)
* [Full GC 的触发条件](#3-full-gc-的触发条件)
* [虚拟机性能监控工具](#虚拟机性能监控工具)
* [可视化工具 ](#可视化工具 )
* [类文件结构](#类文件结构)
* [虚拟机类加载机制](#虚拟机类加载机制)
* [虚拟机字节码执行引擎](#虚拟机字节码执行引擎)
* [运行时栈帧](#运行时栈帧)
* [方法调用](#方法调用)
* [解析](#解析)
* [分派](#分派)
* [静态分派](#静态分派)
* [动态分派](#动态分派)
* [虚拟机动态分派的实现](#虚拟机动态分派的实现)
* [Java 内存模型](#内存模型)
* [处理器、高速缓存、主内存](#处理器-高速缓存-主内存)
* [java内存模型](#java-内存模型)
* [主内存和工作内存](#主内存和工作内存)
* [内存间的交互操作](#内存间的交互操作)
* [内存模型的三大特性](#内存模型的三大特性)
* [先行发生原则](#先行发生原则)
* [java 与线程](#java-与线程)
* [线程的实现](#线程的实现)
* [java线程调度](#java线程调度)
* [状态转换](#状态转换)
* [java 线程安全和锁优化](#java-线程安全和锁优化)
<!-- GFM-TOC -->
<!-- TOC -->
- [1 Java 技术体系](#1-java-技术体系)
- [JVM](#jvm)
- [2 Java 自动内存管理机制](#2-java-自动内存管理机制)
- [3 jvm 垃圾收集](#3-jvm-垃圾收集)
- [判断对象是否可回收](#判断对象是否可回收)
- [1. 引用计数法](#1-引用计数法)
- [2. 可达性分析算法](#2-可达性分析算法)
- [3. 引用类型](#3-引用类型)
- [4. 回收方法区](#4-回收方法区)
- [5. finalize](#5-finalize)
- [垃圾收集算法](#垃圾收集算法)
- [1. 标记-清除](#1-标记-清除)
- [2. 标记-整理](#2-标记-整理)
- [3. 复制](#3-复制)
- [4. 分代收集](#4-分代收集)
- [垃圾收集器](#垃圾收集器)
- [1. Serial 收集器](#1-serial-收集器)
- [2. ParNew 收集器](#2-parnew-收集器)
- [3. Parallel Scavenge 收集器](#3-parallel-scavenge-收集器)
- [4. Serial Old 收集器](#4-serial-old-收集器)
- [5. Parallel Old 收集器](#5-parallel-old-收集器)
- [6. CMS 收集器](#6-cms-收集器)
- [7. G1 收集器](#7-g1-收集器)
- [8. 收集器比较](#8-收集器比较)
- [内存分配与回收策略](#内存分配与回收策略)
- [1. Minor GC 和 Full GC](#1-minor-gc-和-full-gc)
- [2. 内存分配策略](#2-内存分配策略)
- [3. Full GC 的触发条件](#3-full-gc-的触发条件)
- [虚拟机性能监控工具](#虚拟机性能监控工具)
- [可视化工具](#可视化工具)
- [类文件结构](#类文件结构)
- [虚拟机类加载机制](#虚拟机类加载机制)
- [虚拟机字节码执行引擎](#虚拟机字节码执行引擎)
- [运行时栈帧](#运行时栈帧)
- [方法调用](#方法调用)
- [解析](#解析)
- [分派](#分派)
- [静态分派](#静态分派)
- [动态分派](#动态分派)
- [虚拟机动态分派的实现](#虚拟机动态分派的实现)
- [内存模型](#内存模型)
- [处理器、高速缓存、主内存](#处理器高速缓存主内存)
- [java 内存模型](#java-内存模型)
- [主内存和工作内存](#主内存和工作内存)
- [内存间的交互操作](#内存间的交互操作)
- [内存模型的三大特性](#内存模型的三大特性)
- [先行发生原则](#先行发生原则)
- [java 与线程](#java-与线程)
- [线程的实现](#线程的实现)
- [java线程调度](#java线程调度)
- [状态转换](#状态转换)
- [java 线程安全和锁优化](#java-线程安全和锁优化)
<!-- /TOC -->
# 1 Java 技术体系

View File

@ -1,3 +1,35 @@
<!-- TOC -->
- [计算机网络基础](#计算机网络基础)
- [1 网络层次划分](#1-网络层次划分)
- [OSI七层模型](#osi七层模型)
- [各层的数据报格式](#各层的数据报格式)
- [1. 数据链路层](#1-数据链路层)
- [2. 网络层](#2-网络层)
- [3. 运输层格式](#3-运输层格式)
- [2 IP地址的分类](#2-ip地址的分类)
- [特殊的IP地址](#特殊的ip地址)
- [3 各种网络协议](#3-各种网络协议)
- [ARP/RARP协议](#arprarp协议)
- [路由选择协议](#路由选择协议)
- [DNS](#dns)
- [NAT协议](#nat协议)
- [DHCP协议](#dhcp协议)
- [4 TCP三次握手和四次挥手](#4-tcp三次握手和四次挥手)
- [TCP 报文格式](#tcp-报文格式)
- [TCP三次握手](#tcp三次握手)
- [SYN洪泛攻击](#syn洪泛攻击)
- [SYN 超时](#syn-超时)
- [TCP四次挥手](#tcp四次挥手)
- [5 TCP流量控制和拥塞控制](#5-tcp流量控制和拥塞控制)
- [流量控制](#流量控制)
- [拥塞控制](#拥塞控制)
- [流量控制和拥塞控制的区别](#流量控制和拥塞控制的区别)
- [5 浏览器请求的过程](#5-浏览器请求的过程)
- [6 TCP 和 UDP的区别](#6-tcp-和-udp的区别)
- [参考文献](#参考文献)
<!-- /TOC -->
# 计算机网络基础
# 1 网络层次划分

View File

@ -1,14 +1,16 @@
<!--TOC -->
<!-- TOC -->
* [Hadoop](#hadoop)
* [HDFS](#hdfs)
* [HDFS 文件读写](#hdfs-文件读写)
* [HDFS 数据存储](#hdfs-数据存储)
* [HDFS 可靠性保障](#hdfs-可靠性保障)
* [HDFS 优缺点](#hdfs-优缺点)
* [MapReduce](#mapreduce)
- [Hadoop](#hadoop)
- [HDFS](#hdfs)
- [HDFS 文件读写](#hdfs-文件读写)
- [HDFS 数据存储](#hdfs-数据存储)
- [HDFS 可靠性保障](#hdfs-可靠性保障)
- [Second NameNode](#second-namenode)
- [HDFS 优缺点](#hdfs-优缺点)
- [MapReduce](#mapreduce)
- [参考文档](#参考文档)
<!--TOC -->
<!-- /TOC -->
# Hadoop
Hadoop 是一个开源的分布式计算基础架构核心内容包含HDFS、MapReduce, hadoop2.0以后引入yarn.

View File

@ -1,10 +1,26 @@
<!--TOC -->
<!-- TOC -->
* [HBase 简介](#简介)
* [HBase 架构](#架构)
- [简介](#简介)
- [架构](#架构)
- [HBase 容错性](#hbase-容错性)
- [逻辑存储](#逻辑存储)
- [Row Key](#row-key)
- [列簇](#列簇)
- [时间戳](#时间戳)
- [Cell](#cell)
- [物理存储](#物理存储)
- [HBase Rowkey 设计](#hbase-rowkey-设计)
- [RowKey的长度原则](#rowkey的长度原则)
- [RowKey散列原则](#rowkey散列原则)
- [RowKey唯一原则](#rowkey唯一原则)
- [什么是热点](#什么是热点)
- [其他一些建议](#其他一些建议)
- [HBase 读写过程](#hbase-读写过程)
- [HBase 优化](#hbase-优化)
- [面试问题](#面试问题)
- [参考文档](#参考文档)
<!--TOC -->
<!-- /TOC -->
# 简介
@ -183,6 +199,69 @@ HLog文件就是一个普通的Hadoop Sequence FileSequence File 的Key是HLo
# HBase Rowkey 设计
HBase是三维有序存储的通过rowkey行键column keycolumn family和qualifier和TimeStamp时间戳这个三个维度可以对HBase中的数据进行快速定位。
HBase中rowkey可以唯一标识一行记录在HBase查询的时候有两种方式
- 通过get方式指定rowkey获取唯一一条记录
- 通过scan方式设置startRow和stopRow参数进行范围匹配全表扫描即直接扫描整张表中所有行记录
## RowKey的长度原则
rowkey是一个二进制码流可以是任意字符串最大长度 64kb 实际应用中一般为10-100bytes以 byte[] 形式保存,一般设计成定长。
建议越短越好不要超过16个字节原因如下
- 数据的持久化文件HFile中是按照KeyValue存储的如果rowkey过长比如超过100字节1000w行数据光rowkey就要占用100*1000w=10亿个字节将近1G数据这样会极大影响HFile的存储效率
- MemStore将缓存部分数据到内存如果rowkey字段过长内存的有效利用率就会降低系统不能缓存更多的数据这样会降低检索效率。
目前操作系统都是64位系统内存8字节对齐控制在16个字节8字节的整数倍利用了操作系统的最佳特性。
## RowKey散列原则
如果rowkey按照时间戳的方式递增不要将时间放在二进制码的前面建议将rowkey的高位作为散列字段由程序随机生成低位放时间字段这样将提高数据均衡分布在每个RegionServer以实现负载均衡的几率。如果没有散列字段首字段直接是时间信息所有的数据都会集中在一个RegionServer上这样在数据检索的时候负载会集中在个别的RegionServer上造成热点问题会降低查询效率。
## RowKey唯一原则
必须在设计上保证其唯一性rowkey是按照字典顺序排序存储的因此设计rowkey的时候要充分利用这个排序的特点将经常读取的数据存储到一块将最近可能会被访问的数据放到一块。
## 什么是热点
HBase中的行是按照rowkey的字典顺序排序的这种设计优化了scan操作可以将相关的行以及会被一起读取的行存取在临近位置便于scan。然而糟糕的rowkey设计是热点的源头。 热点发生在大量的client直接访问集群的一个或极少数个节点访问可能是读写或者其他操作。大量访问会使热点region所在的单个机器超出自身承受能力引起性能下降甚至region不可用这也会影响同一个RegionServer上的其他region由于主机无法服务其他region的请求。 设计良好的数据访问模式以使集群被充分,均衡的利用。
为了避免写热点设计rowkey使得不同行在同一个region但是在更多数据情况下数据应该被写入集群的多个region而不是一个。
下面是一些常见的避免热点的方法以及它们的优缺点:
- 加盐
这里所说的加盐不是密码学中的加盐而是在rowkey的前面增加随机数具体就是给rowkey分配一个随机前缀以使得它和之前的rowkey的开头不同。分配的前缀种类数量应该和你想使用数据分散到不同的region的数量一致。加盐之后的rowkey就会根据随机生成的前缀分散到各个region上以避免热点。
- 哈希
哈希会使同一行永远用一个前缀加盐。哈希也可以使负载分散到整个集群但是读却是可以预测的。使用确定的哈希可以让客户端重构完整的rowkey可以使用get操作准确获取某一个行数据
- 反转
第三种防止热点的方法时反转固定长度或者数字格式的rowkey。这样可以使得rowkey中经常改变的部分最没有意义的部分放在前面。这样可以有效的随机rowkey但是牺牲了rowkey的有序性。
反转rowkey的例子以手机号为rowkey可以将手机号反转后的字符串作为rowkey这样的就避免了以手机号那样比较固定开头导致热点问题
- 时间戳反转
一个常见的数据处理问题是快速获取数据的最近版本使用反转的时间戳作为rowkey的一部分对这个问题十分有用可以用 Long.Max_Value - timestamp 追加到key的末尾例如 [key][reverse_timestamp] , [key] 的最新值可以通过scan [key]获得[key]的第一条记录因为HBase中rowkey是有序的第一条记录是最后录入的数据。
比如需要保存一个用户的操作记录按照操作时间倒序排序在设计rowkey的时候可以这样设计
[userId反转][Long.Max_Value - timestamp]在查询用户的所有操作记录数据的时候直接指定反转后的userIdstartRow是[userId反转][000000000000],stopRow是[userId反转][Long.Max_Value - timestamp]
如果需要查询某段时间的操作记录startRow是[user反转][Long.Max_Value - 起始时间]stopRow是[userId反转][Long.Max_Value - 结束时间]
## 其他一些建议
尽量减少行和列的大小在HBase中value永远和它的key一起传输的。当具体的值在系统间传输时它的rowkey列名时间戳也会一起传输。如果你的rowkey和列名很大甚至可以和具体的值相比较那么你将会遇到一些有趣的问题。HBase storefiles中的索引有助于随机访问最终占据了HBase分配的大量内存因为具体的值和它的key很大。可以增加block大小使得storefiles索引再更大的时间间隔增加或者修改表的模式以减小rowkey和列名的大小。压缩也有助于更大的索引。
列族尽可能越短越好,最好是一个字符
冗长的属性名虽然可读性好但是更短的属性名存储在HBase中会更好
# HBase 读写过程
hbase使用MemStore和StoreFile存储对表的更新。
@ -337,6 +416,7 @@ Memstore中缓存的内容超过配置的范围后将会写到磁盘上例如
# 面试问题
# 参考文档
- [Hbase架构与原理](https://zhuanlan.zhihu.com/p/29674705)

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -1,3 +1,21 @@
<!-- TOC -->
- [Kafka](#kafka)
- [简介](#简介)
- [使用场景](#使用场景)
- [特性](#特性)
- [重要概念](#重要概念)
- [消息存储](#消息存储)
- [消息顺序](#消息顺序)
- [Guaranteens](#guaranteens)
- [Kafka as a Messaging System](#kafka-as-a-messaging-system)
- [消息队列](#消息队列)
- [发布/订阅](#发布订阅)
- [Kafka as a Storage System](#kafka-as-a-storage-system)
- [Kafka for Stream Processing](#kafka-for-stream-processing)
- [参考资料](#参考资料)
<!-- /TOC -->
# Kafka
Apacha [Kafka](http://kafka.apache.org/intro)是一个分布式流媒体平台主要功能有3个

View File

@ -0,0 +1,413 @@
<!-- TOC -->
- [简介](#简介)
- [设计目标](#设计目标)
- [分布式一致性](#分布式一致性)
- [核心概念](#核心概念)
- [角色](#角色)
- [数据模型](#数据模型)
- [Znode 属性](#znode-属性)
- [Znode 类型](#znode-类型)
- [Watch 机制](#watch-机制)
- [zookeeper 事件](#zookeeper-事件)
- [ACL](#acl)
- [ZAB](#zab)
- [安全属性](#安全属性)
- [安全保证](#安全保证)
- [工作阶段](#工作阶段)
- [ZAB 特性](#zab-特性)
- [zookeeper leader 选举](#zookeeper-leader-选举)
- [选举时机](#选举时机)
- [选举过程](#选举过程)
- [选举算法](#选举算法)
- [数据存储、同步和恢复](#数据存储同步和恢复)
- [应用场景](#应用场景)
- [分布式共享锁](#分布式共享锁)
- [数据发布与订阅](#数据发布与订阅)
- [命名服务](#命名服务)
- [分布式系统通知/协调](#分布式系统通知协调)
- [集群管理](#集群管理)
- [分布式队列](#分布式队列)
- [参考文档](#参考文档)
<!-- /TOC -->
# 简介
ZooKeeper是一个开源的分布式协调服务由雅虎创建是Google Chubby的开源实现。ZooKeeper的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来构成一个高效可靠的原语集并以一系列简单易用的接口提供给用户使用。
ZooKeeper是一个典型的分布式数据一致性的解决方案。分布式应用程序可以基于它实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、分布式锁和分布式队列等功能。ZooKeeper可以保证如下分布式一致性特性。
## 设计目标
- 简单
ZooKeeper允许分布式进程通过共享的层级命名空间相互协调该命名空间与标准文件系统类似地组织。名称空间由数据寄存器组成 - 在ZooKeeper用语中称为znodes - 这些与文件和目录类似。与专为存储而设计的典型文件系统不同ZooKeeper数据保存在内存中这意味着ZooKeeper可以实现高吞吐量和低延迟数量。
- 复制
与它协调的分布式进程一样ZooKeeper本身也可以在称为集合的一组主机上进行复制。
![](http://static.oschina.net/uploads/img/201308/08171345_l5K3.jpg)
组成ZooKeeper服务的服务器必须彼此了解。它们维护内存中的状态图像以及持久性存储中的事务日志和快照。只要大多数服务器可用ZooKeeper服务就可用。
- 有序
ZooKeeper使用反映所有ZooKeeper事务顺序的数字标记每个更新。后续操作可以使用该顺序来实现更高级别的抽象例如同步原语。
- 快速
它在“读取主导”工作负载中特别快。ZooKeeper应用程序在数千台计算机上运行并且在取比写入更常见的情况下表现最佳比率大约为101。
## 分布式一致性
- 顺序一致性
从同一个客户端发起的事务请求最终将会严格按照其发起顺序被应用到ZooKeeper中。
- 原子性
所有事务请求的结果在集群中所有机器上的应用情况是一致的,也就是说要么整个集群所有集群都成功应用了某一个事务,要么都没有应用,一定不会出现集群中部分机器应用了该事务,而另外一部分没有应用的情况。
- 单一视图(最终一致性)
无论客户端连接的是哪个ZooKeeper服务器其看到的服务端数据模型都是一致的。
- 可靠性
一旦服务端成功地应用了一个事务,并完成对客户端的响应,那么该事务所引起的服务端状态变更将会被一直保留下来,除非有另一个事务又对其进行了变更。
- 实时性
通常人们看到实时性的第一反应是一旦一个事务被成功应用那么客户端能够立即从服务端上读取到这个事务变更后的最新数据状态。这里需要注意的是ZooKeeper仅仅保证在一定的时间段内客户端最终一定能够从服务端上读取到最新的数据状态。
- 等待无关wait-free
慢的或者失效的client不得干预快速的client的请求使得每个client都能有效的等待。
# 核心概念
## 角色
Zookeeper的角色主要有3种LeaderFollowerObserver三种如下图所示
![](http://static.oschina.net/uploads/img/201308/08171344_cqXs.jpg)
一个ZooKeeper集群同一时刻只会有一个Leader其他都是Follower或Observer。
ZooKeeper默认只有Leader和Follower两种角色没有Observer角色。
为了使用Observer模式在任何想变成Observer的节点的配置文件中加入
`peerType=observer`
并在所有server的配置文件中配置成observer模式的server的那行配置追加:observer例如
`server.1:localhost:2888:3888:observer`
ZooKeeper集群的所有机器通过一个Leader选举过程来选定一台被称为『Leader』的机器Leader服务器为客户端提供读和写服务。
Follower和Observer都能提供读服务不能提供写服务。两者唯一的区别在于Observer机器不参与Leader选举过程也不参与写操作的『过半写成功』策略因此Observer可以在不影响写性能的情况下提升集群的读性能。
# 数据模型
Zookeeper的结构类似标准的文件系统但这个文件系统中没有文件和目录而是统一使用节点(node)的概念称为znode。Znode作为保存数据的容器(限制在1mb以内),也构成了一个层次化的命名空间。
![](http://img.my.csdn.net/uploads/201303/21/1363878481_5653.jpg)
zookeeper目录中的每一个节点对应着一个znode每个znode维护着一个属性结构它包含数据的版本号、时间戳、等信息。Zookeeper就是通过这些属性来实现它特定的功能。每当znode的数据改变时相应的版本号会增加每当客户端查询、更新和删除数据时也必须提供要被操作的znode版本号如果所提供的数据版本号与实际的不匹配那么将会操作失败。
## Znode 属性
属性 | 描述
--- | ---
czxid |节点被创建的zxid值
mzxid | 节点被修改时zxid值
ctime | 节点创建的时间
mtime | 节点最后一次的修改时间
vesion | 节点的版本号
cversion | 节点所拥有的子节点被修改的版本号
aversion | 节点的ACL被修改的版本号
dataLength | 节点数据的长度
numChildren | 节点拥有子节点的个数
ephemeralOwner | 如果节点为临时节点那么它的值为这个节点拥有者的session ID负责它的值为0
## Znode 类型
- 持久节点(PERSISTENT)
所谓持久节点,是指在节点创建后,就一直存在,直到有删除操作来主动清除这个节点——不会因为创建该节点的客户端会话失效而消失。
- 持久顺序节点(PERSISTENT_SEQUENTIAL)
在ZK中每个父节点会为他的第一级子节点维护一份时序会记录每个子节点创建的先后顺序。基于这个特性在创建子节点的时候注意在此节点下的子节点是有顺序的可以设置这个属性那么在创建节点过程中ZK会自动为给定节点名加上一个数字后缀作为新的节点名。这个数字后缀的上限是整型的最大值。
- 临时节点EPHEMERAL
和持久节点不同的是临时节点的生命周期和客户端会话绑定。也就是说如果客户端会话失效那么这个节点就会自动被清除掉。注意这里提到的是会话失效而非连接断开。另外在临时节点下面不能创建子节点注意是更具Session会话的失效时间来设定的。
- 临时顺序节点EPHEMERAL_SEQUENTIAL
临时顺序节点的特性和临时节点一致,同时是在临时节点的基础上,添加了顺序的特性。
## Watch 机制
zookeeper 的 watch 机制目的是为ZK客户端操作提供一种类似于异步获得数据的操作。zk提供了分布式数据的发布订阅功能一个典型的发布订阅系统定义了一种一对多的订阅关系能够让多个订阅者同时监听某一个主题对象当这个主题对象自身状态变化时会通知所有订阅者使它们能够做出相应的处理。zk引入了watcher机制来实现这种分布式的通知功能。
![](img/20180202154637790.png)
watch 机制的特点:
- 一次性触发
客户端在Znode设置了Watch时如果Znode内容发生改变那么客户端就会获得Watch事件。例如客户端设置getData("/znode1", true)后,如果/znode1发生改变或者删除那么客户端就会得到一个/znode1的Watch事件但是/znode1再次发生变化那客户端是无法收到Watch事件的除非客户端设置了新的Watch。
- 异步发送
Watch事件是异步发送到Client。Zookeeper可以保证客户端发送过去的更新顺序是有序的。例如某个Znode没有设置watcher那么客户端对这个Znode设置Watcher发送到集群之前该客户端是感知不到该Znode任何的改变情况的。换个角度来解释由于Watch有一次性触发的特点所以在服务器端没有Watcher的情况下Znode的任何变更就不会通知到客户端。不过即使某个Znode设置了Watcher且在Znode有变化的情况下通知到了客户端但是在客户端接收到这个变化事件但是还没有再次设置Watcher之前如果其他客户端对该Znode做了修改这种情况下Znode第二次的变化客户端是无法收到通知的。这可能是由于网络延迟或者是其他因素导致所以我们使用Zookeeper不能期望能够监控到节点每次的变化。Zookeeper只能保证最终的一致性而无法保证强一致性。
- 数据监视
Zookeeper有数据监视和子数据监视。例如节点创建节点删除节点改变子节点改变等等。Zookeeper维护了两个Watch列表一个节点数据Watch列表另一个是子节点Watch列表。getData()和exists()设置数据WatchgetChildren()设置子节点Watch。两者选其一可以让我们根据不同的返回结果选择不同的Watch方式getData()和exists()返回节点的内容getChildren()返回子节点列表。因此setData()触发内容Watchcreate()触发当前节点的内容Watch或者是其父节点的子节点Watch。delete()同时触发父节点的子节点Watch和内容Watch以及子节点的内容Watch。
Watch 的运行机制:
![](img/20151130144136090.png)
1Watch是轻量级的其实就是本地JVM的Callback服务器端只是存了是否有设置了Watcher的布尔类型。源码见org.apache.zookeeper.server.FinalRequestProcessor
2在服务端在FinalRequestProcessor处理对应的Znode操作时会根据客户端传递的watcher变量添加到对应的ZKDatabaseorg.apache.zookeeper.server.ZKDatabase中进行持久化存储同时将自己NIOServerCnxn做为一个Watcher callback监听服务端事件变化
3Leader通过投票通过了某次Znode变化的请求后然后通知对应的FollowerFollower根据自己内存中的zkDataBase信息发送notification信息给zookeeper客户端。
4Zookeeper客户端接收到notification信息后找到对应变化path的watcher列表挨个进行触发回调。
## zookeeper 事件
Zookeeper状态/事件对应关系图:
![](img/20151130152159470.png)
watch 事件:
事件 | 说明
--- | ---
None | 在客户端与Zookeeper集群中的服务器断开连接的时候客户端会收到这个事件。
NodeCreated | Znode创建事件
NodeDeleted | Znode删除事件
NodeDataChanged | Znode数据内容更新事件。其实本质上该事件只关注dataVersion版本号但是只要调用了更新接口dataVersion就会有变更。
NodeChildrenChanged | Znode子节点改变事件只关注子节点的个数变更子节点内容有变更是不会通知的。
事件发生时zookeeper的状态
状态 | 说明
--- | ---
Disconnected(0) |客户端处于断开连接的状态并且没有和Zookeeper集群中任何服务器连接。
SyncConnected(3) | 客户端处于连接的状态也就是说客户端连接到了一台server
AuthFailed(4) | 验证失败的状态
ConnectedReadOnly(5) |客户端连接到一个只读Server的状态。
SaslAuthenticated(6) |用于通知客户端他们是SASL认证以至于他们能够SASL认证的权限通过操作Zookeeper。
Expired(-112) | 会话超时状态
## ACL
ZooKeeper采用ACLAccess Control Lists策略来进行权限控制。ZooKeeper定义了如下5种权限。
CREATE: 创建子节点的权限。
READ: 获取节点数据和子节点列表的权限。
WRITE更新节点数据的权限。
DELETE: 删除子节点的权限。
ADMIN: 设置节点ACL的权限。
注意CREATE 和 DELETE 都是针对子节点的权限控制。
## ZAB
Zookeeper使用了一种称为ZabZookeeper Atomic Broadcast的协议作为其一致性复制的核心。ZooKeeper是Chubby的开源实现而Chubby是Paxos的工程实现所以很多人以为ZooKeeper也是Paxos算法的工程实现。事实上ZooKeeper并没有完全采用Paxos算法而是使用了一种称为ZooKeeper Atomic BroadcastZABZooKeeper原子广播协议的协议作为其数据一致性的核心算法。
ZAB协议并不像Paxos算法和Raft协议一样是通用的分布式一致性算法它是一种特别为ZooKeeper设计的崩溃可恢复的原子广播算法。
基于ZAB协议ZooKeeper实现了一种主备模式Leader、Follower的系统架构来保持集群中各副本之间数据的一致性。
ZAB 协议有两个工作模式:**broadcast模式**、**recovery 模式**
在整个ZooKeeper集群启动过程中或是当Leader服务器出现网络中断、崩溃退出与重启等异常情况时ZAB协议就会进入恢复模式并选举产生新的Leader服务器。当选举产生了新的Leader服务器同时集群中有过半的机器与该Leader服务器完成了状态同步之后ZAB协议就会退出恢复模式。其中状态同步是指数据同步用来保证集群中存在过半的机器能够和Leader服务器的数据状态保持一致。
崩溃恢复模式包括两个阶段Leader选举和数据同步。
当集群中有过半的Follower服务器完成了和Leader服务器的状态同步那么整个集群就可以进入消息广播模式了。
### 安全属性
考虑到Zookeeper主要操作数据的状态为了保证状态的一致性Zookeeper提出了两个安全属性Safety Property
- 全序Total order如果消息a在消息b之前发送则所有Server应该看到相同的结果
- 因果顺序Causal order如果消息a在消息b之前发生a导致了b并被一起发送则a始终在b之前被执行
### 安全保证
为了保证上述两个安全属性Zookeeper使用了TCP协议和Leader。
- 通过使用TCP协议保证了消息的全序特性先发先到
- 通过Leader解决了因果顺序问题先到Leader的先执行。
因为有了LeaderZookeeper的架构就变为Master-Slave模式但在该模式中MasterLeader会Crash因此Zookeeper引入了Leader选举算法以保证系统的健壮性。
### 工作阶段
- Atomic Broadcast
同一时刻存在一个Leader节点其他节点称为“Follower”如果是更新请求如果客户端连接到Leader节点则由Leader节点执行其请求如果连接到Follower节点则需转发请求到Leader节点执行。但对读请求Client可以直接从Follower上读取数据如果需要读到最新数据则需要从Leader节点进行Zookeeper设计的读写比例是21。
Leader通过一个简化版的二段提交模式向其他Follower发送请求但与二段提交有两个明显的不同之处
因为只有一个LeaderLeader提交到Follower的请求一定会被接受没有其他Leader干扰
不需要所有的Follower都响应成功只要一个多数派即可
通俗地说如果有2f+1个节点允许f个节点失败。因为任何两个多数派必有一个交集当Leader切换时通过这些交集节点可以获得当前系统的最新状态。如果没有一个多数派存在存活节点数小于f+1算法过程结束。但有一个特例
如果有A、B、C三个节点A是Leader如果B Crash则A、C能正常工作因为A是LeaderA、C还构成多数派如果A Crash则无法继续工作因为Leader选举的多数派无法构成。
- Leader选举
Leader选举主要是依赖Paxos算法具体算法过程请参考其他博文这里仅考虑Leader选举带来的一些问题。Leader选举遇到的最大问题是”新老交互“的问题新Leader是否要继续老Leader的状态。这里要按老Leader Crash的时机点分几种情况
老Leader在COMMIT前Crash已经提交到本地
老Leader在COMMIT后Crash但有部分Follower接收到了Commit请求
第一种情况这些数据只有老Leader自己知道当老Leader重启后需要与新Leader同步并把这些数据从本地删除以维持状态一致。
第二种情况新Leader应该能通过一个多数派获得老Leader提交的最新数据
老Leader重启后可能还会认为自己是Leader可能会继续发送未完成的请求从而因为两个Leader同时存在导致算法过程失败解决办法是把Leader信息加入每条消息的id中Zookeeper中称为zxidzxid为一64位数字高32位为leader信息又称为epoch每次leader转换时递增低32位为消息编号Leader转换时应该从0重新开始编号。通过zxidFollower能很容易发现请求是否来自老Leader从而拒绝老Leader的请求。
因为在老Leader中存在着数据删除情况1因此Zookeeper的数据存储要支持补偿操作这也就需要像数据库一样记录log。
### ZAB 特性
ZooKeeper中提交事务的协议并不是Paxos而是由二阶段提交协议改编的ZAB协议。Zab可以满足以下特性
- 可靠提交 Reliable delivery如果消息m被一个server递交了那么m也将最终被所有server递交。
- 全局有序 Total order如果server在递交b之前递交了a那么所有递交了a、b的server也会在递交b之前递交a。
- 因果有序 Casual order对于两个递交了的消息a、b如果a因果关系优先于(causally precedes)b那么a将在b之前递交。 
第三条的因果优先指的是同一个发送者发送的两个消息a先于b发送或者上一个leader发送的消息a先于当前leader发送的消息。
## zookeeper leader 选举
### 选举时机
1、服务器初始化启动
2、服务器运行期间无法和Leader保持连接
### 选举过程
1、初始化启动时候的选举
若进行Leader选举则至少需要两台机器这里选取3台机器组成的服务器集群为例。在集群初始化阶段当有一台服务器Server1启动时其单独无法进行和完成Leader选举当第二台服务器Server2启动时此时两台机器可以相互通信每台机器都试图找到Leader于是进入Leader选举过程。选举过程如下
  (1) 每个Server发出一个投票。由于是初始情况Server1和Server2都会将自己作为Leader服务器来进行投票每次投票会包含所推举的服务器的myid和ZXID使用(myid, ZXID)来表示此时Server1的投票为(1, 0)Server2的投票为(2, 0),然后各自将这个投票发给集群中其他机器。
  (2) 接受来自各个服务器的投票。集群的每个服务器收到投票后首先判断该投票的有效性如检查是否是本轮投票、是否来自LOOKING状态的服务器。
  (3) 处理投票。针对每一个投票服务器都需要将别人的投票和自己的投票进行PKPK规则如下
    · 优先检查ZXID。ZXID比较大的服务器优先作为Leader。
    · 如果ZXID相同那么就比较myid。myid较大的服务器作为Leader服务器。
  对于Server1而言它的投票是(1, 0)接收Server2的投票为(2, 0)首先会比较两者的ZXID均为0再比较myid此时Server2的myid最大于是更新自己的投票为(2, 0)然后重新投票对于Server2而言其无须更新自己的投票只是再次向集群中所有机器发出上一次投票信息即可。
  (4) 统计投票。每次投票后服务器都会统计投票信息判断是否已经有过半机器接受到相同的投票信息对于Server1、Server2而言都统计出集群中已经有两台机器接受了(2, 0)的投票信息此时便认为已经选出了Leader。
  (5) 改变服务器状态。一旦确定了Leader每个服务器就会更新自己的状态如果是Follower那么就变更为FOLLOWING如果是Leader就变更为LEADING。
2、 服务器运行时期的Leader选举
在Zookeeper运行期间Leader与非Leader服务器各司其职即便当有非Leader服务器宕机或新加入此时也不会影响Leader但是一旦Leader服务器挂了那么整个集群将暂停对外服务进入新一轮Leader选举其过程和启动时期的Leader选举过程基本一致。假设正在运行的有Server1、Server2、Server3三台服务器当前Leader是Server2若某一时刻Leader挂了此时便开始Leader选举。选举过程如下
  (1) 变更状态。Leader挂后余下的非Observer服务器都会讲自己的服务器状态变更为LOOKING然后开始进入Leader选举过程。
  (2) 每个Server会发出一个投票。在运行期间每个服务器上的ZXID可能不同此时假定Server1的ZXID为123Server3的ZXID为122在第一轮投票中Server1和Server3都会投自己产生投票(1, 123)(3, 122),然后各自将投票发送给集群中所有机器。
  (3) 接收来自各个服务器的投票。与启动时过程相同。
  (4) 处理投票。与启动时过程相同此时Server1将会成为Leader。
  (5) 统计投票。与启动时过程相同。
  (6) 改变服务器的状态。与启动时过程相同。
### 选举算法
一种是基于basic paxos实现的另外一种是基于fast paxos算法实现的。系统默认的选举算法为fast paxos。
1. basic paxos
选举线程由当前Server发起选举的线程担任其主要功能是对投票结果进行统计并选出推荐的Server
选举线程首先向所有Server发起一次询问(包括自己)
选举线程收到回复后,验证是否是自己发起的询问(验证zxid是否一致)然后获取对方的id(myid)并存储到当前询问对象列表中最后获取对方提议的leader相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中;
收到所有Server回复以后就计算出zxid最大的那个Server并将这个Server相关信息设置成下一次要投票的Server
线程将当前zxid最大的Server设置为当前Server要推荐的Leader如果此时获胜的Server获得n/2 + 1的Server票数 设置当前推荐的leader为获胜的Server将根据获胜的Server相关信息设置自己的状态否则继续这个过程直到leader被选举出来。通 过流程分析我们可以得出要使Leader获得多数Server的支持则Server总数必须是奇数2n+1且存活的Server的数目不得少于 n+1.每个Server启动后都会重复以上流程。在恢复模式下如果是刚从崩溃状态恢复的或者刚启动的server还会从磁盘快照中恢复数据和会话信 息zk会记录事务日志并定期进行快照方便在恢复时进行状态恢复。
2. fast paxos
![](https://upload-images.jianshu.io/upload_images/6160085-de22099cbdc4c525?imageMogr2/auto-orient/strip%7CimageView2/2/w/678)
在选举过程中某Server首先向所有Server提议自己要成为leader当其它Server收到提议以后解决epoch和 zxid的冲突并接受对方的提议然后向对方发送接受提议完成的消息重复这个流程最后一定能选举出Leader。
选举的具体流程图如下所示:
![](https://upload-images.jianshu.io/upload_images/1292466-e096f571dc2d90b8.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/533)
# 数据存储、同步和恢复
[【分布式】Zookeeper数据与存储](http://www.cnblogs.com/leesf456/p/6179118.html)
# 应用场景
## 分布式共享锁
在Zookeeper上创建一个锁结点然后每个服务器如果需要访问共享资源那么就在锁结点下创建一个锁结点的孩子结点这里需要注意的是孩子结点需要创建成为临时序列结点这样一来如果某个服务器在拥有锁的时候挂了其拥有的锁会自动释放。序列结点会使所有的锁都可以有序。在获取锁的时候创建监听该锁上一个锁的删除事件这样可以避免“羊群效应”在一个锁结点被释放删除时不会唤醒所有在等待的锁结点可以节约网络和服务资源。
![](https://images2015.cnblogs.com/blog/735119/201611/735119-20161122145815550-658939145.png)
这只是一个基本的实现思路,具体的规则就是序列最小的获取锁,优先级是按照时间来算的,但是基本思路都是一样的,可以基于此上修改成为优先级可调节的,或者改成分布式读写锁提高访问性能。
 分布式共享锁的实现请参考https://github.com/wxisme/zoopack/blob/master/zoopack/src/main/java/org/zoopack/lock/
## 数据发布与订阅
发布与订阅即所谓的配置管理顾名思义就是将数据发布到zk节点上供订阅者动态获取数据实现配置信息的集中式管理和动态更新。例如全局的配置信息、地址列表等。
具体用法:
- 索引信息和集群中机器节点状态放在zk的一些指定节点供各个客户端订阅使用。
- 系统日志经处理后存储这些日志通常2-3天后清除。
- 应用中用到的一些配置信息集中管理在应用启动的时候主动来获取一次并在节点上注册一个Watcher以后每次配置有更新实时通知到应用获取最新的配置信息。
- 业务逻辑中需要用到的一些全局变量比如一些消息中间件的消息队列通常有个offset这个offset存放在zk上这样集群中每个发送者都能知道当前的发送进度。
- 系统中有些信息需要动态获取并且还会存在人工手动去修改这个信息。以前通常是暴露出接口例如JMX接口有了zk后只要将这些信息存放到zk节点上即可。
## 命名服务
这个主要是作为分布式命名服务通过调用zk的create node api能够很容易创建一个全局唯一的path可以将这个path作为一个名称。
## 分布式系统通知/协调
ZooKeeper中特有的watcher注册于异步通知机制能够很好的实现分布式环境下不同系统之间的通知与协调实现对数据变更的实时处理。使用方法通常是不同系统都对zk上同一个znode进行注册监听znode的变化包括znode本身内容及子节点内容其中一个系统update了znode那么另一个系统能够收到通知并做出相应处理。
使用ZooKeeper来进行分布式通知和协调能够大大降低系统之间的耦合。
具体用法:
- 心跳检测机制检测系统和被测系统之间并不直接关联起来而是通过zk上某个节点关联大大减少系统耦合。
- 系统调度模式某系统有控制台和推送系统两部分组成控制台的职责是控制推送系统进行相应的推送工作。管理人员在控制台做的一些操作实际上是修改了zk上某些节点的状态而zk就把这些变化通知给它们注册watcher的客户端即推送系统于是做出相应的推送任务。
- 工作汇报模式一些类似于任务分发系统子任务启动后到zk来注册一个临时节点并定时将自己的进度进行汇报将进度写回这个临时节点这样任务管理者就能够实时指导任务进度。
## 集群管理
- 集群机器监控:
这通常用于那种对集群中机器状态、机器在线率有较高要求的场景能够快速对集群中机器变化做出响应。这样的场景中往往有一个监控系统实时监测集群机器是否存活。过去的做法通常是监控系统通过某种手段比如ping定时检测每个机器、或每个机器定时向监控系统发送心跳信息。这种做法存在两个弊端1.集群中机器有变动的时候牵连修改的东西比较多。2.有一定的延迟。利用ZooKeeper可以实现另一种集群机器存活性监控系统a.客户端在节点x上注册watcher如果x的子节点发生变化会通知该客户端。b.创建EPHEMERAL类型的节点一旦客户端和服务器的会话结束或过期该节点就会消失。例如监控系统在/clusterServers节点上注册一个watcher以后每动态加机器就往/culsterServer下创建一个EPHEMERAL类型的节点/clusterServer/{hostname}。这样,监控系统就能实时知道机器的增减情况,至于后续处理就是监控系统的业务了。
- Master选举
在分布式环境中相同的业务应用分布在不同的机器上有些业务逻辑例如一些耗时的计算、网络I/O处理往往需要让整个集群中的某一台机器进行执行其余机器可以共享这个结果这样可以减少重复劳动、提高性能。利用ZooKeeper的强一致性能够保证在分布式高并发情况下节点创建的全局唯一性同时有多个客户端请求创建/currentMaster节点最终一定只有一个客户端请求能够创建成功。利用这个特性就能很轻易的在分布式环境中进行集群选取了。另外这种场景演化一下就是动态Master选举。这就要用到 EPHEMERAL_SEQUENTIAL类型节点的特性了。上文中提到所有客户端创建请求最终只有一个能够创建成功。在这里稍微变化下就是允许所有请求都能够创建成功但是得有个创建顺序于是所有的请求最终在zk上创建结果的一种可能情况是这样 /currentMaster/{sessionId}-1、/currentMaster/{sessionId}-2、/currentMaster/{sessionId}-3……。每次选取序列号最小的那个机器作为Master如果这个机器挂了由于他创建的节点会马上消失那么之后最小的那个机器就是Master了。
## 分布式队列
队列方面,有两种方式:一种是常规的先进先出队列,另一种是要等到队列成员聚齐之后的才统一按序执行。
对于先进先出队列,和分布式锁服务中的控制时序场景基本原理一致,这里不再赘述。
第二种队列其实是在FIFO队列的基础上作了一个增强。通常可以在/queue这个znode下预先建立一个/queue/num节点并且赋值为n或者直接给/queue赋值n表示队列大小之后每次有队列成员加入后就判断下是否已经到达队列大小决定是否可以开始执行了。这种用法的典型场景是分布式环境中一个大任务Task A需要在很多子任务完成或条件就绪情况下才能进行。这个时候凡是其中一个子任务完成就绪那么就去/taskList下建立自己的临时时序节点CreateMode.EPHEMERAL_SEQUENTIAL当/taskList发现自己下面的子节点满足指定个数就可以进行下一步按序进行处理了。
# 参考文档
- [zookeeper 官方文档](https://zookeeper.apache.org/doc/current/zookeeperOver.html)
- [Zookeeper Watch机制](https://blog.csdn.net/qianshangding0708/article/details/50084155)
- [ZooKeeper介绍及典型使用场景](https://blog.csdn.net/liuxinghao/article/details/42747625)
- [ZooKeeper原理与应用](https://www.jianshu.com/p/84ad63127cd1)
- [【分布式】Zookeeper的Leader选举](http://www.cnblogs.com/leesf456/p/6107600.html)
- [Zab协议](https://www.cnblogs.com/sunddenly/articles/4073157.html)