异度部落格

学习是一种生活态度。

0%

Octopress是一个基于Jekyll静态页面生成器,多数用于Github Pages的生成。本文主要介绍Octopress在Windows以及Linux平台下的安装,本文的安装步骤基于Window 7以及Ubuntu 14.04。

Git安装

Git下载地址:http://git-scm.com/downloads

Ubuntu用户也可执行下面命令:

1
sudo apt-get install git

Ruby安装

Ruby下载地址:http://rubyinstaller.org/downloads/。这里需要注意,所选择的版本应为Ruby1.9.3或者更高,本文使用的是Ruby1.9.3。

Windows用户还需额外安装RubyDevKit,下载地址:http://rubyinstaller.org/downloads/

1
2
3
cd C:/RubyDevKit
ruby dk.rb init
ruby dk.rb install

安装后使用ruby --version命令确认所安装的Ruby版本。

Octopress安装

克隆Octopress源码,安装依赖。

1
2
3
4
git clone git://github.com/imathis/octopress.git octopress
cd octopress
gem install bundler
bundle install

安装Octopress默认主题。

1
rake install

到目前为止,Octopress已经安装完成,可以使用rake generate命令尝试生成静态页面。使用rake preview命令到http://localhost:4000进行预览。

Troubleshooting

Windows用户,在执行rake generate命令的过程中,可能会出现类似

1
warning: cannot close fd before spawn

的警告信息,导致静态页面生成失败。这是主要是由于pygments的版本与Windows不兼容导致的。可以尝试执行以下步骤进行修复:

1
2
gem uninstall pygments.rb --version ">0.5.0"
gem install pygments.rb --version "=0.5.0"

在项目文件夹下修改Gemfile.lock文件

1
pygments.rb (0.6.0) => pygments.rb (0.5.0)

Zookeeper 使用了一种称为 Zab(Zookeeper Atomic Broadcast)的协议作为其一致性的核心。Zab 协议是 Paxos 协议的一种变形,下面将展示一些协议的核心内容。

考虑到 Zookeeper 的主要操作数据状态,为了保证一致性,Zookeeper 提出了两个安全属性:

  • 全序(Total Order):如果消息 A 在消息 B 之前发送,则所有 Server 应该看到相同结果。
  • 因果顺序(Causal Order):如果消息 A 在消息 B 之前发生(A 导致了 B),并且一起发送,则消息 A 始终在消息 B 之前被执行。

为了保证上述两个安全属性,Zookeeper 使用了 TCP 协议和 Leader。通过使用 TCP 协议保证了消息的全序的特性(先发先到),通过 Leader 解决了因果顺序(先到 Leader 先执行)。因为有了 Leader,Zookeeper 的架构就变成为:Master-Slave 模式,但在该模式中 Master(Leader)会 Crash,因此,Zookeeper 引入 Leader 选举算法,以保证系统的健壮性。

当 Zookeeper Server 收到写操作,Follower 会将其转发给 Leader,由 Leader 执行操作。Client 可以直接从 Follower 上读取数据,如果需要读取最新数据,则需要从 Leader 节点读取,Zookeeper 设计的读写比大致为 2:1。

Leader 执行写操作可以简化为一个两段式提交的 transaction:

  1. Leader 发送 proposal 给所有的 Follower。
  2. 收到 proposal 后,Follower 回复 ACK 给 Leader,接受 Leader 的 proposal.
  3. 当 Leader 收到大多数的 Follower 的 ACK 后,将 commit 其 proposal。

broadcast

在这个过程中,proposal 的确认不需要所有节点都同意,如果有 2n+1 个节点,那么只要有 n 个节点同意即可,也就是说 Zookeeper 允许 n 个节点 down 掉。任何两个多数派必然有交集,在 Leader 切换(Leader down)时,这些交集依然保持着最新的系统状态。如果集群节点个数少于 n+1 个时,Zookeeper 将无法进行同步,也就无法继续工作。

Zab 与 Paxos

Zab 的作者认为 Zab 与 paxos 并不相同,只所以没有采用 Paxos 是因为 Paxos 保证不了全序顺序:

Because multiple leaders can propose a value for a given instance two problems arise. First, proposals can conflict. Paxos uses ballots to detect and resolve conflicting proposals. Second, it is not enough to know that a given instance number has been committed, processes must also be able to figure out which value has been committed.

举个例子。假设一开始 Paxos 系统中的 Leader 是 P1,他发起了两个事务{t1, v1}(表示序号为 t1 的事务要写的值是 v1)和{t2, v2},过程中 Leader 挂了。新来个 Leader 是 P2,他发起了事务{t1, v1'}。而后又来个新 Leader 是 P3,他汇总了一下,得出最终的执行序列{t1, v1'}和{t2, v2}。

这样的序列为什么不能满足 ZooKeeper 的需求呢?ZooKeeper 是一个树形结构,很多操作都要先检查才能确定能不能执行,比如 P1 的事务 t1 可能是创建节点“/a”,t2 可能是创建节点“/a/aa”,只有先创建了父节点“/a”,才能创建子节点“/a/aa”。而 P2 所发起的事务 t1 可能变成了创建“/b”。这样 P3 汇总后的序列是先创建“/b”再创建“/a/aa”,由于“/a”还没建,创建“a/aa”就搞不定了。

为了保证这一点,ZAB 要保证同一个 leader 的发起的事务要按顺序被 apply,同时还要保证只有先前的 leader 的所有事务都被 apply 之后,新选的 leader 才能在发起事务。

当 Leader 崩溃或者 Leader 失去大多数的 Follower,这时候 zk 进入恢复模式,恢复模式需要重新选举出一个新的 Leader,让所有的 Server 都恢复到一个正确的状态。Zookeeper 中 Leader 的选举采用了三种算法:

  • LeaderElection
  • FastLeaderElection
  • AuthFastLeaderElection

并且在配置文件中是可配置的,对应的配置项为 electionAlg。

背景知识

Zookeeper Server 的状态可分为四种:

  • LOOKING:寻找 Leader
  • LEADING:Leader 状态,对应的节点为 Leader。
  • FOLLOWING:Follower 状态,对应的节点为 Follower。
  • OBSERVING:Observer 状态,对应节点为 Observer,该节点不参与 Leader 选举。

成为 Leader 的必要条件: Leader 要具有最高的 zxid;当集群的规模是 n 时,集群中大多数的机器(至少 n/2+1)得到响应并 follow 选出的 Leader。

心跳机制:Leader 与 Follower 利用 PING 来感知对方的是否存活,当 Leader 无法相应 PING 时,将重新发起 Leader 选举。

术语

zxid:zookeeper transaction id, 每个改变 Zookeeper 状态的操作都会形成一个对应的 zxid,并记录到 transaction log 中。 这个值越大,表示更新越新。

electionEpoch/logicalclock:逻辑时钟,用来判断是否为同一次选举。每调用一次选举函数,logicalclock 自增 1,并且在选举过程中如果遇到 election 比当前 logicalclock 大的值,就更新本地 logicalclock 的值。

peerEpoch: 表示节点的 Epoch。

LeaderElection 选举算法

LeaderElection 是 Fast Paxos 最简单的一种实现,每个 Server 启动以后都询问其它的 Server 它要投票给谁,收到所有 Server 回复以后,就计算出 zxid 最大的哪个 Server,并将这个 Server 相关信息设置成下一次要投票的 Server。该算法于 Zookeeper 3.4 以后的版本废弃。

选举算法流程如下:

  1. 选举线程首先向所有 Server 发起一次询问(包括自己);
  2. 选举线程收到回复后,验证是否是自己发起的询问(验证 xid 是否一致),然后获取对方的 id(myid),并存储到当前询问对象列表中,最后获取对方提议的 leader 相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中;
  3. 收到所有 Server 回复以后,就计算出 zxid 最大的那个 Server,并将这个 Server 相关信息设置成下一次要投票的 Server;
  4. 线程将当前 zxid 最大的 Server 设置为当前 Server 要推荐的 Leader,如果此时获胜的 Server 获得多数 Server 票数, 设置当前推荐的 leader 为获胜的 Server,将根据获胜的 Server 相关信息设置自己的状态,否则,继续这个过程,直到 leader 被选举出来。

leader-election

通过流程分析我们可以得出:要使 Leader 获得多数 Server 的支持,则 Server 总数必须是奇数 2n+1,且存活的 Server 的数目不得少于 n+1.

异常问题的处理:

  1. 选举过程中,Server 的加入
    当一个 Server 启动时它都会发起一次选举,此时由选举线程发起相关流程,那么每个 Serve r 都会获得当前 zxi d 最大的哪个 Serve r 是谁,如果当次最大的 Serve r 没有获得 n/2+1 个票数,那么下一次投票时,他将向 zxid 最大的 Server 投票,重复以上流程,最后一定能选举出一个 Leader。
  2. 选举过程中,Server 的退出
    只要保证 n/2+1 个 Server 存活就没有任何问题,如果少于 n/2+1 个 Server 存活就没办法选出 Leader。
  3. 选举过程中,Leader 死亡
    当选举出 Leader 以后,此时每个 Server 应该是什么状态(FLLOWING)都已经确定,此时由于 Leader 已经死亡我们就不管它,其它的 Fllower 按正常的流程继续下去,当完成这个流程以后,所有的 Fllower 都会向 Leader 发送 Ping 消息,如果无法 ping 通,就改变自己的状为(FLLOWING ==> LOOKING),发起新的一轮选举。
  4. 选举完成以后,Leader 死亡
    处理过程同上。
  5. 双主问题
    Leader 的选举是保证只产生一个公认的 Leader 的,而且 Follower 重新选举与旧 Leader 恢复并退出基本上是同时发生的,当 Follower 无法 ping 同 Leader 是就认为 Leader 已经出问题开始重新选举,Leader 收到 Follower 的 ping 没有达到半数以上则要退出 Leader 重新选举。

FastLeaderElection 选举算法

由于 LeaderElection 收敛速度较慢,所以 Zookeeper 引入了 FastLeaderElection 选举算法,FastLeaderElection 也成了 Zookeeper 默认的 Leader 选举算法。

FastLeaderElection 是标准的 Fast Paxos 的实现,它首先向所有 Server 提议自己要成为 leader,当其它 Server 收到提议以后,解决 epoch 和 zxid 的冲突,并接受对方的提议,然后向对方发送接受提议完成的消息。FastLeaderElection 算法通过异步的通信方式来收集其它节点的选票,同时在分析选票时又根据投票者的当前状态来作不同的处理,以加快 Leader 的选举进程。

算法流程

数据恢复阶段

每个 ZooKeeper Server 读取当前磁盘的数据(transaction log),获取最大的 zxid。

发送选票

每个参与投票的 ZooKeeper Server 向其他 Server 发送自己所推荐的 Leader,这个协议中包括几部分数据:

  • 所推举的 Leader id。在初始阶段,第一次投票所有 Server 都推举自己为 Leader。
  • 本机的最大 zxid 值。这个值越大,说明该 Server 的数据越新。
  • logicalclock。这个值从 0 开始递增,每次选举对应一个值,即在同一次选举中,这个值是一致的。这个值越大说明选举进程越新。
  • 本机的所处状态。包括 LOOKING,FOLLOWING,OBSERVING,LEADING。

处理选票

每台 Server 将自己的数据发送给其他 Server 之后,同样也要接受其他 Server 的选票,并做一下处理。

如果 Sender 的状态是 LOOKING

  • 如果发送过来的 logicalclock 大于目前的 logicalclock。说明这是更新的一次选举,需要更新本机的 logicalclock,同事清空已经收集到的选票,因为这些数据已经不再有效。然后判断是否需要更新自己的选举情况。首先判断 zxid,zxid 大者胜出;如果相同比较 leader id,大者胜出。
  • 如果发送过来的 logicalclock 小于于目前的 logicalclock。说明对方处于一个比较早的选举进程,只需要将本机的数据发送过去即可。
  • 如果发送过来的 logicalclock 等于目前的 logicalclock。根据收到的 zxid 和 leader id 更新选票,然后广播出去。

当 Server 处理完选票后,可能需要对 Server 的状态进行更新:

  • 判断服务器是否已经收集到所有的服务器的选举状态。如果是根据选举结果设置自己的角色(FOLLOWING or LEADER),然后退出选举。
  • 如果没有收到没有所有服务器的选举状态,也可以判断一下根据以上过程之后更新的选举 Leader 是不是得到了超过半数以上服务器的支持。如果是,那么尝试在 200ms 内接收下数据,如果没有心数据到来说明大家已经认同这个结果。这时,设置角色然后退出选举。

如果 Sender 的状态是 FOLLOWING 或者 LEADER

  • 如果 LogicalClock 相同,将数据保存早 recvset,如果 Sender 宣称自己是 Leader,那么判断是不是半数以上的服务器都选举它,如果是设置角色并退出选举。
  • 否则,这是一条与当前 LogicalClock 不符合的消息,说明在另一个选举过程中已经有了选举结果,于是将该选举结果加入到 OutOfElection 集合中,根据 OutOfElection 来判断是否可以结束选举,如果可以也是保存 LogicalClock,更新角色,退出选举。

fast-leader-election

具体实现

数据结构

本地消息结构:

1
2
3
4
5
6
7
8
9
10
static public class Notification {
long leader; //所推荐的Server id

long zxid; //所推荐的Server的zxid(zookeeper transtion id)

long epoch; //描述leader是否变化(每一个Server启动时都有一个logicalclock,初始值为0)

QuorumPeer.ServerState state; //发送者当前的状态
InetSocketAddress addr; //发送者的ip地址
}

网络消息结构:

1
2
3
4
5
6
7
8
9
10
11
12
static public class ToSend {

int type; //消息类型
long leader; //Server id
long zxid; //Server的zxid
long epoch; //Server的epoch
QuorumPeer.ServerState state; //Server的state
long tag; //消息编号

InetSocketAddress addr;

}

线程处理

每个 Server 都一个接收线程池和一个发送线程池, 在没有发起选举时,这两个线程池处于阻塞状态,直到有消息到来时才解除阻塞并处理消息,同时每个 Server 都有一个选举线程(可以发起选举的线程担任)。

  • 接收线程的处理
    notification: 首先检测当前 Server 上所被推荐的 zxid,epoch 是否合法(currentServer.epoch <= currentMsg.epoch && (currentMsg.zxid > currentServer.zxid || (currentMsg.zxid == currentServer.zxid && currentMsg.id > currentServer.id))) 如果不合法就用消息中的 zxid,epoch,id 更新当前 Server 所被推荐的值,此时将收到的消息转换成 Notification 消息放入接收队列中,将向对方发送 ack 消息。
    ack: 将消息编号放入 ack 队列中,检测对方的状态是否是 LOOKING 状态,如果不是说明此时已经有 Leader 已经被选出来,将接收到的消息转发成 Notification 消息放入接收对队列

  • 发送线程池的处理
    notification: 将要发送的消息由 Notification 消息转换成 ToSend 消息,然后发送对方,并等待对方的回复,如果在等待结束没有收到对方法回复,重做三次,如果重做次还是没有收到对方的回复时检测当前的选举(epoch)是否已经改变,如果没有改变,将消息再次放入发送队列中,一直重复直到有 Leader 选出或者收到对方回复为止。
    ack: 主要将自己相关信息发送给对方

  • 选举线程的处理
    首先自己的 epoch 加 1,然后生成 notification 消息,并将消息放入发送队列中,系统中配置有几个 Server 就生成几条消息,保证每个 Server 都能收到此消息,如果当前 Server 的状态是 LOOKING 就一直循环检查接收队列是否有消息,如果有消息,根据消息中对方的状态进行相应的处理。

AuthFastLeaderElection 选举算法

AuthFastLeaderElection 算法同 FastLeaderElection 算法基本一致,只是在消息中加入了认证信息,该算法在最新的 Zookeeper 中也建议弃用。

Example

下面看一个 Leader 选举的例子以加深对 Leader 选举算法的理解。

  1. 服务器 1 启动,此时只有它一台服务器启动了,它发出去的报没有任何响应,所以它的选举状态一直是 LOOKING 状态.
  2. 服务器 2 启动,它与最开始启动的服务器 1 进行通信,互相交换自己的选举结果,由于两者都没有历史数据,所以 id 值较大的服务器 2 胜出,但是由于没有达到超过半数以上的服务器都同意选举它(这个例子中的半数以上是 3),所以服务器 1,2 还是继续保持 LOOKING 状态.
  3. 服务器 3 启动,根据前面的理论分析,服务器 3 成为服务器 1,2,3 中的 Leader,而与上面不同的是,此时有三台服务器选举了它,所以它成为了这次选举的 Leader.
  4. 服务器 4 启动,根据前面的分析,理论上服务器 4 应该是服务器 1,2,3,4 中最大的,但是由于前面已经有半数以上的服务器选举了服务器 3,所以它只能是 Follower.
  5. 服务器 5 启动,同 4 一样,Follower.

参考资料

http://blog.csdn.net/xhh198781/article/details/10949697

http://blog.cnsolomo.com/ld/liunx/nginx/264.html

http://blog.sina.com.cn/s/blog_3fe961ae01012jod.html

http://blog.csdn.net/xhh198781/article/details/6619203

http://csrd.aliapp.com/?p=162

http://codemacro.com/2014/10/19/zk-fastleaderelection/

在 Zookeeper 集群中,主要分为三者角色,而每一个节点同时只能扮演一种角色,这三种角色分别是:

  • Leader:接受所有 Follower 的提案请求并统一协调发起提案的投票,负责与所有的 Follower 进行内部的数据交换(同步);
  • Follower:直接为客户端服务并参与提案的投票,同时与 Leader 进行数据交换(同步);
  • Observer:直接为客户端服务但并不参与提案的投票,同时也与 Leader 进行数据交换(同步);

Follower 与 Observer 并称为 Learner。

zookeeper-server-roles

Leader

在 Zookeeper 集群中,只有一个 Leader 节点,其主要职责:

  • 恢复数据;
  • 维持与 Learner 的心跳,接收 Learner 请求并判断 Learner 的请求消息类型;

Leader 的工作流程简图如下所示,在实际实现中,流程要比下图复杂得多,启动了三个线程来实现功能。

leader-workflow

PING:Learner 的心跳。
REQUEST:Follower 发送的提议信息,包括写请求及同步请求。
ACK:Follower 的对提议的回复,超过半数的 Follower 通过,则 commit 该提议。
REVALIDATE:用来延长 SESSION 有效时间。

Follower

在 Zookeeper 集群中,follower 可以为多个,其主要职责:

  • 向 Leader 发送请求;
  • 接收 Leader 的消息并进行处理;
  • 接收 Zookeeper Client 的请求,如果为写清求,转发给 Leader 进行处理

Follower 的工作流程简图如下所示,在实际实现中,Follower 是通过 5 个线程来实现功能的。

follower-workflow

PING:心跳消息。
PROPOSAL:Leader 发起的提案,要求 Follower 投票。
COMMIT:服务器端最新一次提案的信息。
UPTODATE:表明同步完成。
REVALIDATE:根据 Leader 的 REVALIDATE 结果,关闭待 revalidate 的 session 还是允许其接受消息。
SYNC:返回 SYNC 结果到客户端,这个消息最初由客户端发起,用来强制得到最新的更新。

Observer

此处 Observer 就不再分析,其与 follower 基本相同,唯一不同就在于 Observer 不参与投票。

Zookeeper Server 的启动入口为 org.apache.zookeeper.server.quorum.QuorumPeerMain。Zookeeper 的启动模式分为两种:一种为 standalone mode;另一种为 cluster mode。

  • Standalone 模式:当配置文件中仅配置了一台 server 时,Zookeeper 将以 standalone 模式启动,启动类为 ZooKeeperServerMain,此处不作详细分析。
  • Cluster 模式:当配置文件中配置了多台 server,构建 cluster,启动类为 QuorumPeer#start()。
1
2
3
4
5
6
public synchronized void start() {
loadDataBase();
cnxnFactory.start();
startLeaderElection();
super.start();
}

清理 DataDir

在 Server 启动时,根据启动的配置参数启动一个 TimeTask 用于清理 DataDir 中的 snapshot 及对应的 transactionlog。由于 Zookeeper 的任何一个写操作都将在 transaction log 中留下记录,当写操作达到一定量或者一定时间间隔,Zookeeper 将 transaction log 合并为 snapshot。所以随着运行时间的增长生成的 transaction log 和 snapshot 将越来越多,所以定期清理是必要的。在 DatadirCleanupManager 中有两个参数:

  • snapRetainCount:清理后保留的 snapshot 的个数,该参数至少要大于 3。
  • purgeInterval:定期清理的时间间隔,以小时为单位。
1
2
3
4
5
// Start and schedule the the purge task
DatadirCleanupManager purgeMgr = new DatadirCleanupManager(config
.getDataDir(), config.getDataLogDir(), config
.getSnapRetainCount(), config.getPurgeInterval());
purgeMgr.start();

加载 ZKDatabase

1.从 snapshot 和 transaction log 中恢复 ZKDatabase,并将其载入内存。

1
zkDb.loadDataBase();

2.载入 currentEpoch 和 acceptedEpoch

首先要明白什么是 epoch,官方给出的解释是:

The zxid has two parts: the epoch and a counter. In our implementation the zxid is a 64-bit number. We use the high order 32-bits for the epoch and the low order 32-bits for the counter. Because it has two parts represent the zxid both as a number and as a pair of integers, (epoch, count). The epoch number represents a change in leadership. Each time a new leader comes into power it will have its own epoch number.

从 currentEpoch 文件中读取 current epoch;若 currentEpoch 文件不存在,Zookeeper 将从 lastProcessedZxid 中获取 epoch 作为 current epoch,写入 currentEpoch 文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
try {
currentEpoch = readLongFromFile(CURRENT_EPOCH_FILENAME);
if (epochOfZxid > currentEpoch && updating.exists()) {
LOG.info("{} found. The server was terminated after " +
"taking a snapshot but before updating current " +
"epoch. Setting current epoch to {}.",
UPDATING_EPOCH_FILENAME, epochOfZxid);
setCurrentEpoch(epochOfZxid);
if (!updating.delete()) {
throw new IOException("Failed to delete " +
updating.toString());
}
}
} catch(FileNotFoundException e) {
// pick a reasonable epoch number
// this should only happen once when moving to a
// new code version
currentEpoch = epochOfZxid;
LOG.info(CURRENT_EPOCH_FILENAME
+ " not found! Creating with a reasonable default of {}. This should only happen when you are upgrading your installation",
currentEpoch);
writeLongToFile(CURRENT_EPOCH_FILENAME, currentEpoch);
}

同理,获取 accepted epoch。

1
2
3
4
5
6
7
8
9
10
11
12
try {
acceptedEpoch = readLongFromFile(ACCEPTED_EPOCH_FILENAME);
} catch(FileNotFoundException e) {
// pick a reasonable epoch number
// this should only happen once when moving to a
// new code version
acceptedEpoch = epochOfZxid;
LOG.info(ACCEPTED_EPOCH_FILENAME
+ " not found! Creating with a reasonable default of {}. This should only happen when you are upgrading your installation",
acceptedEpoch);
writeLongToFile(ACCEPTED_EPOCH_FILENAME, acceptedEpoch);
}

启动 ServerCnxnFactory 线程

ServerCnxnFacotry 是管理 ServerCnxn 处理类的工厂,它负责对 connection 上数据处理的调度,以及 server 级别的一些处理,例如关闭指定 session 等。有关 ServerCnxnFactory 的实现类,可分为三种情况:

  • NIO 模式。这是 Zookeeper 默认的 ServerCnxnFactory 实现,其实现类为 NIOServerCnxnFactory。
  • Netty 模式。在 Zookeeper 3.4 以后引入了 Netty 作为 Server 端连接处理的可选实现。Netty 是一套非常高效的异步通信框架。可以通过 JVM 参数 zookeeper.serverCnxnFactory 进行配置。
  • 自定义模型。Zookeeper 还支持自定类来实现通信,同样可以通过 JVM 参数 zookeeper.serverCnxnFactory 进行配置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Static public ServerCnxnFactory createFactory() throws IOException {
String serverCnxnFactoryName =
System.getProperty(ZOOKEEPER_SERVER_CNXN_FACTORY);
if (serverCnxnFactoryName == null) {
serverCnxnFactoryName = NIOServerCnxnFactory.class.getName();
}
try {
return (ServerCnxnFactory) Class.forName(serverCnxnFactoryName)
.newInstance();
} catch (Exception e) {
IOException ioe = new IOException("Couldn't instantiate "
+ serverCnxnFactoryName);
ioe.initCause(e);
throw ioe;
}
}

ServerCnxnFactory 的主要职责:

  1. 在单机模式下,引导完成 Zookeeper Server 的实例化。
  2. 异步接收 Client 的 IO 连接,并维护连接的 IO 操作,这是 ServerCnxnFactory 的核心功能。ServerCnxnFacotry 本身被设计成一个 Thread,在完成初始化工作之后,就开始启动自身线程,在线程 run 方法中,采用 NIO 的方式 Accept 客户端连接,创建一个 NIOServerCnxn 实例,此实例和普通的 NIO 设计思路一样,它持有当前连接的 Channel 句柄和 Buffer 队列,最终将此 NIOServerCnxn 放入 Factory 内部的一个 set 中,以便此后对链接信息进行查询和操作(比如关闭操作,IO 中 read 和 write 操作等)。

Leader 选举

Leader 的选举算法有三种:

  • LeaderElection:LeaderElection 采用的是 fast paxos 算法的一种简单实现,目前该算法在 3.4 以后已经建议弃用。
  • FastLeaderElection:FastLeaderElection 是标准的 fast paxos 的实现,这是目前 Zookeeper 默认的 Leader 选举算法。
  • AuthFastLeaderElection:AuthFastLeaderElection 算法同 FastLeaderElection 算法基本一致,只是在消息中加入了认证信息,该算法在最新的 Zookeeper 中也建议弃用。

Leader 选举算法分析详见:Zookeeper 源码分析-Zookeeper Leader 选举算法

QuorumPeer 启动

完成 DataDir,ZKDatabase 加载以及 Leader 选举动作后,QuorumPeer 完成初始化及启动工作。

参考资料

http://zookeeper.apache.org/doc/r3.4.6/zookeeperInternals.html

http://shift-alt-ctrl.iteye.com/blog/1846507

本文主要介绍 Zookeeper 的数据模型,包括 Zookeeper 的数据视图,节点类型以及节点所包含的信息。

节点

Zookeeper 的数据视图采用的是类似 Unix 的数据视图,但是并没有引入文件系统的相关概念:目录和文件,而是引入了节点的概念,称为 Znode。它是 Zookeeper 最小的组成单元,每个 Znode 包含三部分组成:

  • data:表示节点所存储的数据,单个节点存储数据不能超过 1M。
  • stat:表示节点的信息,例如节点创建时间,节点的版本,节点的权限等信息。
  • children:表是所包含的子节点。

zookeeper-data-model

节点类型

Zookeeper 中的节点类型可以分为三种:

  • 持久节点(Perisient Node),这类节点在创建之后将一直存在,知道有客户端对它进行显示删除,也就是说它不会因为创建其的 client 的关闭而消失。
  • 临时节点(Ephemeral Node),这类节点的生命周期与创建它们的 client 绑定。也就是说当 client 的 session 关闭时,其所创建的临时节点也将被删除。这里有一点需要注意的是 session 关闭,而不是 connection loss。因为 zookeeper 的 client 支持 session 转移,也就是当所连接的 server 出现网络连接断开或者 server down 掉的情况,client 将自动选择 zookeeper 集群中的其他节点进行连接,同时将 session 转移到其他节点上。这种情况只能算 connection loss,而不会造成 session 关闭。除非,该 client 无法连接所有的节点,支持 session timeout。
  • 时序节点(Sequential Node),这类节点在创建节点的时候,zookeeper 会自动为其添加一个数字后缀%10d,例如/test-0000000001。不过这类节点是不能单独存在的,需要同持久节点或临时节点组合使用形成:
    • Perisient Sequential Node
    • Ephemeral Sequential Node

节点信息

Znode 的信息包含下面一些字段:

czxid 创建这个znode的zxid
mzxid 最后一次修改这个znode的zxid
ctime 该znode创建时间
mtime 该znode最后一次修改的时间
version 该znode的version,也就是该znode的修改次数。
cversion 该znode的子节点的version
aversion 该znode的ACL信息version
ephemeralOwner 如果该znode是ephemeral node,此字段就是对应client的session;否则为0。
dataLength The length of the data field of this znode.
numChildren 子节点个数

Note:zxid = znode transaction id

查看节点信息,可以使用 zkCli 中的 get 命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
[zk: localhost:2181(CONNECTED) 15] get /zk_test
my_data
cZxid = 0x8
ctime = Sun Sep 14 14:43:56 CST 2014
mZxid = 0x8
mtime = Sun Sep 14 14:43:56 CST 2014
pZxid = 0x10
cversion = 2
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 7
numChildren = 2

源码下载

1
git clone https://github.com/apache/zookeeper.git

源码编译

Zookeeper 的代码构建采用的是,所以可以直接使用 ant 命令进行编译。

1
ant

Unit Tests

1
ant -Djavac.args="-Xlint -Xmaxwarns 1000" clean test tar

Troubleshooting

1.在运行 unit test 的时候出现

1
2
3
4
5
6
7
create-cppunit-configure:
[exec] configure.ac:37: warning: macro 'AM_PATH_CPPUNIT' not found in library
[exec] configure.ac:37: error: possibly undefined macro: AM_PATH_CPPUNIT
[exec] If this token and others are legitimate, please use m4_pattern_allow.
[exec] See the Autoconf documentation.
[exec] configure.ac:57: error: possibly undefined macro: AC_PROG_LIBTOOL
[exec] autoreconf: /usr/bin/autoconf failed with exit status: 1

解决方法:

1
sudo apt-get install libtool

2.在运行 unit test 的时候出现

1
2
3
4
5
6
create-cppunit-configure:
[mkdir] Created dir: /home/killua/Workspace/zookeeper/build/test/test-cppunit
[exec] checking for doxygen... no
[exec] checking for perl... /usr/bin/perl
[exec] checking for dot... no
[exec] configure: error: cannot find install-sh, install.sh, or shtool in "/home/killua/Workspace/zookeeper/src/c" "/home/killua/Workspace/zookeeper/src/c/.." "/home/killua/Workspace/zookeeper/src/c/../.."

解决方法:

1
2
3
4
5
sudo apt-get install doxygen
sudo apt-get install perl
sudo apt-get install shtool
sudo apt-get install graphviz
sudo apt-get install libcppunit-dev

最近在Linux使用GoAgent+SwitchySharp下,Github无法加载CSS。

image

在Chrome中按下F12进行调试,重新加载页面显示如下:

image

由此可见,CSS无法加载的原因就在于,GoAgent无法连接到github.global.ssl.fastly.net。因此解决方法只要将github.global.ssl.fastly.net加入SwitchySharp的rule就可以了,使其Direct Connection。

image

SkipList 介绍

SkipList(跳跃表)是一种随机化数据结构,基于并联的链表,实现简单,插入、删除、查找的复杂度均为O(logN)

SkipList 是对有序的链表增加上附加的前进链接,增加是以随机化的方式进行的,所以在列表中的查找可以快速的跳过部分列表,因此得名。所有操作都以对数随机化的时间进行。

此外,SkipList 在当前热门的开源项目中也有很多应用,比如 LevelDB 的核心数据结构 memtable 以及 redis 的 sorted set。在 JDK 中,ConcurrentSkipListMap 的核心数据结构也是利用 SkipList 实现的。

SkipList 主要思想

先看一下普通的有序单链表: image

要在里面查找一个值就需要顺序比较,如何降低复杂度,折半查找也却可以将复杂度降到 O(log N),但不适应单链表,那就将折半的思想抽出来,隔一段位置就建立一个标签索引,根据标签索引缩短查找范围,就是 SkipList,如下图:

image SkipList 通过对间隔的数据做一个标签索引,产生了多层单链表,在最高层依次确定查找数据的范围,最终将范围缩小到可接受值,我们看 SkipList 其实就是一个二叉查找树的变形,只是所有的数据都在最左段,其他节点用来建立查找索引,如此 SkipList 的插入删除就比二叉查找树方便多了。

一个 SkipList,应该具有以下特征:

  • 一个 SkipList 应该有几个层(level)组成;
  • SkipList 的第一层包含所有的元素;
  • 每一层都是一个有序的链表;
  • 如果元素 x 出现在第 i 层,则所有比 i 小的层都包含 x;
  • 第 i 层的元素通过一个 down 指针指向下一层拥有相同值的元素;
  • 在每一层中,-1 和 1 两个元素都出现(分别表示 INT_MIN 和 INT_MAX);
  • Top 指针指向最高层的第一个元素。

SkipList 主要操作

查找

例如:查找元素 117

  • 比较 21, 比 21 大,往后面找
  • 比较 37,   比 37 大,比链表最大值小,从 37 的下面一层开始找
  • 比较 71,   比 71 大,比链表最大值小,从 71 的下面一层开始找
  • 比较 85, 比 85 大,从后面找
  • 比较 117, 等于 117, 找到了节点。

image

插入

先确定该元素要占据的层数 K(采用随机的方式),然后在 Level1 … LevelK 各个层的链表都插入元素。

例如:插入 119, K = 2 image 如果 K 大于链表的层数,则要添加新的层。

例如:插入 119, K = 4 image

删除

在各个层中找到包含 x 的节点,使用标准的 delete from list 方法删除该节点。

例如:删除 71 image

SkipList 复杂度分析

image image image

参考资料

http://blog.csdn.net/daniel_ustc/article/details/20218489 http://dsqiu.iteye.com/blog/1705530 http://in.sdo.com/?p=711