Tallate

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

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 等全屏编辑器;主要用途为通过匹配一个或多个正则表达式来对文本进行处理,实现过滤和转换文本。

阅读全文 »

sar

Collect, report, or save system activity information.
根据要统计的信息类型的不同,输出格式也不同。

统计CPU利用率

1
2
3
4
# 查看全天
sar -p
# 每隔1秒统计一次,统计10次
sar -u 1 10

CPU统计信息输出格式:

  • CPU
    all表示统计信息为所有 CPU 的平均值。
  • %user
    显示在用户级别(application)运行使用 CPU 总时间的百分比。
  • %nice
    显示在用户级别,用于nice操作,所占用 CPU 总时间的百分比。
  • %system
    在核心级别(kernel)运行所使用 CPU 总时间的百分比。
  • %iowait
    显示用于等待I/O操作占用 CPU 总时间的百分比。
  • %steal
    管理程序(hypervisor)为另一个虚拟进程提供服务而等待虚拟 CPU 的百分比。
  • %idle
    显示 CPU 空闲时间占用 CPU 总时间的百分比。

内存利用率

1
2
3
4
# 查看全天
sar -r
# 每隔1秒统计一次,统计10次
sar -r 1 10

内存统计信息输出格式

  • kbmemfree
    这个值和free命令中的free值基本一致,所以它不包括buffer和cache的空间。
  • kbmemused
    这个值和free命令中的used值基本一致,所以它包括buffer和cache的空间。
  • %memused
    这个值是kbmemused和内存总量(不包括swap)的一个百分比。
  • kbbuffers和kbcached
    这两个值就是free命令中的buffer和cache。
  • kbcommit
    保证当前系统所需要的内存,即为了确保不溢出而需要的内存(RAM+swap)。
  • %commit
    这个值是kbcommit与内存总量(包括swap)的一个百分比。

磁盘IO

1
2
3
4
# 查看全天
sar -d
# 每隔1秒统计一次,统计10次
sar -d 1 10

IO信息输出格式

  • await
    表示平均每次设备I/O操作的等待时间(以毫秒为单位)。
  • svctm
    表示平均每次设备I/O操作的服务时间(以毫秒为单位)。
  • %util
    表示一秒中有百分之几的时间用于I/O操作。

网络流量

1
2
3
4
# 查看全天
sar -n DEV
# 每个1秒统计一次,统计10次
sar -n DEV 1 10

流量信息输出格式:

  • IFACE
    就是网络设备的名称。
  • rxpck/s
    每秒钟接收到的包数目。
  • txpck/s
    每秒钟发送出去的包数目。
  • rxkB/s
    每秒钟接收到的字节数。
  • txkB/s
    每秒钟发送出去的字节数。
  • rxcmp/s
    每秒钟接收到的压缩包数目。
  • txcmp/s
    每秒钟发送出去的压缩包数目。
  • rxmcst/s
    每秒钟接收到的多播包的包数目。

系统信息

  1. 操作系统版本
    1
    head -n 1 /etc/issue
  2. uname(系统信息)
    1
    uname -a
  3. hostname(计算机名称)
    1
    hostname
  4. lspci(PCI 设备信息)
    1
    lspci -tv
  5. lsusb(USB 设备信息)
    1
    lsusb -tv
  6. lsmod(系统加载的模块信息)
    1
    lsmod
  7. CPU 信息
    1
    cat /proc/cpuinfo

CPU & 进程

ps

查看进程状态。

参数 作用
-a 显示所有的进程(包括其他用户的)
-u 用户以及其他详细信息
-x 显示没有控制终端的进程
1
2
3
4
5
ps -ef
ps -aux
$ ps -e -o 'pid,comm,args,pcpu,rsz,vsz,stime,user,uid'
# 其中rsz为实际内存,实现按内存排序,由大到小
$ ps -e -o 'pid,comm,args,pcpu,rsz,vsz,stime,user,uid' | grep oracle | sort -nrk5

linux 系统中进程最常见的 5 种状态为:

  • R(运行):正在运行或在运行队列中等待。
  • S(中断):休眠中, 在等待某个条件的形成或接受到信号。
  • D(不可中断):收到信号不唤醒和不可运行, 进程必须等待直到有中断发生。
  • Z(僵死):进程已终止, 但进程描述符存在, 直到父进程调用 wait()系统调用后释放。
  • T(停止):进程收到 SIGSTOP, SIGSTP, SIGTIN, SIGTOU 信号后停止运行。

当执行”ps aux”命令后通常会看到下面格式的进程状态,表格中只是列举了部分输出值,而且正常的输出值中不包括中文注释部分:

USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
进程的所有者 进程 ID 号 运算器占用率 内存占用率 虚拟内存使用量(单位是 KB) 占用的固定内存量(单位是 KB) 所在终端 进程状态 被启动的时间 实际使用 CPU 的时间 命令名称与参数
root 1 0.0 0.4 53684 7628 ? Ss 07:22 0:02 /usr/lib/systemd/systemd
root 2 0.0 0.0 0 0 ? S 07:22 0:00 [kthreadd]
root 3 0.0 0.0 0 0 ? S 07:22 0:00 [ksoftirqd/0]
root 5 0.0 0.0 0 0 ? S< 07:22 0:00 [kworker/0:0H]
root 7 0.0 0.0 0 0 ? S 07:22 0:00 [migration/0]

pidof

查询某个指定服务的进程 PID 号码值,比如

1
pidof firefox

kill

终止某个指定 PID 号码的进程

1
2
3
$ kill <PID>
# 杀掉所有进程名里带idea的
$ ps -aux | grep idea | awk '{print $2}' | xargs kill

killall

终止某个指定名称的服务所对应的全部进程,因为一般大型软件的服务程序通常都会有数个进程协同为其提供服务,如果逐个去结束 PID 实在麻烦,所以可以使用 killall 命令来批量结束某个服务程序的全部进程,比如

1
$ killall httpd

top

top 命令是 Linux 下常用的性能分析工具,能够动态监视进程活动与系统负载等信息,类似于 Windows 的任务管理器
可以直接使用 top 命令后,查看%MEM 的内容。可以选择按进程查看或者按用户查看,如想查看 oracle 用户的进程内存使用情况的话可以使用如下的命令:

1
$ top -u oracle

示例输出如下:

1
2
3
4
5
6
7
8
9
10
11
top - 17:59:11 up 289 days,  5:57, 20 users,  load average: 0.01, 0.02, 0.05
Tasks: 201 total, 2 running, 199 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.1 us, 0.0 sy, 0.0 ni, 99.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 8010956 total, 465600 free, 886236 used, 6659120 buff/cache
KiB Swap: 8191996 total, 8170296 free, 21700 used. 6412852 avail Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
751 root 20 0 555136 7428 3784 S 0.3 0.1 29:27.31 tuned
1176 root 20 0 91160 2032 1712 S 0.3 0.0 248:22.80 zabbix_agentd
2287 root 20 0 1033580 260536 3204 S 0.3 3.3 149:31.25 salt-minion
......

前面的五行为系统整体的统计信息,下面我们来逐行的讲解:

  • 第 1 行:系统时间,运行时间(上例中是 289 天 5 小时 57 分),登录用户数,系统负载(分别为 1 分钟、5 分钟、15 分钟的平均值)。
  • 第 2 行:进程总数,运行中的,睡眠中的,停止的,僵死的。
  • 第 3 行:用户态占用时间比例(us user),内核态占用时间比例(sy system),改变过优先级的进程占用时间比例(ni nice),空闲 CPU 时间比例(id idle),等待 IO 时间比例(wa iowait),处理硬中断时间比例(hi hard interrupt),处理软中断时间比例(si soft interrupt),当前系统运行在虚拟机中的时候、被其他虚拟机占用的 CPU 时间比例(st steal)。
    此行数据为百分比,比如11.2 id意味着有 11.2%的 CPU 资源是空闲的。
  • 第 4 行:物理内存总量,空闲量,使用量,作为内核缓存的内存量。
  • 第 5 行:虚拟内存总量,空闲量,使用量,已被提前加载的内存数据。

后面每行是进程的统计数据,其中:
PID:进程的 ID
USER:进程所有者
PR:进程的优先级别,越小越优先被执行
NInice:值
VIRT:进程占用的虚拟内存
RES:进程占用的物理内存
SHR:进程使用的共享内存
S:进程的状态。S 表示休眠,R 表示正在运行,Z 表示僵死状态,N 表示该进程优先值为负数
%CPU:进程占用 CPU 的使用率
%MEM:进程使用的物理内存和总内存的百分比
TIME+:该进程启动后占用的总的 CPU 时间,即占用 CPU 使用时间的累加值。
COMMAND:进程启动命令名称

常用的命令:
P:按%CPU 使用率排行
T:按 MITE+排行
M:按%MEM 排行

pmap

可以根据进程查看进程相关信息占用的内存情况,(进程号可以通过 ps 查看)如下所示:

1
$ pmap -d 14596

内存

/proc/meminfo

查看内存总量和空闲内存量

1
2
$ grep MemTotal /proc/meminfo
$ grep MemFree /proc/meminfo

free

内存和交换分区容量及使用情况

1
2
3
4
5
6
7
8
9
10
$ free
total used free shared buffers cached
Mem: 2902976 932640 1970336 9856 928 328196
-/+ buffers/cache: 603516 2299460
Swap: 2097148 0 2097148
$ free -h
total used free shared buffers cached
Mem: 2.8G 910M 1.9G 9.6M 928K 320M
-/+ buffers/cache: 589M 2.2G
Swap: 2.0G 0B 2.0G

total:总计内存量
used:已用量
free:可用量
shared:进程共享的内存量
buffers:磁盘缓存的内存量
cached:缓存的内存量

pmap

TODO

硬盘

df

硬盘分区使用情况

1
df

du

查看某个目录的大小

1
du -sh <目录>

网络

TODO

ifconfig

获取网卡配置与网络状态等信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ifconfig

eno16777728: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
ether 00:0c:29:62:f3:d0 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 0 (Local Loopback)
RX packets 514 bytes 41612 (40.6 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 514 bytes 41612 (40.6 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

注意每段开头的网卡名称、inet 参数后面的 IP 地址、ether 参数后面的物理 mac 地址,以及 RX、TX 的接受与发送数据包的大小

nc

netstat

iostat

tcpdump

mtr

监控

Linux性能观测工具

syslog

TODO

dmesg

如果发现自己的 java 进程悄无声息的消失了,几乎没有留下任何线索,那么 dmesg 一发,很有可能有你想要的。

1
sudo dmesg|grep -i kill|less

去找关键字 oom_killer。找到的结果类似如下:

1
2
3
4
5
[6710782.021013] java invoked oom-killer: gfp_mask=0xd0, order=0, oom_adj=0, oom_scoe_adj=0
[6710782.070639] [<ffffffff81118898>] ? oom_kill_process+0x68/0x140
[6710782.257588] Task in /LXC011175068174 killed as a result of limit of /LXC011175068174
[6710784.698347] Memory cgroup out of memory: Kill process 215701 (java) score 854 or sacrifice child
[6710784.707978] Killed process 215701, UID 679, (java) total-vm:11017300kB, anon-rss:7152432kB, file-rss:1232kB

以上表明,对应的 java 进程被系统的 OOM Killer 给干掉了,得分为 854.
解释一下 OOM killer(Out-Of-Memory killer),该机制会监控机器的内存资源消耗。当机器内存耗尽前,该机制会扫描所有的进程(按照一定规则计算,内存占用,时间等),挑选出得分最高的进程,然后杀死,从而保护机器。
dmesg 日志时间转换公式:
log 实际时间=格林威治 1970-01-01+(当前时间秒数-系统启动至今的秒数+dmesg 打印的 log 时间)秒数:

1
date -d "1970-01-01 UTC `echo "$(date +%s)-$(cat /proc/uptime|cut -f 1 -d' ')+12288812.926194"|bc ` seconds"

剩下的,就是看看为什么内存这么大,触发了 OOM-Killer 了。

/proc/loadavg

系统负载

1
$ cat /proc/loadavg

uptime

包括当前系统时间、系统已运行时间、当前在线用户、平均负载值等。
平均负载值指的是最近 1/5/15 分钟的系统压力情况,负载值越低越好,尽量不要长期超过 1。

1
2
$ uptime
05:43:50 up 16 min, 2 users, load average: 0.00, 0.07, 0.13

配合 watch 命令来每秒刷新一次来获得当前系统负载情况:

1
watch -n 1 uptime

vmstat

TODO

dmidecode

TODO

参考

  1. 温度 sensors
  2. Linux 性能检测常用的 10 个基本命令

Eureka 的目标

原来:负载均衡器会根据配好的 IP 和主机名来进行负载均衡,但是对 AWS cloud 这样体量的系统来说,因为服务实例宕机恢复十分频繁,所以负载均衡器还会有一个更复杂的注册 / 注销服务的机制。
现在:Eureka 在中间层提供一种负载均衡的可能。

服务发现比较

Feature Consul zookeeper etcd euerka
服务健康检查 服务状态,内存,硬盘等 (弱)长连接,keepalive 连接心跳 可配支持
多数据中心 支持
kv存储服务 支持 支持 支持
一致性 raft paxos(zab) raft
cap ca cp cp ap
使用接口(多语言能力) 支持http和dns 客户端 http/grpc http(sidecar)
watch支持 全量/支持long polling 支持 支持 long polling 支持 long polling/大部分增量
自身监控 metrics metrics metrics
安全 acl /https acl https支持(弱)
spring cloud集成 已支持 已支持 已支持 已支持

纵向(功能)比较

服务的健康检查

Euraka 使用时需要显式配置健康检查支持;Zookeeper,Etcd 则在失去了和服务进程的连接情况下任务不健康,而 Consul 相对更为详细点,比如内存是否已使用了90%,文件系统的空间是不是快不足了。

多数据中心支持

Consul 通过 WAN 的 Gossip 协议,完成跨数据中心的同步;而且其他的产品则需要额外的开发工作来实现。

KV 存储服务

除了 Eureka ,其他几款都能够对外支持 k-v 的存储服务,所以后面会讲到这几款产品追求高一致性的重要原因。而提供存储服务,也能够较好的转化为动态配置服务哦。

产品设计中 CAP 理论的取舍(这一段感觉是瞎说的,博客下面评论产生很多争议)

Eureka 典型的 AP,作为分布式场景下的服务发现的产品较为合适,服务发现场景的可用性优先级较高,一致性并不是特别致命。其次 CA 类型的场景 Consul,也能提供较高的可用性,并能 k-v store 服务保证一致性。 而Zookeeper,Etcd则是CP类型 牺牲可用性,在服务发现场景并没太大优势。

多语言能力与对外提供服务的接入协议

Zookeeper的跨语言支持较弱,其他几款支持 http11 提供接入的可能。Euraka 一般通过 sidecar的方式提供多语言客户端的接入支持。Etcd 还提供了Grpc的支持。 Consul除了标准的Rest服务api,还提供了DNS的支持。

Watch的支持(客户端观察到服务提供者变化)

Zookeeper 支持服务器端推送变化,Eureka 2.0(正在开发中)也计划支持。 Eureka 1,Consul,Etcd则都通过长轮询的方式来实现变化的感知。

自身集群的监控

除了 Zookeeper ,其他几款都默认支持 metrics,运维者可以搜集并报警这些度量信息达到监控目的。

安全

Consul,Zookeeper 支持ACL,另外 Consul,Etcd 支持安全通道https。

Spring Cloud的集成

目前都有相对应的 boot starter,提供了集成能力。
总的来看,目前Consul 自身功能,和 spring cloud 对其集成的支持都相对较为完善,而且运维的复杂度较为简单(没有详细列出讨论),Eureka 设计上比较符合场景,但还需持续的完善。

Eureka VS ZooKeeper

  • Eureka 能提供 REST 接口来动态调整配置、renewals、expiration、cancel 等;
  • Eureka 倾向于高可用,而不是 ZooKeeper 的高一致性。
  • Eureka 可以集成到应用中,ZooKeeper 只能作为一个外部组件提供服务,这会增加复杂性、增加系统崩溃的几率。

组成部分

Eureka组件结构

  • 负载均衡:Eureka Client 提供最简单的轮询负载均衡策略,可以封装 Eureka 并根据更多的因素(流量、资源使用、异常发生频次等)来提供一种更好的弹性伸缩特性。
  • 分区:每个 Region 有一个 Eureka 集群用于处理该区域服务失败的情况,各 Region 之间是不会互相通信的。
  • 服务注册到 Eureka Server 后每 30 秒发送一次心跳(heartbeats)来刷新租约(lease),如果网络出现分区或者 Eureka 宕机了,这种心跳自然会停止,如果达到了Renews threshold(即 Server 期望在每分钟中收到的心跳次数,需要考虑是否禁用服务器的自注册、Server/Client 数量等,暂时取默认值 85%就好),Eureka Server 就会将其从服务注册表中移除。
  • 服务注册信息会自动同步到整个 Eureka Server 集群,这也意味着它们是对等的 P2P 集群。
  • 集成到业务服务中的 Eureka Client 可以查询服务注册信息(默认每 30 秒一次)来定位服务及进行远程调用。

服务状态机

Eureka实例状态机

  • STARTING:启动中的状态,应用可以在这个阶段做一些初始化工作
  • UP:可以正常进行通信;
  • DOWN:心跳停了,一般是宕机了或者网络出现了分区
  • OUT_OF_SERVICE:因为某些特殊原因无法提供服务,比如 Elasticsearch 因为没有达到最小可用分片数,或者由于蓝绿发布的需要,新版本如果发布后有问题可以直接将实例状态置为 OUT_OF_SERVICE 来达到回滚的目的。
  • UNKNOWN:WTF?

Client 与 Server 间的交互

Register

Eureka Client 将信息注册到 Eureka Server,注册过程发生在第一次心跳时(在 30 秒后)。

Unregister

正常情况下,Client 必须显式调用 Unregister 来释放自己的注册信息,除非是由于”unclean termination”而导致心跳丢失超过 3 次。

Renew

客户端每30秒通过发送一次心跳(heartbeats)来续约(renewal),心跳告知Eureka Server本实例仍然存活,如果Server在90秒内没有收到续约请求,它将从服务注册表中移除该实例。

Fetch Registry

Eureka clients fetches the registry information from the server and csort_bufferhes it locally. After that, the clients use that information to find other services. This information is updated periodically (every 30 seconds) by getting the delta updates between the last fetch cycle and the current one. The delta information is held longer (for about 3 mins) in the server, hence the delta fetches may return the same instances again. The Eureka client automatically handles the duplicate information.

After getting the deltas, Eureka client reconciles the information with the server by comparing the instance counts returned by the server and if the information does not match for some reason, the whole registry information is fetched again. Eureka server caches the compressed payload of the deltas, whole registry and also per application as well as the uncompressed information of the same. The payload also supports both JSON/XML formats. Eureka client gets the information in compressed JSON format using jersey apache client.

Cancel

Eureka client sends a cancel request to Eureka server on shutdown. This removes the instance from the server’s instance registry thereby effectively taking the instance out of traffic.

This is done when the Eureka client shuts down and the application should make sure to call the following during its shutdown.
DiscoveryManager.getInstance().shutdownComponent()

Time Lag

All operations from Eureka client may take some time to reflect in the Eureka servers and subsequently in other Eureka clients. This is because of the caching of the payload on the eureka server which is refreshed periodically to reflect new information. Eureka clients also fetch deltas periodically. Hence, it may take up to 2 mins for changes to propagate to all Eureka clients.

Communication mechanism

Eureka Client默认使用Jersey发送基于Jackson封装的JSON数据包给Eureka Server。

通信协议

Eureka 不限制通信协议,Thrift、HTTP(S)等均可。

高可用

Eureka Client 的高可用设计:

  • Client 中有服务注册表的缓存,即使所有 Server 都挂掉了,Client 还是能继续工作。
  • 刚开始,Eureka Client 会尝试与同一 zone(可视为同一局域网)中的 Eureka Server 交互,如果交互出现问题或同一 zone 中没有可用的 Eureka Server,则它将转向其他 zone。

Eureka Server 的高可用设计:

  • 启动 Server 时从邻居节点获取注册信息,一个不行换另一个,直到获取成功,如果从邻居节点均无法获取到注册信息,则它会等待几分钟(默认 5 分钟)让 Client 注册它们的信息
    Server 之间获取服务注册信息的机制和 Client 从 Server 获取的一样。
    获取成功后,Server 会设置Renewal Threshold并开始接收 Client 的心跳;
  • 保护模式:如果Renews(last min)(上一分钟内收到的心跳次数)达到了Renews threshold(Server 期望在每分钟中收到的心跳次数,一般是 3),或者过去 15 分钟内的统计数据小于eureka.server.renewalPercentThreshold(renews / renews threshold 的比值,默认为 0.85,当在 15 分钟内微服务心跳数低于 85%,则 Server 会进入自我保护状态,在这种情况下 Server 不会删除注册信息),则进入保护模式,自我保护状态其实是为了防止突发网络不稳定或断电时微服务心跳数剧减,导致微服务注册信息被大量删除的情况。
    在保护模式下,Client 可能从 Server 得到已经不可用的 IP(服务器已不存在或因某些原因无法响应),因此 Client 必须保证这种情况下的弹性高可用,比如快速地超时并重试其他服务器。
  • 退出保护模式:在保护模式下,Eureka Server 会停止移除服务注册信息,直到满足如下条件中的任意之一:
    1. 心跳Renews达到了Renews threshold
    2. 保护模式被禁用,设置eureka.server.enableSelfPreservation=false
  • 孤儿 Server:当发生网络分区,一些 Eureka Server 可能会成为orphaned server,一些 Client 会注册到这些 Server 上,导致一些 Client 能看到这些注册信息而其他的一些则不能。
    当网络恢复后,Server 的 P2P 集群能正常地交互,注册信息会被自动同步到所有 Server 上。

异常情况

比如在测试环境中出现:

1
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

解决办法:

  • 在生产上可以开自注册,部署两个 server
  • 在本机器上测试的时候,可以把比值调低,比如 0.49
  • 或者简单粗暴把自我保护模式关闭:eureka.server.enableSelfPreservation=false

Eureka 配置

配置:
Configuring Eureka
Eureka Server 开放的 REST 接口提供动态配置功能:
Eureka REST operations

添加自定义元数据

静态设置:

1
eureka.metadata.mykey=myvalue

设置后,相当于将mykey:myvalue添加到 eureka 的metadata map中。
动态设置:
需要提供一个自定义的

获取:

1
String myValue = instanceInfo.getMetadata().get("myKey");

源码

原生客户端的执行过程

EurekaClient

通过 DI(依赖注入)使用 EurekaClient

ExampleEurekaGovernatedService

配置

DefaultEurekaClientConfig extends EurekaClientConfig
EurekaServerConfig extends DefaultEurekaServerConfig
CloudInstanceConfig extends PropertiesInstanceConfig
MyDataCenterInstanceConfig extends PropertiesInstanceConfig

To dynamically do this, you will need to first provide your own custom implementation of the EurekaInstanceConfig interface. You can then overload the public Map<String, String> getMetadataMap() method to return a metadata map that contains the desired metadata values. See PropertiesInstanceConfig for an example implementation that provides the configuration based system above.

参考

  1. Netflix/eureka

网关 - Zuul

Filter

Zuul组件结构
Zuul 基于 Netty 开发,使用 filters 包含了核心业务逻辑,Filter 是使用 Groovy 写的,主要是为了提供动态编译加载的能力,filters 主要包含了三类:

  • Inbound Filters execute before routing to the origin and can be used for things like authentication, dynamic routing, rate limiting, DDoS protection, metrics and decorating the request.
  • Endpoint Filters can be used to return static responses, otherwise the built-in ProxyEndpoint filter will route the request to the origin.
  • Outbound Filters execute after getting the response from the origin and can be used for metrics, decorating the response to the user or adding custom headers.
  • Async
    Filter 可以被同步执行或异步执行。
    如果 Filter 没有做太重的工作,可以通过继承HttpInboundSyncFilterHttpOutboundSyncFilter来实现一种同步 Filter,例子见Zuul 源码中的Routes.groovy
    反之,如果需要从其他服务、缓存获取数据,或做一些复杂的计算工作,则最好继承HttpInboundFilterHttpOutboundFilter,例子见Zuul 源码中的SampleServiceFilter.groovy

Filter属性

Type: most often defines the stage during the routing flow when the Filter will be applied (although it can be any custom string)
Async: define if the filter is sync or async, generally meaning do you need to make an external call or just doing work on-box
Execution Order: applied within the Type, defines the order of execution across multiple Filters
Criteria: the conditions required in order for the Filter to be executed
Action: the action to be executed if the Criteria is met

其他的一些例子

这些例子是zuul-sample中的代码。

  • DebugRequest - look for a query param to add extra debug logging for a request
  • Healthcheck - simple static endpoint filter that returns 200, if everything is bootstrapped correctly
  • ZuulResponseFilter - add informational headers to provide extra details on routing, request execution, status and error cause
  • GZipResponseFilter - can be enabled to gzip outbound responses
  • SurgicalDebugFilter - can be enabled to route specific requests to different hosts for debugging

缓存请求体

默认情况下 Zuul 不会缓存请求体,因为 Filter 一般用到请求头就够了,但是如果需要在 inbound 中用到请求头或在 outbound 中用到响应头,则需要明确指定 Zuul 缓存,可以重写 Filter 的needsBodyBuffered()

1
2
3
4
@Override
boolean needsBodyBuffered(HttpResponseMessage input) {
return true
}

网络协议

Zuul 支持修改暴露服务时使用的协议,使用方法见 sample 项目中的SampleServerStartup

其他功能

Core Features

Push Messaging

Push Messaging 机制可以支持从 Server 端推送消息到 Client 端,支持两种协议:WebSocketsServer Sent Events (SSE)
Push Messaging
TODO

负载均衡 - Ribbon

使用

原生 API 如何使用见:Netflix / ribbon - Getting Started
如果是搭配 Spring Boot,可以参考 Spring Could 文档。

组件结构及实现

Rule

a logic component to determine which server to return from a list

  • RoundRobinRule
    简单的轮询策略
  • AvailabilityFilteringRule
    这个 Rule 会跳过那些疑似“电路跳闸”或并发连接数已经很高的服务器。
    比如客户端的最后 3 次连接失败,客户端会认为该服务实例已经出现了类似“电路跳闸”的问题而导致无法提供服务,于是在接下来的 30 秒内均保持这种状态,如果之后还是连接失败,这个等待时间会指数增长(1min、2min、4min…)。
  • WeightedResponseTimeRule
    每个 Server 会根据其平均响应时间计算出一个权重,响应时间越长、比重越小,该 Rule 选择 Server 时会根据该权重来计算概率。

Ping

a component running in background to ensure liveness of servers

ServerList

this can be static or dynamic. If it is dynamic (as used by DynamicServerListLoadBalancer), a background thread will refresh and filter the list at certain interval

  • 静态的 Server 列表
    可以在程序里写一个静态列表,将该列表设置到BaseLoadBalancer.setServerList()中。
  • ConfigurationBasedServerList
    默认的 ServerList 实现,可以通过 Archaius ConfigurationManager来设置 Server 列表。
  • DiscoveryEnabledNIWSServerList
    可以通过 Eureka Client 获取服务器列表,服务器集群必须通过 VipAddress 来定义:
    1
    2
    3
    myClient.ribbon.NIWSServerListClassName=com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList 
    # the server must register itself with Eureka server with VipAddress "myservice"
    myClient.ribbon.DeploymentContextBasedVipAddresses=myservice

ServerListFilter

ServerListFilter 是DynamicServerListLoadBalancer的组件,用于过滤从ServerList返回的服务器列表,现在有两种实现:

  • ZoneAffinityServerListFilter
    过滤掉不在同一个 zone 内的服务器,除非 zone 内没有可用的服务器,这个 Filter 可以通过设置如下属性来启用(假设客户端名为 myclient、客户端的属性空间为 ribbon):
    1
    myclient.ribbon.EnableZoneAffinity=true
  • ServerListSubsetFilter
    可以保证客户端只能看到ServerList返回的全体服务器的一个固定子集,如果有服务器可用性较弱,则可以定期用新服务器替换老服务器。可以通过设置以下属性启用该Filter:
    1
    2
    3
    4
    5
    6
    myClient.ribbon.NIWSServerListClassName=com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList 
    # the server must register itself with Eureka server with VipAddress "myservice"
    myClient.ribbon.DeploymentContextBasedVipAddresses=myservice
    myClient.ribbon.NIWSServerListFilterClassName=com.netflix.loadbalancer.ServerListSubsetFilter
    # only show client 5 servers. default is 20.
    myClient.ribbon.ServerListSubsetFilter.size=5

源码

com.netflix.loadbalancer.DynamicServerListLoadBalancer#updateListOfServers
com.netflix.loadbalancer.ServerList#getUpdatedListOfServers
com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList#obtainServersViaDiscovery
TODO

参考

常见实现

  1. Netflix/zuul
  2. Netflix/ribbon
  3. Netflix / Turbine

虚拟化技术将一台服务器虚拟出多个虚拟机来提供服务。
虚拟化技术包括计算虚拟化(服务器虚拟化)、存储虚拟化、网络虚拟化等。
在实际讲解虚拟化之前,我们需要先解释一下隔离技术,在隔离的基础上我们才能任意粒度、自由地分配资源。

阅读全文 »
0%