【.com快速翻译】所谓分布式系统是指一组通过发送消息进行协作以达到同一目标的资源。正如著名的计算机科学家LeslieLamport所指出的定义:“所谓的分布式系统,其中任何计算设备——即使用户不直接使用它甚至不知道它的存在——故障也会影响其他设备。正常运行。”而这个定义也只是概括了我们在分布式系统中经常遇到的一类问题,事实上,在云计算时代,资源的聚合已经成为满足负载对计算和存储的实际需求的必然手段,这些系统分布式系统的特点是需要管理大量的资源,失败的频率与整体规模成正比。在分布式系统中,失败是一种常态,而不是例外——这意味着我们必须时刻做好准备。鉴于此,相关社区创建了专门的工具来帮助开发者处理这个问题,ApacheZooKeeper就是其中之一。也成为了NovaServiceGroupAPI后端不可或缺的一部分,最近ZooKeeper也与Ceilometer集成,带来更理想的高可用水平tytotheCentralAgent——当然这个话题我们后面会单独讨论。为什么我们需要ZooKeeper?一般来说,当你设计一个分布式应用程序时,你经常会发现需要协调各个进程来执行预期的任务。在大多数情况下,这种合作关系依赖于最基本的分布式合作机制。Heat是一个OpenStack编排器。你可以用它创建一系列的云资源,这些资源会被一个模板文件指定,这就是栈的概念。Heat允许用户更新堆栈,但是更新过程必须以原子方式执行,否则可能会发生资源重复或依赖破坏等冲突。此类问题在并发更新场景中非常常见。为了解决此类问题,Heat会在更新堆栈之前先设置一组所谓的分布式互斥量。在这样的原型之上进行开发是极其困难的,而且常常会让人头疼。事实上,这些在分布式系统中反复出现的问题早已成为技术圈的共识。为了简化开发人员的日常工作,雅虎实验室创建了ApacheZooKeeper项目,为这些协作因素提供集中式API。得益于ZooKeeper的帮助,我们现在可以轻松实现许多不同的协议,包括分布式锁、屏障和队列。ZooKeeper应用的架构和优势一个ZooKeeper应用是由一个或多个ZK服务器支撑的,我们可以称之为“集合”,以及应用端的一组ZK客户端。其设计思路是分布式应用的每个节点都使用一个ZK客户端来使用应用层的相关API,这意味着应用的运行将依赖于ZooKeeper服务器的实现。该架构方案具有以下突出优点:我们可以抽取大部分基于应用层的分布式同步负载,从而实现所谓的KISS(KeepItSimple,Stupid,keepeverythingsimpleandfool-like)架构。各种常见的分布式协作元素开箱即用,开发者无需自己处理。开发者无需处理服务故障等问题,因为整个系统具有极好的弹性。ZooKeeper作为应用程序的神经中枢而存在,因为它负责控制整个协调机制,所以很多组件都需要依附于它来实现它们的功能。基于这些原因,ZooKeeper在其设计中融入了优秀的分布式算法,以提供开发人员所需的高可靠性和可用性保证。一个ZooKeeper集合体以群体为基础存在,通常由三到五个服务器组成。ZooKeeperensemble可以在各种场景中发挥作用。让我们从实际的角度来看一下。ZooKeeper实践ZooKeeper的API非常简单直观,其数据模型基于以内存中树的形式存储的分层命名空间。树中的每个元素称为一个znode,它以文件的形式保存数据,并且可以像目录一样有子znode。首先,您需要确保您的运行环境满足系统配置要求。接下来,我们将部署一个ZK服务器:$wgethttp://apache.crihan.fr/dist/zookeeper/zookeeper-3.4.6/zookeeper-3.4.6.tar.gz$tarxzfzookeeper-3.4.6.tar.gz$cdzookeeper-3.4.6$cpconf/zoo_sample.cfgconf/zoo.cfg$./bin/zkServer.shstartZooKeeper服务器现在能够以独立模式运行,默认情况下将监听127.0.0.1:2181。如果您需要部署一整套服务器,您可以点击此处阅读相关的管理指南。ZooKeeper命令行界面我们可以使用ZooKeeper命令行界面(./bin/zkCli.sh)来完成一些基本的操作。它的用法与shell控制台非常相似,操作体验也与文件系统相当接近。下面列出根znode“/”中的所有子znode:[zk:localhost:2181(CONNECTED)0]ls/[zookeeper]创建一个路径为“/myZnode”的znode,其相关数据为“myData”:[zk:localhost:2181(CONNECTED)1]create/myZnodemyDataCreated/myZnode[zk:localhost:2181(CONNECTED)2]ls/[myZnode,zookeeper]删除一个znode:[zk:localhost:2181(CONNECTED)3]delete/myZnode可以输入“help”命令查看更多操作命令。在这个例子中,我们将使用应用程序编程接口(ApplicationProgrammingInterface,简称API)来编写一个分布式应用程序。PythonZooKeeperAPI我们的ZooKeeper服务器集是用Java编程语言构建的,并绑定了一组用不同语言编写的客户端。在今天的文章中,我们将使用Python捆绑客户端Kazzo探索API。我们可以在虚拟环境中轻松完成Kazoo的安装:$pipinstallkazoo首先,我们需要访问一个ZooKeeper集合:fromkazooimportclienttaskz_clientmy_client=kz_client.KazooClient(hosts='127.0.0.1:2181')defmy_listener(state):ifstate==kz_client.KazooState.CONNECTED:print("Clientconnected!")my_client.add_listener(my_listener)my_client.start(timeout=5)在上面的代码中,我们使用KazooClient类创建了一个ZK客户端。“hosts”参数负责定义ZK服务器地址,以逗号分隔,这样如果一个服务器出现故障,客户端会自动尝试连接到其他服务器。Kazoo可以在连接状态发生变化时通知我们。根据当前的具体情况,这个功能可以非常实用的触发各种预置操作。例如,当无法成功建立连接时,客户端应该停止发送命令,这就是add_listener()方法所做的。start()方法在确认会话创建后,可以在客户端和ZK服务器之间建立连接。每个服务器跟踪每个客户端中的一个会话,这个特性在实际的分布式协作元素中起着非常重要和基础的作用。#p#znode的增删改查和znode交互也非常简单:#createaznodewithdatamy_client.create(“/my_parent_znode”)my_client.create(“/my_parent_znode/child_1”,“child_data_1”)my_client.create(“/my_parent_znode/child_2”,“child_data_2”)#getthechildrenofaznodemy_client.get_children(“/my_parent_znode”)#getthedataofaznodemy_client.get(“/my_parent_znode/child_1”)#updateaznodemy_client.set(“/my_parent_znode/child_nodedele”,b“child_new_data_1”)#deleteaz("/my_parent_znode/child_1")set()方法会接受一个version参数,后者允许我们进行类似CAS的操作,从而保证任何用户在没有读取前提数据更新的情况下无法读取到最新版本有时,您可能希望确保一个znode名称是唯一的。我们可以通过使用sequentialznodes或sequentialznodes来实现这一点,它告诉服务器在每个路径的末尾添加一个单递增计数器。此时,ZooKeeper像一个普通的database,但更多有趣的功能还在后面。Observer观察者机制可以看作是ZooKeeper的核心功能之一,我们可以通过它来通知znode事件。换句话说,每个客户端都可以订阅特定znode的事件,并在其状态发生变化时收到通知。要获得此类通知,客户端必须注册回调方法——当特定事件发生时(通过后台线程)调用的方法。感兴趣的朋友可以点此查看ZooKeeper支持的不同事件类型(英文原文)。我们看一个示例代码,我们可以在一个znode的一个子集发生变化时触发通知机制:=my_func)需要指出的是,回调一旦执行,下次事件发生时客户端必须重新设置才能正常收到通知。临时znode如前所述,当客户端连接到服务器时,将建立会话。此会话始终保持打开状态,并负责向服务器发送心跳消息。一段时间不活动后,如果服务器没有听到来自客户端的更多活动,则会话关闭。由于此会话,服务器能够确定目标客户端是否仍处于活动状态。临时znode和普通znode没有本质区别。最大的区别是前者会在session过期时由服务器自动释放。如果我们将观察者与临时znode结合起来,我们可以实现ZooKeeper的杀手级功能。事实上,这些功能可以说为我们的分布式协作元实现工作开辟了大量的可能性。下面我们一起来看看分布式锁机制。分布式锁分布式锁应该算是分布式应用中出现频率最高的机制,因为我们经常需要以互斥的方式访问某些资源。在ZooKeeper中,这个任务可以说是非常容易了:my_lock=my_client.Lock("/lockpath","my-identifier")withlock:#blockswaitingforlockacquisition#dosomethingwiththelock涉及到的API和本地锁完全一样,但是在发动机发生了什么事?为了一探究竟,我们先来说说分布式算法是如何设计的。任何分布式算法都必须满足两个特性:安全性和活性。其中,安全性保证了算法永远不会偏离目标,而对于Bile来说,这意味着只有一个节点可以获得锁。从直观的角度来看,不可能有两个节点同时持有分布式锁。Liveness保证了算法的不断进步。在分布式锁的场景下,这意味着如果一个节点试图获取锁,它最终将能够获取到它。原生实现锁是一个众所周知的难题,大量算法专门作为解决方案存在——例如,Dekker算法,每个现代编程语言都将其包含在标准库中。但是需要强调的是,在分布式环境下这个问题会变得更加复杂。这两个特性之所以难以实现,是因为每个节点随时都可能发生故障,这必然会导致大量可能的故障场景。ZooKeeper为我们确保了这些属性:它可以帮助我们确保这两个特性:Liveness保证:多个临时znode组合起来检测故障节点,观察者机制负责通知其他节点。因此,如果一个节点获取分布式锁失败,其他节点将立即识别这种情况。安全保证:使用连续的znode,保证每个节点都有一个相互独立的名字,这样只有一个节点会获得分发锁。强烈建议大家点这里查看分布式锁文档,里面提到了Kazoo的各种实现。结束语构建分布式应用往往会成为一件令人头疼的事情,因为我们必须预见到所有可能随时发生的异常(即随机故障),并同时处理多个元素的组合。条件的指数增长(系统规模越大,条件的具体数量越多)。ZooKeeper是一个非常方便的工具,适合每个人打理自己的基础设施栈。在它的帮助下,我们可以更专注于应用程序逻辑。在OpenStack中,我们希望充分发挥ZooKeeper的设计目标,即用一个单一的通用工具来解决所有分布式系统带来的复杂问题。因此,我们创建了一组名为Tooz的库来实现分布式协作的一些通用元素。Tooz的正常运行依赖于许多不同的后端驱动程序——ZooKeeper当然是其中之一——并且可以在所有OpenStack项目中使用。在下一篇文章中,我们将看到如何使用OpenStackCeilometer让CentralAgent拥有出色的高可用性——这涉及到另一个重要的分布式元素,即组成员。我们也将基于ZooKeeper开发我们的第一个真正的应用程序,到时候见!原标题:ZooKeeperPart1:分布式系统工程师的瑞士军刀作者及出处为.com】
