Tallate

该吃吃该喝喝 啥事别往心里搁

Elasticsearch 是一个实时的分布式搜索和分析引擎。它可以帮助你用前所未有的速度去处理大规模数据。ElasticSearch 是一个基于 Lucene 的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于 RESTful web 接口。Elasticsearch 是用 Java 开发的,并作为 Apache 许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。
Elasticsearch 是一个分布式、可扩展、实时的搜索与数据分析引擎。大致上,它有以下重要特征:

  • 面向文档
  • Lucene 索引
  • 分布式

这篇文档主要介绍如何配置测试环境、搭建ES集群。

阅读全文 »

基于 Lucene,ES 实现了分布式的索引管理,这篇文档分析单机视角下的索引原理。

[x] ES如何保证搜索的近实时(1秒后被搜到)
[x] 为什么删除文档,不会立刻释放空间

阅读全文 »

就 ZooKeeper 来说,只明白原理是不够的,因为实际场景非常多,在不同场景下 ZooKeeper 几乎都有不同的应用模式,不过幸运的是 ZooKeeper 已经有一个比较完善的客户端 Curator,在生产环境下几乎不需要投入太多人力就可以解决大部分集群协调问题。

阅读全文 »

命名服务

把服务器名、资源名记录到zk里。
ZooKeeper命名服务
通过调用Zookeeper节点创建的API接口就可以创建一个顺序节点,并且在API返回值中会返回这个节点的完整名字,利用此特性,可以生成全局ID,其步骤如下

  1. 客户端根据任务类型,在指定类型的任务下通过调用接口创建一个顺序节点,如”job-“。
  2. 创建完成后,会返回一个完整的节点名,如”job-00000001”。
  3. 客户端拼接type类型和返回值后,就可以作为全局唯一ID了,如”type2-job-00000001”。

配置管理

程序分布式的部署在不同的机器上,将程序的配置信息放在zk的znode下,当有配置发生改变时,也就是znode发生变化时,可以通过改变zk中某个目录节点的内容,利用watcher通知给各个客户端从而更改配置。

集群管理

Zookeeper的两大特性:

  • 客户端如果对Zookeeper的数据节点注册Watcher监听,那么当该数据及诶单内容或是其子节点列表发生变更时,Zookeeper服务器就会向订阅的客户端发送变更通知。
  • 对在Zookeeper上创建的临时节点,一旦客户端与服务器之间的会话失效,那么临时节点也会被自动删除。

利用其两大特性,可以实现集群机器存活监控系统,若监控系统在/clusterServers节点上注册一个Watcher监听,那么但凡进行动态添加机器的操作,就会在/clusterServers节点下创建一个临时节点:/clusterServers/[Hostname],这样,监控系统就能够实时监测机器的变动情况。

Master选举

集群需要有一个Master,比如MySQL中需要有一个Master来负责写请求。ZooKeeper的强一致性可以保证这样的Master是唯一的。
集群中的每个节点可以定时在一个命名空间内创建节点,但只有一个客户端能创建成功,此时其成为Master。

分布式锁

分布式锁用于控制分布式系统之间同步访问共享资源的一种方式,可以保证不同系统访问一个或一组资源时的一致性,主要分为排它锁和共享锁。

排他锁

排他锁

  • 获取锁,在需要获取排它锁时,所有客户端通过调用接口,在/exclusive_lock节点下创建临时子节点/exclusive_lock/lock。Zookeeper可以保证只有一个客户端能够创建成功,没有成功的客户端需要注册/exclusive_lock节点监听。
  • 释放锁,当获取锁的客户端宕机或者正常完成业务逻辑都会导致临时节点的删除,此时,所有在/exclusive_lock节点上注册监听的客户端都会收到通知,可以重新发起分布式锁获取。

共享锁

共享锁又称为读锁,若事务T1对数据对象O1加上共享锁,那么当前事务只能对O1进行读取操作,其他事务也只能对这个数据对象加共享锁,直到该数据对象上的所有共享锁都被释放。
共享锁

获取锁

在需要获取共享锁时,所有客户端都会到/shared_lock下面创建一个临时顺序节点,如果是读请求,那么就创建例如/shared_lock/host1-R-00000001的节点,如果是写请求,那么就创建例如/shared_lock/host2-W-00000002的节点。

判断读写顺序

不同事务可以同时对一个数据对象进行读写操作,而更新操作必须在当前没有任何事务进行读写情况下进行,通过Zookeeper来确定分布式读写顺序,大致分为四步。

  1. 创建完节点后,获取/shared_lock节点下所有子节点,并对该节点变更注册监听。
  2. 确定自己的节点序号在所有子节点中的顺序。
  3. 对于读请求:若没有比自己序号小的子节点或所有比自己序号小的子节点都是读请求,那么表明自己已经成功获取到共享锁,同时开始执行读取逻辑,若有写请求,则需要等待。对于写请求:若自己不是序号最小的子节点,那么需要等待。
  4. 接收到Watcher通知后,重复步骤1。

释放锁

其释放锁的流程与独占锁一致。

惊群现象

上述共享锁的实现方案,可以满足一般分布式集群竞争锁的需求,但是如果机器规模扩大会出现一些问题,下面着重分析判断读写顺序的步骤3。

针对如上图所示的情况进行分析

  1. host1首先进行读操作,完成后将节点/shared_lock/host1-R-00000001删除。
  2. 余下4台机器均收到这个节点移除的通知,然后重新从/shared_lock节点上获取一份新的子节点列表。
  3. 每台机器判断自己的读写顺序,其中host2检测到自己序号最小,于是进行写操作,余下的机器则继续等待。
  4. 继续…

可以看到,host1客户端在移除自己的共享锁后,Zookeeper发送了子节点更变Watcher通知给所有机器,然而除了给host2产生影响外,对其他机器没有任何作用。大量的Watcher通知和子节点列表获取两个操作会重复运行,这样会造成系能鞥影响和网络开销,更为严重的是,如果同一时间有多个节点对应的客户端完成事务或事务中断引起节点小时,Zookeeper服务器就会在短时间内向其他所有客户端发送大量的事件通知,这就是所谓的羊群效应(惊群效应)

可以有如下改动来避免羊群效应。

  1. 客户端调用create接口常见类似于/shared_lock/[Hostname]-请求类型-序号的临时顺序节点。
  2. 客户端调用getChildren接口获取所有已经创建的子节点列表(不注册任何Watcher)。
  3. 如果无法获取共享锁,就调用exist接口来对比自己小的节点注册Watcher。对于读请求:向比自己序号小的最后一个写请求节点注册Watcher监听。对于写请求:向比自己序号小的最后一个节点注册Watcher监听。
  4. 等待Watcher通知,继续进入步骤2。

此方案改动主要在于:每个锁竞争者,只需要关注/shared_lock节点下序号比自己小的那个节点是否存在即可。

分布式队列(FIFO先入先出)

先进入队列的请求操作先完成后,才会开始处理后面的请求。FIFO队列就类似于全写的共享模型,所有客户端都会到/queue_fifo这个节点下创建一个临时节点,如/queue_fifo/host1-00000001。
分布式队列
创建完节点后,按照如下步骤执行。

  1. 通过调用getChildren接口来获取/queue_fifo节点的所有子节点,即获取队列中所有的元素。
  2. 确定自己的节点序号在所有子节点中的顺序。
  3. 如果自己的序号不是最小,那么需要等待,同时向比自己序号小的最后一个节点注册Watcher监听。
  4. 接收到Watcher通知后,重复步骤1。

分布式屏障

最终的合并计算需要基于很多并行计算的子结果来进行,开始时,/queue_barrier节点已经默认存在,并且将结点数据内容赋值为数字n来代表Barrier值,之后,所有客户端都会到/queue_barrier节点下创建一个临时节点,例如/queue_barrier/host1。
分布式屏障

创建完节点后,按照如下步骤执行。

  1. 通过调用getData接口获取/queue_barrier节点的数据内容,如10。
  2. 通过调用getChildren接口获取/queue_barrier节点下的所有子节点,同时注册对子节点变更的Watcher监听。
  3. 统计子节点的个数。
  4. 如果子节点个数还不足10个,那么需要等待。
  5. 接受到Wacher通知后,重复步骤3。

ZooKeeper 是分布式的、开源的分布式应用程序协调服务,原本是 Hadoop、HBase 的一个重要组件。它为分布式应用提供一致性服务的软件,包括:配置维护、域名服务、分布式同步、组服务等。

阅读全文 »

只要是具备 CP(CAP 取 CP)特点的分布式 KV 系统,原则上都可以作为 ZooKeeper 的替代品,但是选择时仍然要考虑设计原理上的差异、落地方案的成熟程度及业务场景实际所需。

阅读全文 »

连接建立和会话管理

ZooKeeper的连接与会话就是客户端通过实例化ZooKeeper对象来实现客户端与服务器创建并保持TCP连接的过程。

Watcher

数据存储

Leader 选举

选举相关概念

  • 服务器 ID
    编号越大在选择算法中的权重越大。比如有三台服务器,编号分别是 1、2、3,其中 3 的那台权重最大。
  • 数据 ID
    服务器中存放的最大数据 ID。值越大说明数据越新,在选举算法中数据越新权重越大。
  • 逻辑时钟
    或者叫投票的次数,同一轮投票过程中的逻辑时钟值是相同的。每投完一次票这个数据就会增加,然后与接收到的其它服务器返回的投票信息中的数值相比,根据不同的值做出不同的判断。
  • 选举状态
    LOOKING,竞选状态。
    FOLLOWING,随从状态,同步 leader 状态,参与投票。
    OBSERVING,观察状态,同步 leader 状态,不参与投票。
    LEADING,领导者状态。

zk 集群选举概述

配置多个实例共同构成一个集群对外提供服务以达到水平扩展的目的,每个服务器上的数据是相同的,每一个服务器均可以对外提供读和写的服务,这点和 redis 是相同的,即对客户端来讲每个服务器都是平等的。
zk集群选举概述
zookeeper 提供了三种集群选举方式:

  • LeaderElection
  • AuthFastLeaderElection
  • FastLeaderElection

默认的算法是 FastLeaderElection,所以这里主要分析它的选举机制。

QuorumPeer

主要看这个类,只有 LOOKING 状态才会去执行选举算法。每个服务器在启动时都会选择自己做为领导,然后将投票信息发送出去,循环一直到选举出领导为止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public void run() {
//.......
try {
while (running) {
switch (getPeerState()) {
case LOOKING:
if (Boolean.getBoolean("readonlymode.enabled")) {
//...
try {
//投票给自己...
setCurrentVote(makeLEStrategy().lookForLeader());
} catch (Exception e) {
//...
} finally {
//...
}
} else {
try {
//...
setCurrentVote(makeLEStrategy().lookForLeader());
} catch (Exception e) {
//...
}
}
break;
case OBSERVING:
//...
break;
case FOLLOWING:
//...
break;
case LEADING:
//...
break;
}

}
} finally {
//...
}
}

FastLeaderElection

它是 zookeeper 默认提供的选举算法,核心方法如下。可以与本文上面的流程图对照。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
public Vote lookForLeader() throws InterruptedException {
//...
try {
HashMap<Long, Vote> recvset = new HashMap<Long, Vote>();
HashMap<Long, Vote> outofelection = new HashMap<Long, Vote>();
int notTimeout = finalizeWait;
synchronized(this){
//给自己投票
logicalclock.incrementAndGet();
updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch());
}
//将投票信息发送给集群中的每个服务器
sendNotifications();
//循环,如果是竞选状态一直到选举出结果
while ((self.getPeerState() == ServerState.LOOKING) &&
(!stop)){

Notification n = recvqueue.poll(notTimeout,
TimeUnit.MILLISECONDS);
//没有收到投票信息
if(n == null){
if(manager.haveDelivered()){
sendNotifications();
} else {
manager.connectAll();
}
//...
}
//收到投票信息
else if (self.getCurrentAndNextConfigVoters().contains(n.sid)) {

switch (n.state) {
case LOOKING:

// 判断投票是否过时,如果过时就清除之前已经接收到的信息
if (n.electionEpoch > logicalclock.get()) {
logicalclock.set(n.electionEpoch);
recvset.clear();
//更新投票信息
if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
getInitId(), getInitLastLoggedZxid(), getPeerEpoch())) {
updateProposal(n.leader, n.zxid, n.peerEpoch);
} else {
updateProposal(getInitId(),
getInitLastLoggedZxid(),
getPeerEpoch());
}
//发送投票信息
sendNotifications();
} else if (n.electionEpoch < logicalclock.get()) {
//忽略
break;
} else if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
proposedLeader, proposedZxid, proposedEpoch)) {
//更新投票信息
updateProposal(n.leader, n.zxid, n.peerEpoch);
sendNotifications();
}
recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));
//判断是否投票结束
if (termPredicate(recvset,
new Vote(proposedLeader, proposedZxid,
logicalclock.get(), proposedEpoch))) {
// Verify if there is any change in the proposed leader
while((n = recvqueue.poll(finalizeWait,
TimeUnit.MILLISECONDS)) != null){
if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
proposedLeader, proposedZxid, proposedEpoch)){
recvqueue.put(n);
break;
}
}

if (n == null) {
self.setPeerState((proposedLeader == self.getId()) ?
ServerState.LEADING: learningState());
Vote endVote = new Vote(proposedLeader,
proposedZxid, proposedEpoch);
leaveInstance(endVote);
return endVote;
}
}
break;
case OBSERVING:
//忽略
break;
case FOLLOWING:
case LEADING:
//如果是同一轮投票
if(n.electionEpoch == logicalclock.get()){
recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));
//判断是否投票结束
if(termPredicate(recvset, new Vote(n.leader,
n.zxid, n.electionEpoch, n.peerEpoch, n.state))
&& checkLeader(outofelection, n.leader, n.electionEpoch)) {
self.setPeerState((n.leader == self.getId()) ?
ServerState.LEADING: learningState());
Vote endVote = new Vote(n.leader, n.zxid, n.peerEpoch);
leaveInstance(endVote);
return endVote;
}
}
//记录投票已经完成
outofelection.put(n.sid, new Vote(n.leader,
IGNOREVALUE, IGNOREVALUE, n.peerEpoch, n.state));
if (termPredicate(outofelection, new Vote(n.leader,
IGNOREVALUE, IGNOREVALUE, n.peerEpoch, n.state))
&& checkLeader(outofelection, n.leader, IGNOREVALUE)) {
synchronized(this){
logicalclock.set(n.electionEpoch);
self.setPeerState((n.leader == self.getId()) ?
ServerState.LEADING: learningState());
}
Vote endVote = new Vote(n.leader, n.zxid, n.peerEpoch);
leaveInstance(endVote);
return endVote;
}
break;
default:
//忽略
break;
}
} else {
LOG.warn("Ignoring notification from non-cluster member " + n.sid);
}
}
return null;
} finally {
//...
}
}

  • 判断是否已经胜出
    默认是采用投票数大于半数则胜出的逻辑。

选举消息内容

在投票完成后,需要将投票信息发送给集群中的所有服务器,它包含如下内容。

  • 服务器 ID
  • 数据 ID
  • 逻辑时钟
  • 选举状态

选举流程简述

目前有 5 台服务器,每台服务器均没有数据,它们的编号分别是 1,2,3,4,5,按编号依次启动,它们的选择举过程如下:

  • 服务器 1 启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器 1 的状态一直属于 Looking。
  • 服务器 2 启动,给自己投票,同时与之前启动的服务器 1 交换结果,由于服务器 2 的编号大所以服务器 2 胜出,但此时投票数没有大于半数,所以两个服务器的状态依然是 LOOKING。
  • 服务器 3 启动,给自己投票,同时与之前启动的服务器 1,2 交换信息,由于服务器 3 的编号最大所以服务器 3 胜出,此时投票数正好大于半数,所以服务器 3 成为领导者,服务器 1,2 成为小弟。
  • 服务器 4 启动,给自己投票,同时与之前启动的服务器 1,2,3 交换信息,尽管服务器 4 的编号大,但之前服务器 3 已经胜出,所以服务器 4 只能成为小弟。
  • 服务器 5 启动,后面的逻辑同服务器 4 成为小弟。

选举流程

描述 Leader 选择过程中的状态变化,这是假设全部实例中均没有数据,假设服务器启动顺序分别为:A,B,C。
选举流程图
选举状态图

如果规模为 5 的集群只起来其中的 3 台服务器,这时会进行选举吗

不会,ZooKeeper 更倾向于保持一致性,如果配置中的部分服务器不可用,那么整个集群都是不可用的。

参考

  1. 【分布式】Zookeeper 系统模型

ZooKeeper 服务器的启动流程

  1. 【分布式】Zookeeper 服务端启动
  2. 【分布式】Zookeeper 的服务器角色
  3. 【Zookeeper】源码分析之服务器(一)
  4. 【Zookeeper】源码分析之服务器(二)之 ZooKeeperServer
  5. 【Zookeeper】源码分析之服务器(三)之 LeaderZooKeeperServer
  6. 【Zookeeper】源码分析之服务器(四)之 FollowerZooKeeperServer
  7. 【Zookeeper】源码分析之服务器(五)之 ObserverZooKeeperServer

ZooKeeper 客户端

  1. 【分布式】Zookeeper 客户端

Watcher

  1. zookeeper 中 Watcher 通知机制的一点理解
  2. 【Zookeeper】源码分析之 Watcher 机制(一)
  3. 【Zookeeper】源码分析之 Watcher 机制(二)之 WatchManager
  4. 【Zookeeper】源码分析之 Watcher 机制(三)之 ZooKeeper

Leader 选举

  1. 【分布式】Zookeeper 的 Leader 选举
  2. 【Zookeeper】源码分析之 Leader 选举(一)
  3. 【Zookeeper】源码分析之 Leader 选举(二)之 FastLeaderElection

持久化

  1. 【分布式】Zookeeper 数据与存储
  2. 【Zookeeper】源码分析之持久化(一)之 FileTxnLog
  3. 【Zookeeper】源码分析之持久化(二)之 FileSnap
  4. 【Zookeeper】源码分析之持久化(三)之 FileTxnSnapLog

通信(通信协议、序列化、会话、请求处理)

  1. 【分布式】Zookeeper 序列化及通信协议
  2. 【Zookeeper】源码分析之序列化
  3. 【分布式】Zookeeper 会话
  4. 【分布式】Zookeeper 请求处理
  5. 【Zookeeper】源码分析之请求处理链(一)
  6. 【Zookeeper】源码分析之请求处理链(二)之 PrepRequestProcessor
  7. 【Zookeeper】源码分析之请求处理链(三)之 SyncRequestProcessor
  8. 【Zookeeper】源码分析之请求处理链(四)之 FinalRequestProcessor
  9. 【Zookeeper】源码分析之网络通信(一)
  10. 【Zookeeper】源码分析之网络通信(二)之 NIOServerCnxn
  11. 【Zookeeper】源码分析之网络通信(三)之 NettyServerCnxn

最近有点迷 ARPG,但是自己的 T470P 上只有一个 Ubuntu 系统,所以买了一个 2422 的固态往上面装个 Win10,Linux 下构建启动盘还是蛮多坑的,下面记录一下操作流程,免得以后忘了。

阅读全文 »

为什么要使用 sed

sed 全名叫 stream editor,流编辑器(也叫行编辑器),其处理文本的方式为一行一行的,不同于 vi 等全屏编辑器;主要用途为通过匹配一个或多个正则表达式来对文本进行处理,实现过滤和转换文本。

阅读全文 »
0%