背景
简单来说,一个租户就是一个公司的客户,多个租户共用同一个SaaS系统,一旦SaaS系统不可用,那么所有的租户都不可用,
多租户问题,其实是一种架构设计方式,就是在一台或者一组服务器上运行的SaaS系统,可以为多个租户(客户)提供服务,目的是为了让多个租户在互联网环境下使用同一套程序,且保证租户间的数据隔离。从这种架构设计的模式上,不难看出来,多租户架构的重点就是同一套程序下多个租户数据的隔离。由于租户数据是集中存储的,所以要实现数据的安全性,就是看能否实现对租户数据的隔离,防止租户数据不经意或被他人恶意地获取和篡改。
多租户数据隔离架构设计
目前SaaS多租户系统的数据隔离有三种架构设计,即:
- 为每个租户提供独立的数据库
- 为每个租户提供独立的表空间
- 按字段区分租户
每种方案都有其各自的适用情况。
一个租户一个数据库
一个租户独立使用一个数据库,那就意味着SaaS系统要连接多个数据库,这种实现方案其实和分库分表架构设计是一样的,好处就是数据隔离级别高、安全性好,毕竟一个租户单用一个数据库,但是物理硬件成本、维护成本也变高了。
独立的表空间
所有租户共用一个数据库系统,但是每个租户在数据库系统中拥有一个独立的表空间。
按租户id字段隔离租户
这种方案是多租户方案中最简单的数据隔离方法,即在每张表中都添加一个用于区分租户的字段(如tenant_id或org_id啥的)来标识每条数据属于哪个租户,当进行查询的时候每条语句都要添加该字段作为过滤条件,其特点是所有租户的数据全都存放在同一个表中,数据的隔离性是最低的,完全是通过字段来区分的,很容易把数据搞串或者误操作。
三种数据隔离架构设计的对比如下:
隔离方案 | 成本 | 支持租户数量 | 优点 | 缺点 |
---|---|---|---|---|
独立数据库系统 | 高 | 少 | 数据隔离级别高,安全性,可以针对单个租户开发个性化需求 | 数据库独立安装,物理成本和维护成本都比较高 |
独立的表空间 | 中 | 较多 | 提供了一定程度的逻辑数据隔离,一个数据库系统可支持多个租户 | 数据库管理比较困难,表繁多,同时数据修复稍复杂 |
按租户id字段区分 | 低 | 多 | 维护和购置成本最低,每个数据库能够支持的租户数量最多 | 隔离级别最低,安全性也最低 |
Linux kill
用途:kill – terminate or signal a process
kill 是向进程发送信号的命令。当然我们可以向进程发送一个终止运行的信号,此时的 kill 命令才是名至实归。事实上如果我们不给 kill 命令传递信号参数,它默认传递终止进程运行的信号给进程!这是 kill 命令最主要的用法,也是本文要介绍的内容。
一般情况下,终止一个前台进程使用 Ctrl + C 就可以了。对于一个后台进程就须用 kill 命令来终止。我们会先使用 ps、top 等命令获得进程的 PID,然后使用 kill 命令来杀掉该进程。
kill命令格式
kill [options] <pid> [...]
<pid> […] : 把信号发送给列出的所有进程。
options :
-<signal> : 指定发送给进程的信号,指定信号的名称或号码都可以。
-l : 列出所有信号的名称和号码。
有哪些信号可以发送给进程
[root@centos ~]# kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
这些信号中只有第9中信号(SIGKILL)才可以无条件的终止进程,其他信号进程都有权忽略。
k8s网络模型设计的一个基础原则是:每个Pod都拥有一个独立的IP地址,而且假定所有Pod都在一个可以直接连通的、扁平的网络空间中。
所以不管他们是否运行在同一个Node(宿主机中),都要求他们可以直接通过对方的IP进行访问。 设计这个原则的原因是,用户不需要额外考虑如何建立Pod之间的连接,也不需要考虑将容器端口映射到主机端口等问题。
Tips
- 查看一个网卡是否开启了混杂模式
[root@centos ~]# ifconfig eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,PROMISC,MULTICAST> mtu 1500
inet 172.17.36.2 netmask 255.255.240.0 broadcast 172.17.47.255
ether 00:16:3e:03:f4:76 txqueuelen 1000 (Ethernet)
当输出包含PROMISC时,表明该网络接口处于混杂模式。
ifconfig eth0 promisc # 启用网卡的混杂模式
ifconfig eth0 -promisc # 关闭网卡的混杂模式
将网络设备加入Linux bridge后,会自动进入混杂模式。
ConfigMap
使用ConfigMap 的限制条件如下。
-
ConfigMap 必须在Pod 之前创建。
-
ConfigMap 受Namespace 限制,只有处于相同Namespaces 中的Pod 可以引用它。
-
ConfigMap 中的配额管理还未能实现。
-
kubelet 只支持可以被API Server 管理的Pod 使用ConfigMap 。kubelet 在本Node 上通过
--manifest-url
或--config
自动创建的静态Pod将无法引用ConfigMap -
在Pod 对ConfigMap 进行挂载( volumeMount )操作时,容器内部只能挂载为“目录”,无法挂载为“文件”。在挂载到容器内部后,目录中将包含Co nfigMap 定义的每个item,如果该目录下原来还有其他文件,则容器内的该目录将会被挂载的ConfigMap 覆盖。如果应用程序需要保留原来的其他文件,则需要进行额外的处理。可以将ConfigMap挂载到容器内部的临时目录,再通过启动脚本将配置文件复制或者链接到( cp 或link命令)应用所用的实际配置目录下。
k8s核心原理
API Server
总体来看, Kubemetes API Server 的核心功能是提供了Kubemetes 各类资源对象(如Pod 、RC 、Service 等〉的增、删、改、查及Watch 等HTTP Rest 接口,成为集群内各个功能模块之间数据交互和通信的中心枢纽,是整个系统的数据总线和数据中心。 除此之外,它还有以下一些功能特性。
- 是集群管理的API入口。
- 是资源配额控制的入口。
- 提供了完备的集群安全机制。
Pod 生命周期
Pod 在整个生命周期过程中被系统定义为各种状态 Pod 的状态如表2.14 所示。 表2.14 Pod 的状态
状态值 | 描述 |
---|---|
Pending | API Server已经创建该Pod,但Pod内还有一个或多个容器的镜像没有创建,包括正在下载镜像的过程 |
Running | Pod 内所有容器均己创建,且至少有一个容器处于运行状态、正在启动状态或正在重启状态 |
Succeeded | Pod 内所有容器均成功执行退出, 且不会再重启 |
Failed | Pod 内所有容器均已退出,但至少有一个容器退出为失败状态 |
Unknown | 由于某种原因无法获取该Pod 的状态, 可能由于网络通信不畅导致 |
Pod重启策略
Pod 的重启策略( RestartPolicy )应用于Pod 内的所有容器,井且仅在Pod 所处的Node上由kubelet 进行判断和重启操作。当某个容器异常退出或者健康检查(详见下节)失败时, kubelet将根据RestartPolicy 的设置来进行相应的操作。 Pod 的重启策略包括Always、OnFailure和Never, 默认值为Always:
- Always:当容器失效时,由kubelet自动重启该容器。
- OnFailure:当容器终止运行且退出码不为0时,由kubelet自动重启该容器。
- Never :不论容器运行状态如何, kubelet 都不会重启该容器。
kubelet 重启失效容器的时间间隔以sync-frequency 乘以2n 来计算;例如1、2 、4 、8 倍等, 最长延时5min ,并且在成功重启后的10min后重置该时间。
Pod健康检查
可以通过两类探针来检查: LivenessProbe 和ReadinessProbe
- LivenessProbe 探针:用于判断容器是否存活( running 状态),如果LivenessProbe 探针探测到容器不健康,则kubelet 将杀掉该容器,并根据容器的重启策略做相应的处理。如果一个容器不包含LivenessProbe 探针,那么kubelet 认为该容器的LivenessProbe 探针返回的值永远是“ Success"
- ReadinessProbe 探针:用于判断容器是否启动完成( ready 状态),可以接收请求。如果ReadinessProbe 探针检测到失败,则Pod 的状态将被修改。Endpoint Con位oiler 将从Service 的Endpoint 中删除包含该容器所在Pod 的Endpoint 。
对于每种探测方式,都需要设置initialDelaySeconds和timeoutSeconds两个参数,它们的含 义分别如下。
Service
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
解析crontab表达式的网站:https://crontab.guru/
Cron是什么?
简单来讲,cron是基于Unix的系统上的一个实用程序。它使用户能够安排任务在指定的【日期/时间】定期运行。它自然是一个伟大的工具,可以自动运行大量流程,不需要人工干预。
Cron作为守护进程运行。这意味着它只需要启动一次,并将在后台继续运行。此过程使用crontab读取计划条目并启动任务。
随着时间的推移,cron表达式格式被广泛采用,许多其他程序和库也使用它。
Cron表达式
既然使用定时任务,那必定离不开定时表达式,其命名规则有如下部分组成:
<minute> <hour> <day-of-month> <month> <day-of-week> <command>
其中每一个域可出现的字符如下。
- minute:可出现
,.*/
这4 个字符,有效范围为0-59的整数。 - hour:可出现
,.*/
这4 个字符,有效范围为0-23整数。 - day-of-month:可出现
,.*/?LWC
这8个字符,有效范围为0-31的整数。 - month :可出现
,.*/
这4个字符,有效范围为1-12的整数或JAN-DEC 。 - day-of-week:可出现
,.*/?LC#
这8个字符,有效范围为1-7的整数或SUN-SAT两个范围。1表示星期天,2表示星期一,以此类推。
特殊字符
(1)*
:表示匹配该域的任意值。假如在Minutes域使用*, 即表示每分钟都会触发事件。
(2)?
:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。
(3)-
:表示范围。例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次
(4)/
:表示起始时间开始触发,然后每隔固定时间触发一次。例如在Minutes域使用5/20,则意味着第一次触发是在第5分钟时,接下来每20分钟触发一次,将在第25,45时刻等分别触发一次.
(5),
:表示列出枚举值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。
(6)L
:表示最后,只能出现在DayofWeek和DayofMonth域。如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。
(7)W
:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份 。
(8)LW
:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。
(9)#
:用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。
常见例子
# 每天凌晨1点执行
0 1 * * * /usr/local/test.sh
# 每10分钟执行
*/10 * * * * /usr/local/test.sh
# 每天晚上21:30执行一次
30 21 * * * /usr/local/test.sh
# 每个月1号、10号、22号凌晨4:45执行
45 4 1,10,22 * * /usr/local/test.sh
# 每天18点至23点之间,每隔30分钟执行
0,30 18-23 * * * /usr/local/test.sh
参考:https://blog.csdn.net/IndexMan/article/details/120801069
ASCII码,使用7位二进制数,表示128个标准ASCII字符,使用8位二进制数,表示256 个标准及扩展ASCII字符;
ASCII编码字符分类:
控制字符:0~32、127表示,共33个,如CR(回车)、LF(换行)、FF(换页)、BS(退格)、DEL(删除)、Space(空格)等。
特殊符号:33-47表示,如+(加)、-(减)、*(乘)、/(除)、!(感叹号)。
数字:48~57表示,0-9阿拉伯数字。
字母:65~90为26个大写英文字母,97~122号为26个小写英文字母。
DEC | OCT | HEX | BIN | 缩写/符号 | HTML实体 | 描述 |
---|---|---|---|---|---|---|
0 | 000 | 00 | 00000000 | NUL | � | Null char (空字符) |
1 | 001 | 01 | 00000001 | SOH | � | Start of Heading (标题开始) |
2 | 002 | 02 | 00000010 | STX | � | Start of Text (正文开始) |
3 | 003 | 03 | 00000011 | ETX | � | End of Text (正文结束) |
4 | 004 | 04 | 00000100 | EOT | � | End of Transmission (传输结束) |
5 | 005 | 05 | 00000101 | ENQ | � | Enquiry (请求) |
6 | 006 | 06 | 00000110 | ACK | � | Acknowledgment (收到通知) |
7 | 007 | 07 | 00000111 | BEL | � | Bell (响铃) |
8 | 010 | 08 | 00001000 | BS | � | Back Space (退格) |
9 | 011 | 09 | 00001001 | HT | Horizontal Tab (水平制表符) | |
10 | 012 | 0A | 00001010 | LF | Line Feed (换行键) | |
11 | 013 | 0B | 00001011 | VT | � | Vertical Tab (垂直制表符) |
12 | 014 | 0C | 00001100 | FF | Form Feed (换页键) | |
13 | 015 | 0D | 00001101 | CR | Carriage Return (回车键) | |
14 | 016 | 0E | 00001110 | SO | � | Shift Out / X-On (不用切换) |
15 | 017 | 0F | 00001111 | SI | � | Shift In / X-Off (启用切换) |
16 | 020 | 10 | 00010000 | DLE | � | Data Line Escape (数据链路转义) |
17 | 021 | 11 | 00010001 | DC1 | � | Device Control 1 (设备控制1) |
18 | 022 | 12 | 00010010 | DC2 | � | Device Control 2 (设备控制2) |
19 | 023 | 13 | 00010011 | DC3 | � | Device Control 3 (设备控制3) |
20 | 024 | 14 | 00010100 | DC4 | � | Device Control 4 (设备控制4) |
21 | 025 | 15 | 00010101 | NAK | � | Negative Acknowledgement (拒绝接收) |
22 | 026 | 16 | 00010110 | SYN | � | Synchronous Idle (同步空闲) |
23 | 027 | 17 | 00010111 | ETB | � | End of Transmit Block (传输块结束) |
24 | 030 | 18 | 00011000 | CAN | � | Cancel (取消) |
25 | 031 | 19 | 00011001 | EM | � | End of Medium (介质中断) |
26 | 032 | 1A | 00011010 | SUB | � | Substitute (替补) |
27 | 033 | 1B | 00011011 | ESC | � | Escape (溢出) |
28 | 034 | 1C | 00011100 | FS | � | File Separator (文件分割符) |
29 | 035 | 1D | 00011101 | GS | � | Group Separator (分组符) |
30 | 036 | 1E | 00011110 | RS | � | Record Separator (记录分离符) |
31 | 037 | 1F | 00011111 | US | � | Unit Separator (单元分隔符) |
32 | 040 | 20 | 00100000 | Space (空格) | ||
33 | 041 | 21 | 00100001 | ! | ! | Exclamation mark |
34 | 042 | 22 | 00100010 | " | " | Double quotes |
35 | 043 | 23 | 00100011 | # | # | Number |
36 | 044 | 24 | 00100100 | $ | $ | Dollar |
37 | 045 | 25 | 00100101 | % | % | Procenttecken |
38 | 046 | 26 | 00100110 | & | & | Ampersand |
39 | 047 | 27 | 00100111 | ’ | ' | Single quote |
40 | 050 | 28 | 00101000 | ( | ( | Open parenthesis\ |
41 | 051 | 29 | 00101001 | ) | ) | Close parenthesis |
42 | 052 | 2A | 00101010 | * | * | Asterisk |
43 | 053 | 2B | 00101011 | + | + | Plus |
44 | 054 | 2C | 00101100 | , | , | Comma |
45 | 055 | 2D | 00101101 | - | - | Hyphen |
46 | 056 | 2E | 00101110 | . | . | Period, dot or full stop |
47 | 057 | 2F | 00101111 | / | / | Slash or divide |
48 | 060 | 30 | 00110000 | 0 | 0 | Zero |
49 | 061 | 31 | 00110001 | 1 | 1 | One |
50 | 062 | 32 | 00110010 | 2 | 2 | Two |
51 | 063 | 33 | 00110011 | 3 | 3 | Three |
52 | 064 | 34 | 00110100 | 4 | 4 | Four |
53 | 065 | 35 | 00110101 | 5 | 5 | Five |
54 | 066 | 36 | 00110110 | 6 | 6 | Six |
55 | 067 | 37 | 00110111 | 7 | 7 | Seven |
56 | 070 | 38 | 00111000 | 8 | 8 | Eight |
57 | 071 | 39 | 00111001 | 9 | 9 | Nine |
58 | 072 | 3A | 00111010 | : | : | Colon |
59 | 073 | 3B | 00111011 | ; | ; | Semicolon |
60 | 074 | 3C | 00111100 | < | < | Less than |
61 | 075 | 3D | 00111101 | = | = | Equals |
62 | 076 | 3E | 00111110 | > | > | Greater than |
63 | 077 | 3F | 00111111 | ? | ? | Question mark |
64 | 100 | 40 | 01000000 | @ | @ | At symbol |
65 | 101 | 41 | 01000001 | A | A | Uppercase A |
66 | 102 | 42 | 01000010 | B | B | Uppercase B |
67 | 103 | 43 | 01000011 | C | C | Uppercase C |
68 | 104 | 44 | 01000100 | D | D | Uppercase D |
69 | 105 | 45 | 01000101 | E | E | Uppercase E |
70 | 106 | 46 | 01000110 | F | F | Uppercase F |
71 | 107 | 47 | 01000111 | G | G | Uppercase G |
72 | 110 | 48 | 01001000 | H | H | Uppercase H |
73 | 111 | 49 | 01001001 | I | I | Uppercase I |
74 | 112 | 4A | 01001010 | J | J | Uppercase J |
75 | 113 | 4B | 01001011 | K | K | Uppercase K |
76 | 114 | 4C | 01001100 | L | L | Uppercase L |
77 | 115 | 4D | 01001101 | M | M | Uppercase M |
78 | 116 | 4E | 01001110 | N | N | Uppercase N |
79 | 117 | 4F | 01001111 | O | O | Uppercase O |
80 | 120 | 50 | 01010000 | P | P | Uppercase P |
81 | 121 | 51 | 01010001 | Q | Q | Uppercase Q |
82 | 122 | 52 | 01010010 | R | R | Uppercase R |
83 | 123 | 53 | 01010011 | S | S | Uppercase S |
84 | 124 | 54 | 01010100 | T | T | Uppercase T |
85 | 125 | 55 | 01010101 | U | U | Uppercase U |
86 | 126 | 56 | 01010110 | V | V | Uppercase V |
87 | 127 | 57 | 01010111 | W | W | Uppercase W |
88 | 130 | 58 | 01011000 | X | X | Uppercase X |
89 | 131 | 59 | 01011001 | Y | Y | Uppercase Y |
90 | 132 | 5A | 01011010 | Z | Z | Uppercase Z |
91 | 133 | 5B | 01011011 | [ | [ | Opening bracket |
92 | 134 | 5C | 01011100 | \|\|Backslash | ||
93 | 135 | 5D | 01011101 | ] | ] | Closing bracket |
94 | 136 | 5E | 01011110 | ^ | ^ | Caret - circumflex |
95 | 137 | 5F | 01011111 | _ | _ | Underscore |
96 | 140 | 60 | 01100000 | ` | ` | Grave accent |
97 | 141 | 61 | 01100001 | a | a | Lowercase a |
98 | 142 | 62 | 01100010 | b | b | Lowercase b |
99 | 143 | 63 | 01100011 | c | c | Lowercase c |
100 | 144 | 64 | 01100100 | d | d | Lowercase d |
101 | 145 | 65 | 01100101 | e | e | Lowercase e |
102 | 146 | 66 | 01100110 | f | f | Lowercase f |
103 | 147 | 67 | 01100111 | g | g | Lowercase g |
104 | 150 | 68 | 01101000 | h | h | Lowercase h |
105 | 151 | 69 | 01101001 | i | i | Lowercase i |
106 | 152 | 6A | 01101010 | j | j | Lowercase j |
107 | 153 | 6B | 01101011 | k | k | Lowercase k |
108 | 154 | 6C | 01101100 | l | l | Lowercase l |
109 | 155 | 6D | 01101101 | m | m | Lowercase m |
110 | 156 | 6E | 01101110 | n | n | Lowercase n |
111 | 157 | 6F | 01101111 | o | o | Lowercase o |
112 | 160 | 70 | 01110000 | p | p | Lowercase p |
113 | 161 | 71 | 01110001 | q | q | Lowercase q |
114 | 162 | 72 | 01110010 | r | r | Lowercase r |
115 | 163 | 73 | 01110011 | s | s | Lowercase s |
116 | 164 | 74 | 01110100 | t | t | Lowercase t |
117 | 165 | 75 | 01110101 | u | u | Lowercase u |
118 | 166 | 76 | 01110110 | v | v | Lowercase v |
119 | 167 | 77 | 01110111 | w | w | Lowercase w |
120 | 170 | 78 | 01111000 | x | x | Lowercase x |
121 | 171 | 79 | 01111001 | y | y | Lowercase y |
122 | 172 | 7A | 01111010 | z | z | Lowercase z |
123 | 173 | 7B | 01111011 | { | { | Opening brace |
124 | 174 | 7C | 01111100 | |||
125 | 175 | 7D | 01111101 | } | } | Closing brace |
126 | 176 | 7E | 01111110 | ~ | ~ | Equivalency sign (tilde) |
127 | 177 | 7F | 01111111 | � | Delete |
———————————————— 版权声明:本文为CSDN博主「火腿肠」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_39511050/article/details/126809454
可理解的共识算法研究
(In Search of an Understandable Consensus Algorithm–Extended Version)
作者:Diego Ongaro and John Ousterhout–Stanford University
摘要
Raft是管理复制日志的共识算法, 比Paxos牛.
1. 介绍
复制状态机,如图1
共识算法管理
2. Raft把共识算法分解为3个独立的子问题:
2.1 Leader选举
当前Leader挂掉后必须进入一个新Leader的选举
集群中每个节点,任意时刻处于Leader, Follower, Candidate这三个角色之一。选举特点如下:
- 当集群初始化时候,每个节点都是Follower角色;
- 集群中存在至多1个有效的主节点,通过心跳与其他节点同步数据;
- 当Follower在一定时间内没有收到来自主节点的心跳,会将自己角色改变为Candidate,并发起一次选主投票;当收到包括自己在内超过半数节点赞成后,选举成功;当收到票数不足半数选举失败,或者选举超时。若本轮未选出主节点,将进行下一轮选举(出现这种情况,是由于多个节点同时选举,所有节点均未获得过半选票)。
- Candidate节点收到来自主节点的信息后,会立即终止选举过程,进入Follower角色。
为了避免陷入选主失败循环,每个节点未收到心跳发起选举的时间是一定范围内的随机值,这样能够避免2个节点同时发起选主。
2.2 日志复制
所谓日志复制,是指Leader将每次操作形成日志条目,并持久化到本地磁盘,然后通过网络IO发送给其他节点。其他节点根据日志的逻辑时钟(TERM)和日志编号(INDEX)来判断是否将该日志记录持久化到本地。 当Leader收到包括自己在内超过半数节点成功返回,那么认为该日志是可提交的(committed),并将日志输入到状态机,将结果返回给客户端。
这里需要注意的是,每次选Leader都会形成一个唯一的TERM编号,相当于逻辑时钟。每一条日志都有全局唯一的编号。
Leader通过网络IO向其他节点追加日志。若某节点收到日志追加的消息,首先判断该日志的TERM是否过期,以及该日志条目的INDEX是否比当前已经提交的日志的INDEX更早。若已过期,或者比提交的日志更早,那么就拒绝追加,并返回该节点当前的已提交的日志的编号。否则,将日志追加,并返回成功。
当Leader收到其他节点关于日志追加的回复后,若发现有拒绝,则根据该节点返回的已提交日志编号,发送其编号下一条日志。
Leader向其他节点同步日志,还作了拥塞控制。具体地说,主节点发现日志复制的目标节点拒绝了某次日志追加消息,将进入日志探测阶段,一条一条发送日志,直到目标节点接受日志,然后进入快速复制阶段,可进行批量日志追加。
按照日志复制的逻辑,我们可以看到,集群中慢节点不影响整个集群的性能。另外一个特点是,数据只从Leader复制到Follower,这样大大简化了逻辑流程。
2.3 安全性
Leader不会删除或复写log entries;只增加新的entries
选主以及日志复制并不能保证节点间数据一致。试想,当一个某个节点挂掉了,一段时间后再次重启,并当选为主节点。而在其挂掉这段时间内,集群若有超过半数节点存活,集群会正常工作,那么会有日志提交。这些提交的日志无法传递给挂掉的节点。当挂掉的节点再次当选主节点,它将缺失部分已提交的日志。在这样场景下,按Raft协议,它将自己日志复制给其他节点,会将集群已经提交的日志给覆盖掉。
这显然是不可接受的。
其他协议解决这个问题的办法是,新当选的主节点会询问其他节点,和自己数据对比,确定出集群已提交数据,然后将缺失的数据同步过来。这个方案有明显缺陷,增加了集群恢复服务的时间(集群在选举阶段不可服务),并且增加了协议的复杂度。
Raft解决的办法是,在选主逻辑中,对能够成为主的节点加以限制,确保选出的节点已定包含了集群已经提交的所有日志。如果新选出的主节点已经包含了集群所有提交的日志,那就不需要从和其他节点比对数据了。简化了流程,缩短了集群恢复服务的时间。
这里存在一个问题,加以这样限制之后,还能否选出主呢?答案是:只要仍然有超过半数节点存活,这样的主一定能够选出。因为已经提交的日志必然被集群中超过半数节点持久化,显然前一个主节点提交的最后一条日志也被集群中大部分节点持久化。当主节点挂掉后,集群中仍有大部分节点存活,那这存活的节点中一定存在一个节点包含了已经提交的日志了。
3. Raft 节点
一般来说,Raft集群节点个数为5个,此时可以容忍两个失效。
3.1 节点角色
Leader
, Follower
, Candidate
,一般来说,只有一个Leader,其余都是Follower。
图4:各节点的状态。
3.1.1 Followers
只响应来自其他节点的请求【只对Leader和Candidate做出响应】,如果没有收到其他节点的信息,则会变成Candidate并且初始化一个选举过程。若该节点收到了客户端的请求,则会重定向至leader节点。
3.1.2 Candidate
用来选举新的Leader。如果收到大多数节点的投票,则会变成Leader。