ZooKeeper论文阅读笔记
Posted のNice
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ZooKeeper论文阅读笔记相关的知识,希望对你有一定的参考价值。
介绍
ZooKeeper 是一个开源的分布式协调服务,它提供了高可用性和一致性的数据管理和协调功能。它被设计用于构建可靠的分布式系统,并提供了一组简单而强大的 wait-free 原语,使开发人员能够处理分布式应用中的共享配置、命名服务、分布式锁、分布式队列等问题。
在论文中,客户端表示使用 Zookeeper 服务的应用,服务端表示一个运行 Zookeeper 程序的程序,znode 表示内存中的数据节点(Zookeeper 使用树形结构作为存储结构,整体就像一个文件系统)。
Zookeeper服务概述
znode 有两种类型:
- 常规:数据对象正常创建和删除
- 临时:创建对象的 session 终止之后,对象会被删除
znode 默认最大存储 1MB 数据,这是可配置的。
在创建文件的时候设置 SEQUENTIAL 标志,那么会在文件名后增加一个自动增加的计数器。比如上图创建 /app1 节点时设置了 SEQUENTIAL 标志,那么连续创建三个 /app1/p_ 节点时结果如上图所示。ZooKeeper 实现了观测(watch)机制,能够在数据对象更新后通知客户端(由与客户端连接的服务端通知),观测只会触发一次,如果想继续监测需要重新设置 watch。
ZooKeeper 以库的形式向客户端提供 API,类似于 RPC 中的 client stub,库也负责客户端到 ZooKeeper 服务器的连接,每个连接即是一个 session,它有一个超时时间 s,如果在超时时间内没有收到客户端请求,那么服务器认为客户端出故障,客户端即便没有读写请求也要发送心跳避免超时。为了防止会话超时,ZooKeeper 客户端库在会话空闲 \\(\\fracs3\\) 后发送心跳,如果在 \\(\\frac2s3\\) 内没有收到服务器的消息,则切换到新服务器,
其中 s 是以毫秒为单位的会话超时
客户端API
create(path, data, flags)
:创建一个路径为path
的 znode,将data[]
保存到其中,返回新 znode 的名称,flags
用来设置 znode 类型:普通或者临时,以及设置SEQUENTIAL
标志。delete(path, version)
:如果版本匹配,删除path
对应的 znode。exists(path, watch)
:如果path
对应的 znode 存在,那么返回真,否则返回假。watch
标志让客户端观测这个 znode。getData(path, watch)
:返回 znode 对应的数据和元数据,watch
功能类似。setData(path, data, version)
:如果版本匹配,将data[]
写入到path
对应的 znode 中。getChildren(path, watch)
:返回 znode 的子节点集合。sync(path)
:等待目前所有未决的更新,path
没什么用。
以上全部的方法提供了阻塞版本和非阻塞版本,如果传入版本号为-1,那么不进行版本检查。
Zookeeper保证
Zookeeper 给出了两个保证:
- 线性写入:所有改变 ZooKeeper 状态的更新都是串行的
- 客户端先进先出:所有来自客户端的请求按照先进先出顺序执行
Zookeeper 并不完全满足线性一致性,它只支持写请求的线性一致性,以及保证一个客户端的读操作一定可以读取到该客户端最后一个写操作的数据或者在这个写操作之后的其它客户端的写入数据,这是通过为每个写操作分配一个 zxid,zxid 是单调递增的,写请求的响应包含该写请求对应的 zxid,服务器将该写请求修改的 znode 的最近修改信息更新为该zxid(这句话是我的猜测),之后客户端发出读请求要求服务器读取的 znode 的 zxid 不小于客户端最近一次读请求返回的 zxid,如果该服务器在一定时间内还不能满足该要求,那么客户端会连接其它的服务器获取数据。
ZooKeeper 服务包含一组使用复制来实现高可用性和性能的服务器。ZooKeeper 使用简单的流水线架构实现,该架构允许 ZooKeeper 处理成百上千个未完成的请求,同时仍能实现低延迟。 这样的管道自然能够以 FIFO 顺序从单个客户端执行操作。 保证 FIFO 客户端顺序使客户端能够异步提交操作。 通过异步操作,客户端可以同时进行多个未完成的操作。
为了满足线性写入,Zookeeper 实现了一个 leader-based 的原子广播协议 ZAB,该协议改良自 Paxos 协议。
对于写入请求,如果与客户端连接的服务器是 Follower 节点,那么它会把请求转发给 Leader 服务器,如果是同步写,那么当写请求应用到超过半数服务器时返回响应给客户端,如果是异步写,那么立即返回成功响应给客户端。
对于读操作,不用转发给 Leader,直接返回服务器本地存储的数据即可。这样可能造成返回的数据不是最新的,但这是 ZooKeeper 可以容忍的。如果客户端想要最新的数据,那么它可以先发送一个 Sync 请求,该请求可以看作是一个空的写请求,由于 Zookeeper 的两个保证,在执行完 Sync 后再执行读操作就可以读取到所有在 Sync 之前执行的写请求数据了。
现在举个例子演示这两个保证如何保障系统运行。假设一个系统选举主节点管理其他节点,主节点随后需要更新一些配置,然后通知其他节点,要求:
- 主节点在修改配置过程,不希望其他节点访问正在被修改的配置
- 主节点在更新完成前崩溃,不希望其他节点访问这些破碎的配置
可以设置一个ready
znode解决,主节点可以在配置前删除,完成后重新建立。当其他节点看到ready
不存在时就不读取配置。
但是还会存在问题:如果其他节点看到ready
后读取配置,但是主节点随即删除开始修改配置,那么其他节点将得到过时的配置。这个问题可以采用 watch 机制来解决,ready
删除后会及时通知其他节点,这样客户端就会放弃读取的数据,然后重新设置 watch,当配置文件更新完毕后通知客户端发送读取请求。
原语示例:
-
配置管理:只需要将配置保存在一个 znode 中,各个进程可以通过 watch 机制来获取配置更新通知。
-
组成员关系:组成员进程上线之后可以在组对应的 znode 之下创建对应的临时子 znode,成员进程退出之后临时znode也被删除,因此可以通过组 znode 的子znode获取组成员状态。
-
简单锁:应用试图创建同一个文件,如果 znode 创建成功,那么获取锁。如果 znode 已经存在,那么需要等待锁被释放(znode被删除)后才能继续尝试获取锁(创建znode)。
-
无惊群效应的简单锁:简单锁会出现大量进程竞争的情况,因为每次锁被释放后所有等待的进程都会被通知唤醒,但最终只有一个进程可以获取锁,可以将锁请求排序后,按次序分配锁,实现公平锁,如下面的算法所示:
Lock (l是 znode 路径) 1 n = create(l + “/lock-”, EPHEMERAL|SEQUENTIAL) 2 C = getChildren(l, false) 3 if n is lowest znode in C, exit 4 p = znode in C ordered just before n 5 if exists(p, true) wait for watch event 6 goto 2
Unlock 1 delete(n)
-
读写锁:写锁和普通锁类似,和其他的锁互斥。读锁之间可以互相兼容,和写锁互斥。
Write Lock 1 n = create(l + “/write-”, EPHEMERAL|SEQUENTIAL) 2 C = getChildren(l, false) 3 if n is lowest znode in C, exit 4 p = znode in C ordered just before n 5 if exists(p, true) wait for event 6 goto 2
Read Lock 1 n = create(l + “/read-”, EPHEMERAL|SEQUENTIAL) 2 C = getChildren(l, false) 3 if no write znodes lower than n in C, exit 4 p = write znode in C ordered just before n 5 if exists(p, true) wait for event 6 goto 3
-
双栅栏:双栅栏用来保证多个客户端的计算同时开始和同时结束。客户端开始计算之前添加 znode 到栅栏对应的 znode 之下,结束计算之后删除 znode。客户端需要等待栅栏 znode 的子 znode 数量到达一定阈值后才能开始计算,客户端可以等待一个特殊的
ready
的 znode 的创建,当数量到达阈值后创建。客户端退出的时候需要等待子 znode 全部被删除,同样可以通过删除ready
删除。
Zookeeper实现
ZooKeeper 的组件如下图所示,ZooKeeper 的数据副本保存在每一个服务器的内存中,写操作需要通过一致性协议提交到数据库,而读取请求可以直接访问服务器本地数据库获得。ZooKeeper 在写请求修改到数据库之前会先将其写入日志(WAL)中,并在适当的时机持久化当前数据块的快照,故障后采用快照加日志的方式进行恢复。根据一致协议,写入请求会转发到领导(leader)节点。
请求处理器
请求处理器收到写入请求之后,会将其转换为事务,与客户端发送的请求不同,事务是幂等的。 当领导者收到写入请求时,它会计算应用写入时系统的状态,并将其转换为捕获此新状态的事务。 必须计算未来状态,因为可能有尚未应用到数据库的未完成事务。 例如,如果客户端执行条件 setData 并且请求中的版本号与正在更新的 znode 的未来版本号相匹配,则该服务会生成一个包含新数据、新版本号和更新时间戳的 setDataTXN。 如果发生错误,例如版本号不匹配或要更新的 znode 不存在,则会生成 errorTXN。
原子广播(Atomic Broadcast)
所有更新 ZooKeeper 状态的请求都被转发给领导者。 领导者执行请求并通过原子广播协议 Zab 广播 ZooKeeper 的状态变化。 接收到客户端请求的服务器在接收到相应的状态变化时响应客户端。 Zab 默认使用 majority quorums 来决定提案,因此 Zab 和 ZooKeeper 只能在大多数服务器正确的情况下工作。Zab 保证领导者广播的更改按照发送的顺序进行交付,并且在广播自己的更改之前,先前领导者的所有更改都已交付给现在的领导者
ZAB 使用 TCP 协议传输数据,而 TCP 协议可以保证传输的数据按照发送顺序到达。
在正常操作期间,Zab 确实按顺序传递所有消息,并且只传递一次,但由于 Zab 不会持久记录传递的每条消息的 ID,因此 Zab 可能会在恢复期间重新传递消息。 因为使用的是幂等事务,多次投递是可以接受的,只要按顺序投递即可。 事实上,ZooKeeper 要求 Zab 至少重新投递上次快照开始后投递的所有消息
多副本数据库
当服务器故障后,使用周期性的快照和快照之后的日志恢复。创建快照的时候并不需要锁定数据库,相反,Zookeeper 通过优先深度搜索遍历 data-tree 中znode 里的数据并持久化到磁盘中,由于生成的快照可能应用了快照生成期间交付的状态更改的某些子集,因此结果可能与 ZooKeeper 在任何时间点的状态都不对应。 但是,由于状态更改是幂等的,只要我们按顺序应用状态更改,我们就可以应用它们两次。
例如,假设现在有两个 znode 节点分别为 /foo 和 \\goo,这两个节点分别存储了数据 f1 和 g1,且版本号都为 1。此时开始生成快照,然后按序执行以下三个操作,操作序列含义为<transactionType,path,value,new-version>,此时快照检测点的位置不包含下面的操作序列。
- <SetFDataTrx,\\foo,f2,2>
- <SetFDataTrx,\\goo,g2,2>
- <SetFDataTrx,\\foo,f3,3>
处理完这些状态更改后,/foo 和 /goo 的值分别为版本 3 和版本 2 的 f3 和 g2。 然而,快照可能记录了 /foo 和 /goo 分别具有版本 3 和 1 的值 f3 和 g1,这不是 ZooKeeper 数据树的有效状态。 但如果服务器崩溃并使用此快照恢复,然后 Zab执行检查点后的日志重新提交上面的三个操纵,则生成的状态可以正确对应崩溃前的服务状态。
关于 AlphaGo 论文的阅读笔记
这是Deepmind 公司在2016年1月28日Nature 杂志发表论文 《Mastering the game of Go with deep neural networks and tree search》。介绍了 AlphaGo 程序的细节。
本博文是对这篇论文的阅读笔记。
AlphaGo 神经网络构成
AlphaGo 总体上由两个神经网络构成。以下我把它们简单称为「两个大脑」,这并非原文中的提法,仅仅是我的一个比喻。
第一个大脑(Policy Network)的作用是在当前局面下推断下一步能够在哪里走子。它有两种学习模式:一个是简单模式。它通过观察 KGS(一个围棋对弈server)上的对局数据来训练。粗略地说:这能够理解为让大脑学习「定式」,也就是在一个给定的局面下人类通常会怎么走,这样的学习不涉及对优劣的推断。
还有一个是自我强化学习模式。它通过自己和自己的海量对局的终于胜负来学习评价每一步走子的优劣。由于是自我对局,数据量能够无限增长。
第二个大脑(Value Network)的作用是学习评估总体盘面的优劣。它也是通过海量自我对局来训练的(由于採用人类对局会由于数据太少而失败)。
在对弈时。这两个大脑是这样协同工作的:
第一个大脑的简单模式会推断出在当前局面下有哪些走法值得考虑。
第一个大脑的复杂模式通过蒙特卡洛树来展开各种走法,即所谓的「算棋」,以推断每种走法的优劣。在这个计算过程中,第二个大脑会协助第一个大脑通过推断局面来砍掉大量不值得深入考虑的分岔树,从而大大提高计算效率。
与此同一时候,第二个大脑本身通过下一步棋导致的新局面的优劣本身也能给出关于下一步棋的建议。
终于,两个大脑的建议被平均加权。做出终于的决定。
在论文中一个有趣的结论是:两个大脑取平均的结果比依赖两者各自得出的结果都要好非常多。
这应当是让 AlphaGo 表现出和人类相似性的关键所在。
======================================
两个网络的输入都是整个棋盘的状态,不存在一个注重局部一个注重总体。差别在于功能,policy network用于直接给出落子策略,value network用于高速预计当前局面导致终于获胜的概率。MCTS在一个简化版的policy network的指导下多次搜索到终局来实时(但比較慢)预计当前局面终于获胜的概率。
value network估算的概率和MCTS估算的概率直接加权平均,终于选择落子位置的时候是根据这个加权平均和完整版policy network给出的对各落子位置的收益的一个加权平均,可是一个位置被MCTS搜索(相似于计算)的次数越多,policy network的结果(相似于经验和直觉)给的权值也越小。所以这一步并非一个简单的加权平均。也不是局部推断和全局推断的加权平均。两个网络考虑了多少全局因素和局部因素仅仅跟训练数据和当前棋盘状态有关,跟用的哪个网络没有关系。
使用图像识别经常使用的卷积神经网咯识别棋局确实可能不是最好的方案,可是围棋的状态和图像都具有一定的平移对称性。这是卷积网络擅长利用的特点。
我不了解围棋,但我猜人识别围棋局面应该也是有使用识别图像时的相似思维的。我看到有懂围棋的说人会使用一些抽象的模糊的概念进行高速但不精确的推演,假设这点非常重要的话,如今的算法可能还须要增加recurrent neural network来实现相似的功能。但也应该不是大问题。
加上机器高速搜索的能力。我觉得三月份输赢或许不好说。可是AI在围棋上碾压人类也就一两年内的事了。
学习效率问题。如今AI学习的方式确实跟人非常不一样,但这仅仅是由于人在学习一个新领域的知识的时候并非真正从零開始的,而像神经网络这样的模型往往是从随机的參数開始训练的,没有不论什么别的先验信息。
比方AI假设懂得人类语言以及各种常识,经常使用概念,那么開始学围棋的时候并不用像如今这样看大量人类的棋谱,然后疯狂自己跟自己下,而能够跟人一样由老师从基础知识開始一点点教授。这样或许起步能更快一些。
可是到了须要大量模仿和练习来提升技能的阶段,AI的学习效率真不一定比人差。
======================================
提出以下这些问题和评论
首先,这些神经网络训练在非常大程度上是通过自我对局来实现的。
这既是某种优势(依照 Facebook 人工智能研究员田渊栋的说法,几千万自我对局这样的规模是相当惊人的数据量)。某种程度上来说也是不得已而为之,由于人类对局的总数实在太少,会导致机器学习中常见的过度拟合问题。
可是这样是否有可能造成自我设限乃至画地为牢的后果?这同一时候牵涉到人们对神经网络学习过程的理解和对围棋本身的理解。
一方面,神经网络本身是否包容一定程度的「think out of the box」的能力。这固然取决于详细的神经网络算法,但也确实是人们对神经网络方法的一个本质困惑。
还有一方面,由于 AlphaGo 最基础的定式仍然是来源于人类对局,因此。这个问题依赖于人类棋手本身是否已经穷尽了围棋中全部有意义的基本定式。
(作为一个案例,在 AlphaGo 和樊麾的第二盘对局中,非常多人都注意到 AlphaGo 走了一个不标准的大雪崩定式。这是说明 AI 学错了呢,还是它发现这是更好的走法?)
其次,这两个大脑的工作方式确实和人类非常相似,一个推断细部,一个纵览全局。但 AlphaGo 终于的结合两者的方式相当简单粗暴:让两者各自评估一下每种可能的优劣,然后取一个平均数。
这可绝不是人类的思维方式。
对人类来说,这两种思考问题的方式的结合要复杂的多(不仅仅是在围棋中是这样)。
人们并非总是同一时候对事态做出宏观和微观的推断。而是有时候側重于大局。有时候側重于细部。
详细的精力分配取决于事态本身,也取决于人在当时的情绪、心理和潜意识应激反应。这当然是人类不完美之处,但也是人类行为丰富性的源泉。
而 AlphaGo 固然体现出一定的大局观,但从详细算法看来。它在为了宏观优势做出局部牺牲这方面的能力和人类全然不能相提并论。AlphaGo 引入总体盘面评估确实是它胜于很多别的围棋 AI 的地方。但从根本上来说,这仅仅是人们让 AI 具有「战略思维」的尝试的第一步,还有太多能够改进的可能性。
最后。和非常多别的围棋 AI 一样,当 AlphaGo 学习盘面推断的时候,採用的是图像处理的技术,也就是把围棋棋盘当做一张照片来对待。这当然在技术上是非常自然的选择,可是围棋棋局到底不是一般意义上的图案,它是否具有某些特质是常见的图像处理方法本身并不擅好处理的呢?
应用
为什么要让人工智能去下围棋?有非常多理由。但在我看来最重要的一个,是能够让我们更深入地理解智能这件事的本质。
神经网络和机器学习在过去十年里跃进式的发展,确实让 AI 做到了很多之前仅仅有人脑才干做到的事。但这并不意味着 AI 的思维方式接近了人类。并且吊诡的是,AI 在计算能力上的巨大进步。反而掩盖了它在学习人类思维方式上的短板。
以 AlphaGo 为例。
和国际象棋中的深蓝系统相比,AlphaGo 已经和人类接近了很多。深蓝仍然依赖于人类外部定义的价值函数,所以本质上仅仅是个高效计算器。但 AlphaGo 的价值推断是自我习得的,这就有了人的影子。然而如前所述。AlphaGo 的进步依赖于海量的自我对局数目,这当然是它的好处,但也恰好说明它并未真正掌握人类的学习能力。
一个人类棋手一生至多下几千局棋,就能掌握 AlphaGo 在几百万局棋中所训练出的推断力,这足以说明,人类学习过程中还有某种本质是临时还无法用当前的神经网络程序来刻画的。
(顺便提一句,非常多评论觉得 AlphaGo 能够通过观察一个特定棋手的对局来了解他的棋风以做出相应的对策。
至少从论文来看,这差点儿确定是不可能的事。
一个棋手的对局数对 AlphaGo 来说实在太少,无从对神经网络构成有效的训练。观察和总结一个人的「棋风」这件事仍然是人类具有全然优势的能力,对电脑来说。这恐怕比赢棋本身还更难一些。)
这当然不是说。AlphaGo 应该试图去复刻一个人类棋手的大脑。可是 AlphaGo 的意义当然也不应该仅仅反映在它终于的棋力上。
它是怎样成长的?成长曲线具有什么规律?它的不同參数设置怎样影响它的综合能力?这些不同參数是否就相应了不同的棋风和性格?假设有还有一个不同但水平相当的 AI 和它重复对弈。它是否能从对方身上「学到」和自我对弈不同的能力?对这些问题的研究和回答,恐怕比单纯观察它是否有朝一日能够超越人类要告诉我们多得多的知识。
因此。即使 AlphaGo 在三月份战胜了李世乭,在我看来也是还有一扇大门的开启而非关闭。
其实,即使就围棋发展本身而论。假设把 AlphaGo 的两个大脑以如此简单的方式线性耦合起来就能胜过人类,那仅仅能说明人们对围棋的规律还有太多值得探索的空间。
而对人工智能领域来说。AlphaGo 和一切神经网络一样,本质上还仅仅是个大黑盒,我们能观察到它表现出的巨大能力,但对它到底是怎样「思考」的这件事依旧所知甚少。
在project上,这是个伟大的胜利。在科学上,这仅仅是万里长征的第一步而已。
參考资料
AlphaGo 项目主页:http://www.deepmind.com/alpha-go.html
Nature 论文:http://www.nature.com/nature/journal/v529/n7587/full/nature16961.html
Nature 报道:http://www.nature.com/news/google-ai-algorithm-masters-ancient-game-of-go-1.19234
Dan Maas 对论文的浅显总结:http://www.dcine.com/2016/01/28/alphago/
关于程序算法艺术与实践很多其它讨论与交流,敬请关注本博客和新浪微博songzi_tea.
以上是关于ZooKeeper论文阅读笔记的主要内容,如果未能解决你的问题,请参考以下文章
SurfaceDefectsDetectionBasedonAdaptiveMultiscaleImageCollectionandConvolutionalNeuralNetworks-论文阅读笔记