上来就送干货 本书的练习以及代码可以再O’Reilly官网网站本书页面下载。
相关链接
http://www.oreilly.com.cn
http://www.oreilly.com
1、区分,单机多线程和分布式多线程的区别
在典型的不共享环境下不同的计算机之间不共享除网络之外的其他任何信息,言外之意,就是它们之间能够沟通的方式只有网络。
2、zookeeper在分布式环境下的作用:
利用消息传递算法实现同步原语,zookeeper提供某种有序共享存储的组件。
Apache kafka 利用zookeeper检测崩溃,实现主题的发现,并发痴主题的生产和消费
Apache Solr 是一起企业级的搜索平台。使用ZooKeeper来存储集群的元数据,并协作更新这些元数据。
Yahoo!Fetching Service 是爬虫实现的一部分,通过缓存内容的方法高效地获取网页信息,同时确保满足网页服务器的管理规则。该服务器采用zookeeper实现主节点的选举、奔溃检测和元数据的存储。
zookeeper的客户端API的强大:
1、保障强一致性、有序性和持久性。
2、实现通过的同步原语能力
3、在实际的分布式系统中,并发往往导致不正确的行为。ZooKeeper提供了一种简单的并发处理机制
对于一致性和持久性的不同需求,最佳时间还是应该讲应用数据和协同数据独立开。
1.1.2 ZooKeeper不适用的场景,ZooKeeper不适合用作海量数据存储,取而代之的可以采用数据库或者分布式文件系统等,
分布式系统中的进程通讯有两种选择:直接通过网络进行信息交换,或读取某些共享的存储。ZooKeeper使用的共享存储模型来使用应用间协作和同步原语。对于共享存储本身,有需要在进程和存储间进行网络通讯。网络是分布式系统中并行设计的基础。
备份主节点却认为主节点已经奔溃,例如节点负载很高,导致消息延迟,备份主节点将接管成功主节点的角色,执行所有必要的程序,如果一些从节点无法与主要主节点的通信,如由于网络分区(network partition)错误导致。针对这个场景中导致的问题,我们一般称之为脑裂(split-brain):系统中两个获得多个部分开始独立工作,导致整体行为不一致性。
1.2.3 通信故障
如果一个从节点与主节点的网络连接断开,比如网络分区(network partition)导致,重新分配一个任务,可能会导致两个从节点执行相同的任务。
元数据:主节点和从节点必须具有通过某种可靠的方式来保存分配状态和执行状态的能力
1.3分布式协作的难点
当开发分布式应用时,其复杂性会立即凸显出来,例如,当我们的应用启动后,所有不同的进程通过某种方式,需要知道应用的配置信息,一段时间之后,配置信息也许发生了很大的变化,我们可以停止所有的进程,重新分发配置信息的文件,然后重新启动,但是重新匹配就会延长应用的停机时间。
FLP(Fischer Lynch Patternson)这个结论证明了在异步通信的分布式系统中,进程奔溃,所有的进程可能无法在这个比特位的配置上达成一致。类似定律称为CAP 表示一致性(Consistency),可用性(Avaliability)和分区容错性(Partition-tolerance),该定力指出,当设计一个分布式系统时,我们希望这种属性全部满足,但没有系统可以同时满足这三种属性。因此ZooKeeper的设计尽可能满足一致性和可用性,当然,发生网络分区时也提高了只读能力。
我们无法拥有一个理想的故障容错、分布式的、真是的环境存在的系统来处理所发生的所有的问题,
适当放款目标:假设时钟在某种范围内是同步的,大妈也可以牺牲一些网络分区容错的能力,并认为其一直是一致的。
1.4 ZooKeeper的成功和注意事项
Paxos算法和虚拟同步技术Virtual Synchrony,通过这些技术可以无缝的处理所发生的某些变化或情况,并提供一个开发者一框架。
2.1 zookeeper基础
提供元语列表,暴露出每个原语的实例化调用方法,并直接控制这些实例。
比如:分布式锁机制组成了一个重要的原语,同时暴露创建create 获取acquire 和释放release三个调用方法
这种设计存在一个重大的缺陷:首先,我们要么预先提出一份详尽的原语的列表,要么提供API的扩展,以便引入新的原语;其次以这种方式实现元语的服务是的应用上市了灵活性。
因此在zookeeper中,我们另辟蹊径。zookeeper并不直接暴露原语,取而代之,它暴露了由一小部分调用方法组成的类似文件系统的API,以便允许应用实现自己的原语。我们通常使用菜谱(recipes)来表示这些原语。菜谱包括zookeeper操作和维护的一个小型的数据节点,这些数据节点被称为znode,采用类似于文件系统的层级树状结构进行管理。
2.1.1 API概述
znode 节点可能还有数据,也可能没有,如果一个znode节点包含任何数据,那么数据存储为字节数组byte array。ZooKeeper并不直接提供解析支持。我们可以使用Protocol buffers , Thrift、Avro或者MessagePack等序列化(Serialization)包来方便的处理保存于znode节点的数据格式。不过有时候UTF-8和ASC2编码的字符串就够用了。
zookeeper的API暴露了以下的方法:
carete /path data
创建一个名为 /path 的node 节点,包含数据data。
delete /path
删除名为 /path的znode
exists /path
检查是否存在名为 /path的节点
setData /path data
设置名为/path 的znode的数据为data
getData /path
返回名为/path节点的数据信息
getChildren /path
返回所有 /path节点的所有子节点的列表。
zookeeper客户端连接到zookeeper服务,通过API调用来建立会话session。
2.1.2 znode的不同类型
持久检点和临时节点
znode节点可以是持久(persistent)节点,还可以是临时(ephemeral)节点。持久的znode,如/path,只能通过调用delete来进行删除。临时的znode与之相反,当穿件该节点的客户端崩溃的时或者关闭与zookeeper的链接时,这个节点就会被删除。
持久znode是一种非常有用的znode,可以通过持久类型的znode为应用保存一些数据。
临时znode,在一下两种情况下会被删除;
1、当创建该znode的客户端的会话因超时或者注定关闭而终止
2、当某个客户端主动删除该节点时候。
版本机制
假设客户端C1对znode /config写入一些配置信息,如果另一个客户端C2同时更新了这个znode,此时c1的版本号已经过期,c1调用setData一定不会成功。使用版本机制有效避免了以上情况。在这个例子中,C1在写入数据使用的版本无法匹配,是的操作失败。
2.2zookeeper架构
zookeeper服务器端运行于两种模式下:独立模式standalone,冲裁模式quorum。
在仲裁模式下,具有一组zookeeper服务,我们称之为zookeeper集合(zookeeper ensemble),它们之前可以进行状态复制,并同时为服务于客户端请求。
1、在仲裁模式下,利用公共管理领域最小有效投票数,在zookeeper中,则是指为了使得zookeeper工作必须有效的运行的服务的最小数量。这个数字也是服务器告知客户端安全数据前,需要保存客户端数据的最小数量。
2.2.2会话
,对zookeeper集合执行任何请求前,一个客户端必先与服务建立会话。会话的概念非常重要,对ZooKeeper的运行也非常关键。客户端提交给zookeeper的所有操作均关联在一个会话上。当一个会话因某种原因而中止时,在这个会话期间创建的临时节点会消失。
2.3.2 会话的状态和声明周期
会话的生命周期lifetime是指会话从创建到结束的时期,无论是会话的正常关闭还是因超时而导致过期。
状态:CONNECTING\CONNECTED\CLOSED\NOT_CONNECTED
开始——>NOT_CONNECTED
———>CONNECTING
———>CONNECTED
当发生断开或者无法收到服务器超时时
————>CONNECTING
当服务器确认会话有效后,状态会转回CONNECTED状态。
否则声明会话过期,然后转到CLOSED状态。
———
创建一个会时,你需要设置会话超时这个重要的参数,这个参数设置了zookeeper服务允许会话
被声明为超时之前存在的时间。如果经过时间t之后,服务接受不到这个会话的任何消息,服务就会声明会话过期。而在客户端侧,如果经过t/3的时间未收到任何消息,客户端将向服务器发送心跳消息。经过2t/3的时间后,zookeeper客户端开始寻找其他的服务器,而此时它还有t/3的时间去寻找。
寻找可用服务器列表是通过给服务器发送可用服务器地址列表的方式实现。
2.3.3zookeeper与仲裁模式
配置:
tickTime=2000
initLimit=10
synclimit=5
dataDir=./data
clientPort=2181
server.1=127.0.0.1:2222:2223
server.2=127.0.0.1:3333:3334
server.3=127.0.0.1:4444:4445
每一个server.h项指定了编号为n的zookeeper服务器使用的地址和端口号。每个server.h项通过冒号分隔为三部分,第一部分为服务器n的Ip地址或主机名称hostname,第二部分和第三部分为TCP端口号,分别为仲裁通信和群首选举。
穿件data目录
mkdir z1
mkdir z1/data
mkdir z2
mkdir z2/data
mkdir z3
mkdir z3/data
当启动一个服务时,我们需要知道启动的是哪个服务器,一个服务器通过读取data目录下的名为myid的文件来获得服务器ID信息,通过以下命令来创建这些文件
echo 1> z1/data/myid
echo 2> z2/data/myid
echo 3 > z3/data/myid
在同一台机器上需要创建z1/z1.cfg z2/z2.cfg z3/z3.cfg
zkServer.sh ./z1.cfg
注意:简单的负载均衡
客户端以随机顺序连接串中的服务器。这样可以用zookeeper来实现一个简单的负载均衡。不过客户端无法指定优先选择的服务器来进行连接。例如我们有5个zookeeper服务器的集合,
第二部分
使用zookeeper进行开发
例子代码
https://github.com/fpj/zookeeper-book-example
zookeeper java api进行开发:
顺序和ConnectionLossException
zooKeeper会严格维护执行顺序,并提供强有力的有序保障,然而,在多线程下还是需要小心面对顺序问题。在多线程下,当回调函数中包括重试逻辑的代码时,一些常见的场景都可能导致错误发生。当遇到connectionLossExceptin异常补发一个请求时,新建立的请求可能排序在其他线程中的请求之后,而实际上其他线程中的请求应该在原来请求之后。
3.5任务队列化
第四章 处理状态变化
4.1 单次触发器
我们所说的事件(event)表示一个znode节点执行更新操作。而一个监视点(watch)表示一个与之关联的znode节点和时间类型组成的单次触发器(例如:znode节点的数据被赋值,或znode节点被删除)。当一个监视点被一个事件触发时,就会产生一个通知(notification)。通知是注册了监视点的应用客户端收到的事件报告的消息。
当应用程序注册了一个监视点来接收通知,匹配该监视点条件的第一个事件会触发监视点的通知。并且最多只触发一次。当znode节点/z被删除,客户端需要知道该变化(例如,表示备份主节点),客户端在/z节点执行exists操作并设置监视点标志位,等待通知,客户端会以回调函数的形式受到通知。
客户端设置的每个监视点与会话关联,如果会话过期,期待中的监视点将会被删除。不过监视点可以扩月不同的服务端连接而保持,例如,当一个zookeeper客户端与一个zookeeper服务端的连接断开后连接到集合中的另一个服务器,客户端会发送未触发的监视点列表,在注册监视点时,服务端将要检查已监视的znode节点在之前注册监视点之后是否已经变化,如果znode节点已经发生变化,一个监视点的事件就会被发送给客户端,否则在新的服务端上注册监视点。
单次触发是否会丢失事件
答案是肯定的。一个应用在接收到通知后,注册另一个监视点时,可能会丢失事件,不过,这个问题需要再深入探讨,丢失事件通常并不是问题,因为任何在接受通知与注册新监视点之间的变化情况,均可以通过读取zookeeper的状态信息来获得。
假设一个节点接收到一个新任务分配给它的通知,为了接受新任务,从节点读取任务列表,如果在通知接收后,又给这个从节点分配了更多的任务,通过getchildren调用任务获取任务列表时会返回所有的任务。同时调用getchildren时也可以设置新的监视点,从而保证从节点不会丢失任务。
实际上,将多个事件分摊到一个通知上是具有积极的作用,比如,应用进行高频率的更新操作时,这种通知机制比每个时间都发送通知更加轻量化。举例子,如果每个通知平均补货两个事件,我们为每个时间只产生0.5个通知,而不是每个事件1个通知。
4.2如何设置监视点
Zookeeper的API中的所有读操作:getData,getChildren和exits,均可以选择在读取的znode节点上设置监视点。使用监视点机制,我们需要实现watcher接口,实现其中的process方法:
public void process(WatchedEvent event);
zookeeper会话状态:keeperSate:Disconnected、SyncConnected、AuthFailed、ConnectedReadOnly、SasAuthenticated和Expired。
事件类型(EventType):NodeCreated、NodeDeleted、NodeDataChanged、NodeChildrenChanged和None。
NodeCreated
通过exists调用设置一个监视点。
NodeDeleted
通过exists或者getData调用设置监视点。
NodeDataChanged
通过exists或者getData调用设置监视点
nodeChildrenChanged
通过getChildren调用设置监视点。
4.3普遍模型
1、进行调用异步
2、实现回调对象,并传入异步调用函数中
3、如果操作需要设置监视点,实现一个watcher对象,并传入异步调用函数中。
4.4主从模式的例子
现在,我们通过主从模式的例子来看看如何处理状态的变化。以下为一个任务列表,一个组件需要等待处理的变化情况:
1、管理权变化
2、主节点等待从节点列表的变化
3、主节点等待新任务进行分配
4、从节点等待分配新任务
5、客户端等待任务的执行结果
4.5另一种调用方式
我们希望大多数应用采用我们之前所讨论的模式,即使是该模式的各种变体也可以。我们专注于异步API,并且建议开发者也使用异步的方式,异步API可以让应用程序变得更加有效使用zookeeper资源、同时获得更高的性能。
8、Curator Zookeeper API的高级封装库
Curator作为zookeeper的一个高层次封装库,为开发人员封装了一组开发库,Curator的核心目的是为你管理zookeeper的相关操作
CuratorFramework
8.2 刘畅式API
流畅式API可以让我们编写链式调用的代码,而不用在进行请求操作时采用严格的签名方案。
实现异步调用的回调处理。假设在之前的调用中,回调方式将会通过create事件传递给注册的监听器。inBackground调用可以传入一个上下文对象,铜鼓该参数可以传入一个具体的回调方法的实现。
第9章
ZooKeeper 内部原理
服务器对客户端发送的请求操作了哪些工作?
群首和追随者组成了保障状态变化有序的核心实体,同时还存在第三类服务器,称为观察者(observer)。
观察者不会参与决策哪些请求可被接受的过程,只是观察决策的结果,观察者的设计师为了系统的可扩展性。
那些会改变Zookeeper状态的客户端请求create、delete和setData)将会被转发给群首、群首执行相应的请求,并形成状态的更新,我们成为事务(transaction)
事务的幂等性:可以对同一个事务执行多次,得到的结果都一样,前提是我们确保多个事务的执行顺序每次都是一样的。事务的幂等性可以让我们在进行恢复处理时更加简单。
当群首产生了一个事务,就会为该事务分配一个表示符,我们称之为zookeeper会话ID (zxid),通过zxid对事务进行标识,就可以按照群首所指定的顺序在各个服务器中按序执行。
服务器之间在进行新的群首选举时也会交换zxid信息,这样就可以知道哪个无故障服务器接受了更多的事务,并可以同步他们之间的状态信息。这样就知道哪个无故障服务器接受了更多的事务,并可以同步他们之间的状态信息。
zxid为一个long型64位整数,分为两部分:时间戳(epoch)部分和计数器counter两部分,每部分为32位,在我们讨论zab协议时,我们就发现时间错epoch和计数器counter的具体作用,我们通过该协议来广播各个服务器的状态变更信息。
9.2群首选举
设置群首的目的是为了对客户端发起的zookeeper状态变更请求进行排序,包括create、setData,delete操作。群首将每一个请求转化为一个事务,将这些事务发送给追随者,确保集群按照确定的顺序接受并处理这些事务。
仲裁(quorum)
仲裁模式要求服务器之间两两相交。
每个服务器启动后进入LOOKING状态,开始选举一个新的群首或查找已经存在的群首,如果群首已经存在,其他服务器就会通知这个新启动的服务器,告知那个服务器是群首,于此同时,新的服务器会与群首建立连接,以确保自己的状态与群首一致。
如果集群中所有的服务器均处于LOOKING状态,这些服务器之间就会进行通信来选举一个群首,通过信息交换对群首选举达成共识的选择,本次选举过程中胜出的服务器将进入LEADING状态
对于群首选择的消息,我们称之为群首选举通知消息(leader eletion notifications),或简单地称为通知(notifications)。该协议非常简单,当一个服务器进行LOOKING状态,就会向集群中每个服务器发送一个通知消息,该消息中包括服务器的投标vote信息,投票中包含服务器标识符(sid)和最近执行的事务的zxid信息。比如,一个服务器所发送的投标信息为(1,5),该服务器表示的sid为1,最近执行的事务的zxid为5.
当一个服务器收到一个投票信息,该服务器将会根据以下规则修改自己的投票信息:
1、将收到的Voteid和votezxid作为标识符,并获取接收方当前的投票中的zxid,用myzxid和mysid表示接收方服务器自己的值
2、如果votezxid>myzxid或者votezxid = myzxid且 voteid>myid,保留当前的投票信息。
3、否则,修改自己的投票信息,将votezxid赋值给myzxid,将voteid赋值给mySid,简而言之,只有最新的服务器将赢得选举,因为其拥有最近一个的zxid,如果多个服务器拥有最新的zxid值,其中的sid值最大的将赢得选举。
查找群首
在Zookeeper中对应实现选举的java类为QuorumPeer,其中的run方法实现了服务器的主要工作循环。当进入LOOKING状态,将会执行lookForLeader方法来进行群首的选举,该方法主要执行我们刚刚所讨论为协议。在方法返回前,在该方法中会将服务器状态设置为LEADING或FOLLOWING状态,当然还可能为OBSERVING状态,如果服务器称为群首,就会创建一个leader对象并运行这个对象。如果服务器为追随者,就会创建一个Follower对象并进行运行。
并不是所有执行过程,服务器s2做出了错误判断,选择了另一个服务器s3不是服务器s1,虽然s1的zxid值更高,但在从服务器s1向服务器s2传送消息时发生了网络故障导致长时间延迟,与此同时,服务器s2选择服务器s3作为群首,最终,服务器s1和服务器s3组成了仲裁数量quorum,并忽略服务器s2.
9.3 Zab:ZooKeeper原子广播协议(ZooKeeper Atomic Broadcast protocol)