qeesung

qeesung 查看完整档案

深圳编辑华南理工大学  |  自动化 编辑华为技术有限公司  |  软件工程师 编辑 github.com/qeesung 编辑
编辑

做了过河卒子,只能拼命向前

个人动态

qeesung 收藏了文章 · 5月31日

Raft 算法之集群成员变更

原文地址: https://qeesung.github.io/202...

Raft 集群成员变更

在前面三个章节中,我们介绍了Raft的:

上面的讨论都是基于Raft集群成员恒定不变的,然而在很多时候,集群的节点可能需要进行维护,或者是因为需要扩容,那么就难以避免的需要向Raft集群中添加和删除节点。最简单的方式就是停止整个集群,更改集群的静态配置,然后重新启动集群,但是这样就丧失了集群的可用性,往往是不可取的,所以Raft提供了两种在不停机的情况下,动态的更改集群成员的方式:

  • 单节点成员变更:One Server ConfChange
  • 多节点联合共识:Joint Consensus

动态成员变更存在的问题

在Raft中有一个很重要的安全性保证就是只有一个Leader,如果我们在不加任何限制的情况下,动态的向集群中添加成员,那么就可能导致同一个任期下存在多个Leader的情况,这是非常危险的。

如下图所示,从Cold迁移到Cnew的过程中,因为各个节点收到最新配置的实际不一样,那么肯能导致在同一任期下多个Leader同时存在。

比如图中此时Server3宕机了,然后Server1和Server5同时超时发起选举:

  • Server1:此时Server1中的配置还是Cold,只需要Server1和Server2就能够组成集群的Majority,因此可以被选举为Leader
  • Server5:已经收到Cnew的配置,使用Cnew的配置,此时只需要Server3,Server4,Server5就可以组成集群的Majority,因为可以被选举为Leader

换句话说,以Cold和Cnew作为配置的节点在同一任期下可以分别选出Leader。

raft-multi-leader.png

所以为了解决上面的问题,在集群成员变更的时候需要作出一些限定。

单节点成员变更

所谓单节点成员变更,就是每次只想集群中添加或移除一个节点。比如说以前集群中存在三个节点,现在需要将集群拓展为五个节点,那么就需要一个一个节点的添加,而不是一次添加两个节点。

这个为什么安全呢?很容易枚举出所有情况,原有集群奇偶数节点情况下,分别添加和删除一个节点。在下图中可以看出,如果每次只增加和删除一个节点,那么Cold的Majority和Cnew的Majority之间一定存在交集,也就说是在同一个Term中,Cold和Cnew中交集的那一个节点只会进行一次投票,要么投票给Cold,要么投票给Cnew,这样就避免了同一Term下出现两个Leader。
raft-single-server.png

变更的流程如下:

  1. 向Leader提交一个成员变更请求,请求的内容为服务节点的是添加还是移除,以及服务节点的地址信息
  2. Leader在收到请求以后,回向日志中追加一条ConfChange的日志,其中包含了Cnew,后续这些日志会随着AppendEntries的RPC同步所有的Follower节点中
  3. ConfChange的日志被添加到日志中是立即生效(注意:不是等到提交以后才生效)
  4. ConfChange的日志被复制到Cnew的Majority服务器上时,那么就可以对日志进行提交了

以上就是整个单节点的变更流程,在日志被提交以后,那么就可以:

  1. 马上响应客户端,变更已经完成
  2. 如果变更过程中移除了服务器,那么服务器可以关机了
  3. 可以开始下一轮的成员变更了,注意在上一次变更没有结束之前,是不允许开始下一次变更的

可用性

可用性问题

在我们向集群添加或者删除一个节点以后,可能会导致服务的不可用,比如向一个有三个节点的集群中添加一个干净的,没有任何日志的新节点,在添加节点以后,原集群中的一个Follower宕机了,那么此时集群中还有三个节点可用,满足Majority,但是因为其中新加入的节点是干净的,没有任何日志的节点,需要花时间追赶最新的日志,所以在新节点追赶日志期间,整个服务是不可用的。

在接下来的子章节中,我们将会讨论三个服务的可用性问题:

  • 追赶新的服务器
  • 移除当前的Leader
  • 中断服务器

追赶新的服务器

在添加服务器以后,如果新的服务器需要花很长时间来追赶日志,那么这段时间内服务不可用。

如下图所示:

  • 左图:向集群中添加新的服务器S4以后,S3宕机了,那么此时因为S4需要追赶日志,此时不可用
  • 右图:向集群中添加多个服务器,那么添加以后Majority肯定是包含新的服务器的,那么此时S4,S5,S6需要追赶日志,肯定也是不可用的

raft-catch-up-server.png

新加入集群中的节点可能并不是因为需要追赶大量的日志而不可用,也有可能是因为网络不通,或者是网速太慢,导致需要花很长的时间追赶日志。

在Raft中提供了两种解决方案:

  • 在集群中加入新的角色LeanerLeaner只对集群的日志进行复制,并不参加投票和提交决定,在需要添加新节点的情况下,添加Leaner即可。
  • 加入一个新的Phase,这个阶段会在固定的Rounds(比如10)内尝试追赶日志,最后一轮追赶日志的时间如果小于ElectionTimeout, 那么说明追赶上了,否则就抛出异常

下面我们就详细讨论一下第二种方案。

在固定Rounds内追赶日志

如果需要添加的新的节点在很短时间内可以追赶上最新的日志,那么就可以将该节点添加到集群中。那要怎么判断这个新的节点是否可以很快时间内追赶上最新的日志呢?

Raft提供了一种方法,在配置变更之前引入一个新的阶段,这个阶段会分为多个Rounds(比如10)向Leader同步日志,如果新节点能够正常的同步日志,那么每一轮的日志同步时间都将缩短,如果在最后一轮Round同步日志消耗的时间小于ElectionTimeout,那么说明新节点的日志和Leader的日志已经足够接近,可以将新节点加入到集群中。但是如果最后一轮的Round的日志同步时间大于ElectionTimeout,就应该立即终止成员变更。

raft-replicate-round.png

移除当前的Leader

如果在Cnew中不包含当前的Leader所在节点,那么如果Leader在收到Cnew配置以后,马上退位成为Follower,那么将会导致下面的问题:

  • ConfChange的日志尚未复制到Cnew中的大多数的节点
  • 马上退位成为Follower的可能因为超时成为新的Leader,因为该节点上的日志是最新的,因为日志的安全性,该节点并不会为其他节点投票

为了解决以上的问题,一种很简单的方式就是通过Raft的拓展Leadership transfer首先将Leader转移到其他节点,然后再进行成员变更,但是对于不支持Leadership transfer的服务来说就行不通了。

Raft中提供了一种策略,Leader应该在Cnew日志提交以后才退位。

中断的服务器

如果Cnew中移除了原有集群中的节点,因为被移除的节点是不会再收到心跳信息,那么将会超时发起一轮选举,将会造成当前的Leader成为Follower,但是因为被移除的节点不包含Cnew的配置,所以最终会导致Cnew中的部分节点超时,重新选举Leader。如此反反复复的选举将会造成很差的可用性。

一种比较直观的方式是采用Pre-Vote方式,在任何节点发起一轮选举之前,就应该提前的发出一个Pre-Vote的RPC询问是否当前节点会同意给当前节点投票,如果超过半数的节点同意投票,那么才发生真正的投票流程的,有点类似于Two-Phase-Commit,这种方式在正常情况下,因为被移除的节点没有包含Cnew的ConfChange日志,所以在Pre-Vote情况下,大多数节点都会拒绝已经被移除节点的Pre-Vote请求。

但是上面只能处理大多数正常的情况,如果Leader收到Cnew的请求后,尚未将Cnew的ConfChange日志复制到集群中的大多数,Cnew中被移除的节点就超时开始选举了,那么Pre-Vote此时是没有用的,被移除的节点仍有可能选举成功。顺便一说,这里的Pre-Vote虽然不能解决目前的问题,但是针对脑裂而产生的任期爆炸式增长和很有用的,这里就不展开讨论了。

就如下图所示,S4收到Cnew成员变更的请求,立马将其写入日志中,Cnew中并不包含S1节点,所以在S4将日志复制到S2,S3之前,如果S1超时了,S2,S3中因为没有最新的Cnew日志,仍让会投票给S1,此时S1就能选举成功,这不是我们想看到的。

raft-disruptive-server.png

Raft中提供了另一种方式来避免这个问题,如果每一个服务器如果在ElectionTimeout内收到现有Leader的心跳(换句话说,在租约期内,仍然臣服于其他的Leader),那么就不会更新自己的现有Term以及同意投票。这样每一个Follower就会变得很稳定,除非自己已经知道的Leader已经不发送心跳给自己了,否则会一直臣服于当前的leader,尽管收到其他更高的Term的服务器投票请求。

任意节点的Joint Consensus

上面我们提到单节点的成员变更,很多时候这已经能满足我们的需求了,但是有些时候我们可能会需要随意的的集群成员变更,每次变更多个节点,那么我们就需要Raft的Joint Consensus, 尽管这会引入很多的复杂性。

Joint Consensus会将集群的配置转换到一个临时状态,然后开始变更:

  1. Leader收到Cnew的成员变更请求,然后生成一个Cold,new的ConfChang日志,马上应用该日志,然后将日志通过AppendEntries请求复制到Follower中,收到该ConfChange的节点马上应用该配置作为当前节点的配置
  2. 在将Cold,new日志复制到大多数节点上时,那么Cold,new的日志就可以提交了,在Cold,new的ConfChange日志被提交以后,马上创建一个Cnew的ConfChange的日志,并将该日志通过AppendEntries请求复制到Follower中,收到该ConfChange的节点马上应用该配置作为当前节点的配置
  3. 一旦Cnew的日志复制到大多数节点上时,那么Cnew的日志就可以提交了,在Cnew日志提交以后,就可以开始下一轮的成员变更了

为了理解上面的流程,我们有几个概念需要解释一下:

  • Cold,new:这个配置是指Cold,和Cnew的联合配置,其值为Cold和Cnew的配置的交集,比如Cold为[A, B, C], Cnew为[B, C, D],那么Cold,new就为[A, B, C, D]
  • Cold,new的大多数:是指Cold中的大多数和Cnew中的大多数,如下表所示,第一列因为Cnew的C, D没有Replicate到日志,所以并不能达到一致
ColdCnewReplicate结果是否是Majority
A, B, CB, C, DA+, B+, C-, D-
A, B, CB, C, DA+, B+, C+, D-
A, B, CB, C, DA-, B+, C+, D-

由上可以看出,整个集群的变更分为几个过渡期,就如下图所示,在每一个时期,每一个任期下都不可能出现两个Leader:

  1. Cold,new日志在提交之前,在这个阶段,Cold,new中的所有节点有可能处于Cold的配置下,也有可能处于Cold,new的配置下,如果这个时候原Leader宕机了,无论是发起新一轮投票的节点当前的配置是Cold还是Cold,new,都需要Cold的节点同意投票,所以不会出现两个Leader
  2. Cold,new提交之后,Cnew下发之前,此时所有Cold,new的配置已经在Cold和Cnew的大多数节点上,如果集群中的节点超时,那么肯定只有有Cold,new配置的节点才能成为Leader,所以不会出现两个Leader
  3. Cnew下发以后,Cnew提交之前,此时集群中的节点可能有三种,Cold的节点(可能一直没有收到请求), Cold,new的节点,Cnew的节点,其中Cold的节点因为没有最新的日志的,集群中的大多数节点是不会给他投票的,剩下的持有Cnew和Cold,new的节点,无论是谁发起选举,都需要Cnew同意,那么也是不会出现两个Leader
  4. Cnew提交之后,这个时候集群处于Cnew配置下运行,只有Cnew的节点才可以成为Leader,这个时候就可以开始下一轮的成员变更了

raft-multi-server.png

其他

自动的成员变更

如果我们给集群增加一些监控,比如在检测到机器宕机的情况下,动态的向系统中增加新的节点,这样就可以做到自动化,增加系统的节点数。

集群动态配置

一般情况下,我们都是使用静态文件的方式来描述集群中的成员信息,但是有了成员变更的算法,我们就可以动态配置的方式来设置集群的配置信息

查看原文

qeesung 发布了文章 · 5月31日

Raft 算法之集群成员变更

原文地址: https://qeesung.github.io/202...

Raft 集群成员变更

在前面三个章节中,我们介绍了Raft的:

上面的讨论都是基于Raft集群成员恒定不变的,然而在很多时候,集群的节点可能需要进行维护,或者是因为需要扩容,那么就难以避免的需要向Raft集群中添加和删除节点。最简单的方式就是停止整个集群,更改集群的静态配置,然后重新启动集群,但是这样就丧失了集群的可用性,往往是不可取的,所以Raft提供了两种在不停机的情况下,动态的更改集群成员的方式:

  • 单节点成员变更:One Server ConfChange
  • 多节点联合共识:Joint Consensus

动态成员变更存在的问题

在Raft中有一个很重要的安全性保证就是只有一个Leader,如果我们在不加任何限制的情况下,动态的向集群中添加成员,那么就可能导致同一个任期下存在多个Leader的情况,这是非常危险的。

如下图所示,从Cold迁移到Cnew的过程中,因为各个节点收到最新配置的实际不一样,那么肯能导致在同一任期下多个Leader同时存在。

比如图中此时Server3宕机了,然后Server1和Server5同时超时发起选举:

  • Server1:此时Server1中的配置还是Cold,只需要Server1和Server2就能够组成集群的Majority,因此可以被选举为Leader
  • Server5:已经收到Cnew的配置,使用Cnew的配置,此时只需要Server3,Server4,Server5就可以组成集群的Majority,因为可以被选举为Leader

换句话说,以Cold和Cnew作为配置的节点在同一任期下可以分别选出Leader。

raft-multi-leader.png

所以为了解决上面的问题,在集群成员变更的时候需要作出一些限定。

单节点成员变更

所谓单节点成员变更,就是每次只想集群中添加或移除一个节点。比如说以前集群中存在三个节点,现在需要将集群拓展为五个节点,那么就需要一个一个节点的添加,而不是一次添加两个节点。

这个为什么安全呢?很容易枚举出所有情况,原有集群奇偶数节点情况下,分别添加和删除一个节点。在下图中可以看出,如果每次只增加和删除一个节点,那么Cold的Majority和Cnew的Majority之间一定存在交集,也就说是在同一个Term中,Cold和Cnew中交集的那一个节点只会进行一次投票,要么投票给Cold,要么投票给Cnew,这样就避免了同一Term下出现两个Leader。
raft-single-server.png

变更的流程如下:

  1. 向Leader提交一个成员变更请求,请求的内容为服务节点的是添加还是移除,以及服务节点的地址信息
  2. Leader在收到请求以后,回向日志中追加一条ConfChange的日志,其中包含了Cnew,后续这些日志会随着AppendEntries的RPC同步所有的Follower节点中
  3. ConfChange的日志被添加到日志中是立即生效(注意:不是等到提交以后才生效)
  4. ConfChange的日志被复制到Cnew的Majority服务器上时,那么就可以对日志进行提交了

以上就是整个单节点的变更流程,在日志被提交以后,那么就可以:

  1. 马上响应客户端,变更已经完成
  2. 如果变更过程中移除了服务器,那么服务器可以关机了
  3. 可以开始下一轮的成员变更了,注意在上一次变更没有结束之前,是不允许开始下一次变更的

可用性

可用性问题

在我们向集群添加或者删除一个节点以后,可能会导致服务的不可用,比如向一个有三个节点的集群中添加一个干净的,没有任何日志的新节点,在添加节点以后,原集群中的一个Follower宕机了,那么此时集群中还有三个节点可用,满足Majority,但是因为其中新加入的节点是干净的,没有任何日志的节点,需要花时间追赶最新的日志,所以在新节点追赶日志期间,整个服务是不可用的。

在接下来的子章节中,我们将会讨论三个服务的可用性问题:

  • 追赶新的服务器
  • 移除当前的Leader
  • 中断服务器

追赶新的服务器

在添加服务器以后,如果新的服务器需要花很长时间来追赶日志,那么这段时间内服务不可用。

如下图所示:

  • 左图:向集群中添加新的服务器S4以后,S3宕机了,那么此时因为S4需要追赶日志,此时不可用
  • 右图:向集群中添加多个服务器,那么添加以后Majority肯定是包含新的服务器的,那么此时S4,S5,S6需要追赶日志,肯定也是不可用的

raft-catch-up-server.png

新加入集群中的节点可能并不是因为需要追赶大量的日志而不可用,也有可能是因为网络不通,或者是网速太慢,导致需要花很长的时间追赶日志。

在Raft中提供了两种解决方案:

  • 在集群中加入新的角色LeanerLeaner只对集群的日志进行复制,并不参加投票和提交决定,在需要添加新节点的情况下,添加Leaner即可。
  • 加入一个新的Phase,这个阶段会在固定的Rounds(比如10)内尝试追赶日志,最后一轮追赶日志的时间如果小于ElectionTimeout, 那么说明追赶上了,否则就抛出异常

下面我们就详细讨论一下第二种方案。

在固定Rounds内追赶日志

如果需要添加的新的节点在很短时间内可以追赶上最新的日志,那么就可以将该节点添加到集群中。那要怎么判断这个新的节点是否可以很快时间内追赶上最新的日志呢?

Raft提供了一种方法,在配置变更之前引入一个新的阶段,这个阶段会分为多个Rounds(比如10)向Leader同步日志,如果新节点能够正常的同步日志,那么每一轮的日志同步时间都将缩短,如果在最后一轮Round同步日志消耗的时间小于ElectionTimeout,那么说明新节点的日志和Leader的日志已经足够接近,可以将新节点加入到集群中。但是如果最后一轮的Round的日志同步时间大于ElectionTimeout,就应该立即终止成员变更。

raft-replicate-round.png

移除当前的Leader

如果在Cnew中不包含当前的Leader所在节点,那么如果Leader在收到Cnew配置以后,马上退位成为Follower,那么将会导致下面的问题:

  • ConfChange的日志尚未复制到Cnew中的大多数的节点
  • 马上退位成为Follower的可能因为超时成为新的Leader,因为该节点上的日志是最新的,因为日志的安全性,该节点并不会为其他节点投票

为了解决以上的问题,一种很简单的方式就是通过Raft的拓展Leadership transfer首先将Leader转移到其他节点,然后再进行成员变更,但是对于不支持Leadership transfer的服务来说就行不通了。

Raft中提供了一种策略,Leader应该在Cnew日志提交以后才退位。

中断的服务器

如果Cnew中移除了原有集群中的节点,因为被移除的节点是不会再收到心跳信息,那么将会超时发起一轮选举,将会造成当前的Leader成为Follower,但是因为被移除的节点不包含Cnew的配置,所以最终会导致Cnew中的部分节点超时,重新选举Leader。如此反反复复的选举将会造成很差的可用性。

一种比较直观的方式是采用Pre-Vote方式,在任何节点发起一轮选举之前,就应该提前的发出一个Pre-Vote的RPC询问是否当前节点会同意给当前节点投票,如果超过半数的节点同意投票,那么才发生真正的投票流程的,有点类似于Two-Phase-Commit,这种方式在正常情况下,因为被移除的节点没有包含Cnew的ConfChange日志,所以在Pre-Vote情况下,大多数节点都会拒绝已经被移除节点的Pre-Vote请求。

但是上面只能处理大多数正常的情况,如果Leader收到Cnew的请求后,尚未将Cnew的ConfChange日志复制到集群中的大多数,Cnew中被移除的节点就超时开始选举了,那么Pre-Vote此时是没有用的,被移除的节点仍有可能选举成功。顺便一说,这里的Pre-Vote虽然不能解决目前的问题,但是针对脑裂而产生的任期爆炸式增长和很有用的,这里就不展开讨论了。

就如下图所示,S4收到Cnew成员变更的请求,立马将其写入日志中,Cnew中并不包含S1节点,所以在S4将日志复制到S2,S3之前,如果S1超时了,S2,S3中因为没有最新的Cnew日志,仍让会投票给S1,此时S1就能选举成功,这不是我们想看到的。

raft-disruptive-server.png

Raft中提供了另一种方式来避免这个问题,如果每一个服务器如果在ElectionTimeout内收到现有Leader的心跳(换句话说,在租约期内,仍然臣服于其他的Leader),那么就不会更新自己的现有Term以及同意投票。这样每一个Follower就会变得很稳定,除非自己已经知道的Leader已经不发送心跳给自己了,否则会一直臣服于当前的leader,尽管收到其他更高的Term的服务器投票请求。

任意节点的Joint Consensus

上面我们提到单节点的成员变更,很多时候这已经能满足我们的需求了,但是有些时候我们可能会需要随意的的集群成员变更,每次变更多个节点,那么我们就需要Raft的Joint Consensus, 尽管这会引入很多的复杂性。

Joint Consensus会将集群的配置转换到一个临时状态,然后开始变更:

  1. Leader收到Cnew的成员变更请求,然后生成一个Cold,new的ConfChang日志,马上应用该日志,然后将日志通过AppendEntries请求复制到Follower中,收到该ConfChange的节点马上应用该配置作为当前节点的配置
  2. 在将Cold,new日志复制到大多数节点上时,那么Cold,new的日志就可以提交了,在Cold,new的ConfChange日志被提交以后,马上创建一个Cnew的ConfChange的日志,并将该日志通过AppendEntries请求复制到Follower中,收到该ConfChange的节点马上应用该配置作为当前节点的配置
  3. 一旦Cnew的日志复制到大多数节点上时,那么Cnew的日志就可以提交了,在Cnew日志提交以后,就可以开始下一轮的成员变更了

为了理解上面的流程,我们有几个概念需要解释一下:

  • Cold,new:这个配置是指Cold,和Cnew的联合配置,其值为Cold和Cnew的配置的交集,比如Cold为[A, B, C], Cnew为[B, C, D],那么Cold,new就为[A, B, C, D]
  • Cold,new的大多数:是指Cold中的大多数和Cnew中的大多数,如下表所示,第一列因为Cnew的C, D没有Replicate到日志,所以并不能达到一致
ColdCnewReplicate结果是否是Majority
A, B, CB, C, DA+, B+, C-, D-
A, B, CB, C, DA+, B+, C+, D-
A, B, CB, C, DA-, B+, C+, D-

由上可以看出,整个集群的变更分为几个过渡期,就如下图所示,在每一个时期,每一个任期下都不可能出现两个Leader:

  1. Cold,new日志在提交之前,在这个阶段,Cold,new中的所有节点有可能处于Cold的配置下,也有可能处于Cold,new的配置下,如果这个时候原Leader宕机了,无论是发起新一轮投票的节点当前的配置是Cold还是Cold,new,都需要Cold的节点同意投票,所以不会出现两个Leader
  2. Cold,new提交之后,Cnew下发之前,此时所有Cold,new的配置已经在Cold和Cnew的大多数节点上,如果集群中的节点超时,那么肯定只有有Cold,new配置的节点才能成为Leader,所以不会出现两个Leader
  3. Cnew下发以后,Cnew提交之前,此时集群中的节点可能有三种,Cold的节点(可能一直没有收到请求), Cold,new的节点,Cnew的节点,其中Cold的节点因为没有最新的日志的,集群中的大多数节点是不会给他投票的,剩下的持有Cnew和Cold,new的节点,无论是谁发起选举,都需要Cnew同意,那么也是不会出现两个Leader
  4. Cnew提交之后,这个时候集群处于Cnew配置下运行,只有Cnew的节点才可以成为Leader,这个时候就可以开始下一轮的成员变更了

raft-multi-server.png

其他

自动的成员变更

如果我们给集群增加一些监控,比如在检测到机器宕机的情况下,动态的向系统中增加新的节点,这样就可以做到自动化,增加系统的节点数。

集群动态配置

一般情况下,我们都是使用静态文件的方式来描述集群中的成员信息,但是有了成员变更的算法,我们就可以动态配置的方式来设置集群的配置信息

查看原文

赞 2 收藏 1 评论 0

qeesung 收藏了文章 · 4月24日

IntelliJ IDEA 快捷键终极大全,速度收藏!

作者:满风
https://my.oschina.net/dyyweb...

自动代码

常用的有fori/sout/psvm+Tab即可生成循环、System.out、main方法等boilerplate样板代码 。

例如要输入for(User user : users)只需输入user.for+Tab ;

再比如,要输入Date birthday = user.getBirthday()只需输入user.getBirthday().var+Tab即可。

代码标签输入完成后,按Tab,生成代码。

  1. Ctrl+Alt+O 优化导入的类和包
  2. Alt+Insert 生成代码(如get,set方法,构造函数等)   或者右键(Generate)
  3. fori/sout/psvm + Tab
  4. Ctrl+Alt+T  生成try catch  或者 Alt+enter
  5. CTRL+ALT+T  把选中的代码放在 TRY{} IF{} ELSE{} 里
  6. Ctrl + O 重写方法
  7. Ctrl + I 实现方法
  8. Ctr+shift+U 大小写转化
  9. ALT+回车    导入包,自动修正
  10. ALT+/       代码提示
  11. CTRL+J      自动代码
  12. Ctrl+Shift+J,整合两行为一行
  13. CTRL+空格   代码提示
  14. CTRL+SHIFT+SPACE 自动补全代码
  15. CTRL+ALT+L  格式化代码
  16. CTRL+ALT+I  自动缩进
  17. CTRL+ALT+O  优化导入的类和包
  18. ALT+INSERT  生成代码(如GET,SET方法,构造函数等)
  19. CTRL+E      最近更改的代码
  20. CTRL+ALT+SPACE  类名或接口名提示
  21. CTRL+P   方法参数提示
  22. CTRL+Q,可以看到当前方法的声明
  23. Shift+F6  重构-重命名 (包、类、方法、变量、甚至注释等)
  24. Ctrl+Alt+V 提取变量

查询快捷键

  1. Ctrl+Shift+Backspace可以跳转到上次编辑的地
  2. CTRL+ALT+ left/right 前后导航编辑过的地方
  3. ALT+7  靠左窗口显示当前文件的结构
  4. Ctrl+F12 浮动显示当前文件的结构
  5. ALT+F7 找到你的函数或者变量或者类的所有引用到的地方
  6. CTRL+ALT+F7  找到你的函数或者变量或者类的所有引用到的地方
  7. Ctrl+Shift+Alt+N 查找类中的方法或变量
  8. 双击SHIFT 在项目的所有目录查找文件
  9. Ctrl+N   查找类
  10. Ctrl+Shift+N 查找文件
  11. CTRL+G   定位行
  12. CTRL+F   在当前窗口查找文本
  13. CTRL+SHIFT+F  在指定窗口查找文本
  14. CTRL+R   在 当前窗口替换文本
  15. CTRL+SHIFT+R  在指定窗口替换文本
  16. ALT+SHIFT+C  查找修改的文件
  17. CTRL+E   最近打开的文件
  18. F3   向下查找关键字出现位置
  19. SHIFT+F3  向上一个关键字出现位置
  20. 选中文本,按Alt+F3 ,高亮相同文本,F3逐个往下查找相同文本
  21. F4   查找变量来源
  22. CTRL+SHIFT+O  弹出显示查找内容
  23. Ctrl+W 选中代码,连续按会有其他效果
  24. F2 或Shift+F2 高亮错误或警告快速定位
  25. Ctrl+Up/Down 光标跳转到第一行或最后一行下
  26. Ctrl+B 快速打开光标处的类或方法
  27. CTRL+ALT+B  找所有的子类
  28. CTRL+SHIFT+B  找变量的类
  29. Ctrl+Shift+上下键  上下移动代码
  30. Ctrl+Alt+ left/right 返回至上次浏览的位置
  31. Ctrl+X 删除行
  32. Ctrl+D 复制行
  33. Ctrl+/ 或 Ctrl+Shift+/  注释(// 或者/.../ )
  34. Ctrl+H 显示类结构图
  35. Ctrl+Q 显示注释文档
  36. Alt+F1 查找代码所在位置
  37. Alt+1 快速打开或隐藏工程面板
  38. Alt+ left/right 切换代码视图
  39. ALT+ ↑/↓  在方法间快速移动定位
  40. CTRL+ALT+ left/right 前后导航编辑过的地方
  41. Ctrl+Shift+Backspace可以跳转到上次编辑的地
  42. Alt+6    查找TODO

其他快捷键

  1. SHIFT+ENTER 另起一行
  2. CTRL+Z   倒退(撤销)
  3. CTRL+SHIFT+Z  向前(取消撤销)
  4. CTRL+ALT+F12  资源管理器打开文件夹
  5. ALT+F1   查找文件所在目录位置
  6. SHIFT+ALT+INSERT 竖编辑模式
  7. CTRL+F4  关闭当前窗口
  8. Ctrl+Alt+V,可以引入变量。例如:new String(); 自动导入变量定义
  9. Ctrl+~,快速切换方案(界面外观、代码风格、快捷键映射等菜单)

svn快捷键

  1. ctrl+k 提交代码到SVN
  2. ctrl+t 更新代码

调试快捷键

其实常用的 就是F8 F7 F9 最值得一提的就是Drop Frame 可以让运行过的代码从头再来。推荐:Intellij IDEA Debug 调试技巧

  1. alt+F8    debug时选中查看值
  2. Alt+Shift+F9,选择 Debug
  3. Alt+Shift+F10,选择 Run
  4. Ctrl+Shift+F9,编译
  5. Ctrl+Shift+F8,查看断点
  6. F7,步入
  7. Shift+F7,智能步入
  8. Alt+Shift+F7,强制步入
  9. F8,步过
  10. Shift+F8,步出
  11. Alt+Shift+F8,强制步过
  12. Alt+F9,运行至光标处
  13. Ctrl+Alt+F9,强制运行至光标处
  14. F9,恢复程序
  15. Alt+F10,定位到断点

重构

  1. Ctrl+Alt+Shift+T,弹出重构菜单
  2. Shift+F6,重命名
  3. F6,移动
  4. F5,复制
  5. Alt+Delete,安全删除
  6. Ctrl+Alt+N,内联

十大Intellij IDEA快捷键

Intellij IDEA中有很多快捷键让人爱不释手,stackoverflow上也有一些有趣的讨论。每个人都有自己的最爱,想排出个理想的榜单还真是困难。

以前也整理过Intellij的快捷键,这次就按照我日常开发时的使用频率,简单分类列一下我最喜欢的十大快捷-神-键吧。

1 智能提示:

Intellij首当其冲的当然就是Intelligence智能!基本的代码提示用Ctrl+Space,还有更智能地按类型信息提示Ctrl+Shift+Space,但因为Intellij总是随着我们敲击而自动提示,所以很多时候都不会手动敲这两个快捷键(除非提示框消失了)。推荐:Intellij Idea非常6的10个姿势!

用F2/ Shift+F2移动到有错误的代码,Alt+Enter快速修复(即Eclipse中的Quick Fix功能)。当智能提示为我们自动补全方法名时,我们通常要自己补上行尾的反括号和分号,当括号嵌套很多层时会很麻烦,这时我们只需敲Ctrl+Shift+Enter就能自动补全末尾的字符。而且不只是括号,例如敲完if/for时也可以自动补上{}花括号。

最后要说一点,Intellij能够智能感知Spring、Hibernate等主流框架的配置文件和类,以静制动,在看似“静态”的外表下,智能地扫描理解你的项目是如何构造和配置的。

2 重构:

Intellij重构是另一完爆Eclipse的功能,其智能程度令人瞠目结舌,比如提取变量时自动检查到所有匹配同时提取成一个变量等。尤其看过《重构-改善既有代码设计》之后,有了Intellij的配合简直是令人大呼过瘾!也正是强大的智能和重构功能,使Intellij下的TDD开发非常顺畅。推荐: Intellij IDEA 那些隐藏好用的小技巧

切入正题,先说一个无敌的重构功能大汇总快捷键Ctrl+Shift+Alt+T,叫做Refactor This。按法有点复杂,但也符合Intellij的风格,很多快捷键都要双手完成,而不像Eclipse不少最有用的快捷键可以潇洒地单手完成(不知道算不算Eclipse的一大优点),但各位用过Emacs的话就会觉得也没什么了(非Emacs黑)。

此外,还有些最常用的重构技巧,因为太常用了,若每次都在Refactor This菜单里选的话效率有些低。比如Shift+F6直接就是改名,Ctrl+Alt+V则是提取变量。关注Java技术栈微信公众号,在后台回复关键字:IDEA,可以获取一份栈长整理的 IDEA 最新技术干货。

3 代码生成:

这一点类似Eclipse,虽不是独到之处,但因为日常使用频率极高,所以还是罗列在榜单前面。常用的有fori/sout/psvm+Tab即可生成循环、System.out、main方法等boilerplate样板代码,用Ctrl+J可以查看所有模板。

后面“辅助”一节中将会讲到Alt+Insert,在编辑窗口中点击可以生成构造函数、toString、getter/setter、重写父类方法等。这两个技巧实在太常用了,几乎每天都要生成一堆main、System.out和getter/setter。

另外,Intellij IDEA 13中加入了后缀自动补全功能(Postfix Completion),比模板生成更加灵活和强大。例如要输入for(User user : users)只需输入user.for+Tab。再比如,要输入Date birthday = user.getBirthday();只需输入user.getBirthday().var+Tab即可。

4 编辑:

编辑中不得不说的一大神键就是能够自动按语法选中代码的Ctrl+W以及反向的Ctrl+Shift+W了。此外,Ctrl+Left/Right移动光标到前/后单词,Ctrl+[/]移动到前/后代码块,这些类Vim风格的光标移动也是一大亮点。以上Ctrl+Left/Right/[]加上Shift的话就能选中跳跃范围内的代码。Alt+Forward/Backward移动到前/后方法。还有些非常普通的像Ctrl+Y删除行、Ctrl+D复制行、Ctrl+折叠代码就不多说了。

关于光标移动再多扩展一点,除了Intellij本身已提供的功能外,我们还可以安装ideaVim或者emacsIDEAs享受到Vim的快速移动和Emacs的AceJump功能(超爽!)。

另外,Intellij的书签功能也是不错的,用Ctrl+Shift+Num定义1-10书签(再次按这组快捷键则是删除书签),然后通过Ctrl+Num跳转。这避免了多次使用前/下一编辑位置Ctrl+Left/Right来回跳转的麻烦,而且此快捷键默认与Windows热键冲突(默认多了Alt,与Windows改变显示器显示方向冲突,一不小心显示器就变成倒着显式的了,冏啊)。 推荐:Intellij IDEA 撸码最头大的问题。。

5 查找打开:

类似Eclipse,Intellij的Ctrl+N/Ctrl+Shift+N可以打开类或资源,但Intellij更加智能一些,我们输入的任何字符都将看作模糊匹配,省却了Eclipse中还有输入*的麻烦。最新版本的IDEA还加入了Search Everywhere功能,只需按Shift+Shift即可在一个弹出框中搜索任何东西,包括类、资源、配置项、方法等等。

类的继承关系则可用Ctrl+H打开类层次窗口,在继承层次上跳转则用Ctrl+B/Ctrl+Alt+B分别对应父类或父方法定义和子类或子方法实现,查看当前类的所有方法用Ctrl+F12。

要找类或方法的使用也很简单,Alt+F7。要查找文本的出现位置就用Ctrl+F/Ctrl+Shift+F在当前窗口或全工程中查找,再配合F3/Shift+F3前后移动到下一匹配处。

Intellij更加智能的又一佐证是在任意菜单或显示窗口,都可以直接输入你要找的单词,Intellij就会自动为你过滤。 关注Java技术栈微信公众号,在后台回复关键字:IDEA,可以获取一份栈长整理的 IDEA 最新技术干货。

6 其他辅助:

以上这些神键配上一些辅助快捷键,即可让你的双手90%以上的时间摆脱鼠标,专注于键盘仿佛在进行钢琴表演。这些不起眼却是至关重要的最后一块拼图有:

Ø  命令:Ctrl+Shift+A可以查找所有Intellij的命令,并且每个命令后面还有其快捷键。所以它不仅是一大神键,也是查找学习快捷键的工具。

Ø  新建:Alt+Insert可以新建类、方法等任何东西。

Ø  格式化代码:格式化import列表Ctrl+Alt+O,格式化代码Ctrl+Alt+L。

Ø  切换窗口:Alt+Num,常用的有1-项目结构,3-搜索结果,4/5-运行调试。Ctrl+Tab切换标签页,Ctrl+E/Ctrl+Shift+E打开最近打开过的或编辑过的文件。

Ø  单元测试:Ctrl+Alt+T创建单元测试用例。

Ø  运行:Alt+Shift+F10运行程序,Shift+F9启动调试,Ctrl+F2停止。

Ø  调试:F7/F8/F9分别对应Step into,Step over,Continue。

此外还有些我自定义的,例如水平分屏Ctrl+|等,和一些神奇的小功能Ctrl+Shift+V粘贴很早以前拷贝过的,Alt+Shift+Insert进入到列模式进行按列选中。

Ø  Top #10切来切去:Ctrl+Tab

Ø  Top #9选你所想:Ctrl+W

Ø  Top #8代码生成:Template/Postfix +Tab

Ø  Top #7发号施令:Ctrl+Shift+A

Ø  Top #6无处藏身:Shift+Shift

Ø  Top #5自动完成:Ctrl+Shift+Enter

Ø  Top #4创造万物:Alt+Insert

太难割舍,前三名并列吧!

Ø  Top #1智能补全:Ctrl+Shift+Space

Ø  Top #1自我修复:Alt+Enter

Ø  Top #1重构一切:Ctrl+Shift+Alt+T

CTRL+ALT+ left/right 前后导航编辑过的地方 _
Ctrl+Shift+Backspace可以跳转到上次编辑的地方_

关注Java技术栈微信公众号,在后台回复关键字:IDEA,可以获取一份栈长整理的 IDEA 最新技术干货。

推荐去我的博客阅读更多:

1.Java JVM、集合、多线程、新特性系列教程

2.Spring MVC、Spring Boot、Spring Cloud 系列教程

3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程

4.Java、后端、架构、阿里巴巴等大厂最新面试题

觉得不错,别忘了点赞+转发哦!

查看原文

qeesung 赞了文章 · 4月24日

IntelliJ IDEA 快捷键终极大全,速度收藏!

作者:满风
https://my.oschina.net/dyyweb...

自动代码

常用的有fori/sout/psvm+Tab即可生成循环、System.out、main方法等boilerplate样板代码 。

例如要输入for(User user : users)只需输入user.for+Tab ;

再比如,要输入Date birthday = user.getBirthday()只需输入user.getBirthday().var+Tab即可。

代码标签输入完成后,按Tab,生成代码。

  1. Ctrl+Alt+O 优化导入的类和包
  2. Alt+Insert 生成代码(如get,set方法,构造函数等)   或者右键(Generate)
  3. fori/sout/psvm + Tab
  4. Ctrl+Alt+T  生成try catch  或者 Alt+enter
  5. CTRL+ALT+T  把选中的代码放在 TRY{} IF{} ELSE{} 里
  6. Ctrl + O 重写方法
  7. Ctrl + I 实现方法
  8. Ctr+shift+U 大小写转化
  9. ALT+回车    导入包,自动修正
  10. ALT+/       代码提示
  11. CTRL+J      自动代码
  12. Ctrl+Shift+J,整合两行为一行
  13. CTRL+空格   代码提示
  14. CTRL+SHIFT+SPACE 自动补全代码
  15. CTRL+ALT+L  格式化代码
  16. CTRL+ALT+I  自动缩进
  17. CTRL+ALT+O  优化导入的类和包
  18. ALT+INSERT  生成代码(如GET,SET方法,构造函数等)
  19. CTRL+E      最近更改的代码
  20. CTRL+ALT+SPACE  类名或接口名提示
  21. CTRL+P   方法参数提示
  22. CTRL+Q,可以看到当前方法的声明
  23. Shift+F6  重构-重命名 (包、类、方法、变量、甚至注释等)
  24. Ctrl+Alt+V 提取变量

查询快捷键

  1. Ctrl+Shift+Backspace可以跳转到上次编辑的地
  2. CTRL+ALT+ left/right 前后导航编辑过的地方
  3. ALT+7  靠左窗口显示当前文件的结构
  4. Ctrl+F12 浮动显示当前文件的结构
  5. ALT+F7 找到你的函数或者变量或者类的所有引用到的地方
  6. CTRL+ALT+F7  找到你的函数或者变量或者类的所有引用到的地方
  7. Ctrl+Shift+Alt+N 查找类中的方法或变量
  8. 双击SHIFT 在项目的所有目录查找文件
  9. Ctrl+N   查找类
  10. Ctrl+Shift+N 查找文件
  11. CTRL+G   定位行
  12. CTRL+F   在当前窗口查找文本
  13. CTRL+SHIFT+F  在指定窗口查找文本
  14. CTRL+R   在 当前窗口替换文本
  15. CTRL+SHIFT+R  在指定窗口替换文本
  16. ALT+SHIFT+C  查找修改的文件
  17. CTRL+E   最近打开的文件
  18. F3   向下查找关键字出现位置
  19. SHIFT+F3  向上一个关键字出现位置
  20. 选中文本,按Alt+F3 ,高亮相同文本,F3逐个往下查找相同文本
  21. F4   查找变量来源
  22. CTRL+SHIFT+O  弹出显示查找内容
  23. Ctrl+W 选中代码,连续按会有其他效果
  24. F2 或Shift+F2 高亮错误或警告快速定位
  25. Ctrl+Up/Down 光标跳转到第一行或最后一行下
  26. Ctrl+B 快速打开光标处的类或方法
  27. CTRL+ALT+B  找所有的子类
  28. CTRL+SHIFT+B  找变量的类
  29. Ctrl+Shift+上下键  上下移动代码
  30. Ctrl+Alt+ left/right 返回至上次浏览的位置
  31. Ctrl+X 删除行
  32. Ctrl+D 复制行
  33. Ctrl+/ 或 Ctrl+Shift+/  注释(// 或者/.../ )
  34. Ctrl+H 显示类结构图
  35. Ctrl+Q 显示注释文档
  36. Alt+F1 查找代码所在位置
  37. Alt+1 快速打开或隐藏工程面板
  38. Alt+ left/right 切换代码视图
  39. ALT+ ↑/↓  在方法间快速移动定位
  40. CTRL+ALT+ left/right 前后导航编辑过的地方
  41. Ctrl+Shift+Backspace可以跳转到上次编辑的地
  42. Alt+6    查找TODO

其他快捷键

  1. SHIFT+ENTER 另起一行
  2. CTRL+Z   倒退(撤销)
  3. CTRL+SHIFT+Z  向前(取消撤销)
  4. CTRL+ALT+F12  资源管理器打开文件夹
  5. ALT+F1   查找文件所在目录位置
  6. SHIFT+ALT+INSERT 竖编辑模式
  7. CTRL+F4  关闭当前窗口
  8. Ctrl+Alt+V,可以引入变量。例如:new String(); 自动导入变量定义
  9. Ctrl+~,快速切换方案(界面外观、代码风格、快捷键映射等菜单)

svn快捷键

  1. ctrl+k 提交代码到SVN
  2. ctrl+t 更新代码

调试快捷键

其实常用的 就是F8 F7 F9 最值得一提的就是Drop Frame 可以让运行过的代码从头再来。推荐:Intellij IDEA Debug 调试技巧

  1. alt+F8    debug时选中查看值
  2. Alt+Shift+F9,选择 Debug
  3. Alt+Shift+F10,选择 Run
  4. Ctrl+Shift+F9,编译
  5. Ctrl+Shift+F8,查看断点
  6. F7,步入
  7. Shift+F7,智能步入
  8. Alt+Shift+F7,强制步入
  9. F8,步过
  10. Shift+F8,步出
  11. Alt+Shift+F8,强制步过
  12. Alt+F9,运行至光标处
  13. Ctrl+Alt+F9,强制运行至光标处
  14. F9,恢复程序
  15. Alt+F10,定位到断点

重构

  1. Ctrl+Alt+Shift+T,弹出重构菜单
  2. Shift+F6,重命名
  3. F6,移动
  4. F5,复制
  5. Alt+Delete,安全删除
  6. Ctrl+Alt+N,内联

十大Intellij IDEA快捷键

Intellij IDEA中有很多快捷键让人爱不释手,stackoverflow上也有一些有趣的讨论。每个人都有自己的最爱,想排出个理想的榜单还真是困难。

以前也整理过Intellij的快捷键,这次就按照我日常开发时的使用频率,简单分类列一下我最喜欢的十大快捷-神-键吧。

1 智能提示:

Intellij首当其冲的当然就是Intelligence智能!基本的代码提示用Ctrl+Space,还有更智能地按类型信息提示Ctrl+Shift+Space,但因为Intellij总是随着我们敲击而自动提示,所以很多时候都不会手动敲这两个快捷键(除非提示框消失了)。推荐:Intellij Idea非常6的10个姿势!

用F2/ Shift+F2移动到有错误的代码,Alt+Enter快速修复(即Eclipse中的Quick Fix功能)。当智能提示为我们自动补全方法名时,我们通常要自己补上行尾的反括号和分号,当括号嵌套很多层时会很麻烦,这时我们只需敲Ctrl+Shift+Enter就能自动补全末尾的字符。而且不只是括号,例如敲完if/for时也可以自动补上{}花括号。

最后要说一点,Intellij能够智能感知Spring、Hibernate等主流框架的配置文件和类,以静制动,在看似“静态”的外表下,智能地扫描理解你的项目是如何构造和配置的。

2 重构:

Intellij重构是另一完爆Eclipse的功能,其智能程度令人瞠目结舌,比如提取变量时自动检查到所有匹配同时提取成一个变量等。尤其看过《重构-改善既有代码设计》之后,有了Intellij的配合简直是令人大呼过瘾!也正是强大的智能和重构功能,使Intellij下的TDD开发非常顺畅。推荐: Intellij IDEA 那些隐藏好用的小技巧

切入正题,先说一个无敌的重构功能大汇总快捷键Ctrl+Shift+Alt+T,叫做Refactor This。按法有点复杂,但也符合Intellij的风格,很多快捷键都要双手完成,而不像Eclipse不少最有用的快捷键可以潇洒地单手完成(不知道算不算Eclipse的一大优点),但各位用过Emacs的话就会觉得也没什么了(非Emacs黑)。

此外,还有些最常用的重构技巧,因为太常用了,若每次都在Refactor This菜单里选的话效率有些低。比如Shift+F6直接就是改名,Ctrl+Alt+V则是提取变量。关注Java技术栈微信公众号,在后台回复关键字:IDEA,可以获取一份栈长整理的 IDEA 最新技术干货。

3 代码生成:

这一点类似Eclipse,虽不是独到之处,但因为日常使用频率极高,所以还是罗列在榜单前面。常用的有fori/sout/psvm+Tab即可生成循环、System.out、main方法等boilerplate样板代码,用Ctrl+J可以查看所有模板。

后面“辅助”一节中将会讲到Alt+Insert,在编辑窗口中点击可以生成构造函数、toString、getter/setter、重写父类方法等。这两个技巧实在太常用了,几乎每天都要生成一堆main、System.out和getter/setter。

另外,Intellij IDEA 13中加入了后缀自动补全功能(Postfix Completion),比模板生成更加灵活和强大。例如要输入for(User user : users)只需输入user.for+Tab。再比如,要输入Date birthday = user.getBirthday();只需输入user.getBirthday().var+Tab即可。

4 编辑:

编辑中不得不说的一大神键就是能够自动按语法选中代码的Ctrl+W以及反向的Ctrl+Shift+W了。此外,Ctrl+Left/Right移动光标到前/后单词,Ctrl+[/]移动到前/后代码块,这些类Vim风格的光标移动也是一大亮点。以上Ctrl+Left/Right/[]加上Shift的话就能选中跳跃范围内的代码。Alt+Forward/Backward移动到前/后方法。还有些非常普通的像Ctrl+Y删除行、Ctrl+D复制行、Ctrl+折叠代码就不多说了。

关于光标移动再多扩展一点,除了Intellij本身已提供的功能外,我们还可以安装ideaVim或者emacsIDEAs享受到Vim的快速移动和Emacs的AceJump功能(超爽!)。

另外,Intellij的书签功能也是不错的,用Ctrl+Shift+Num定义1-10书签(再次按这组快捷键则是删除书签),然后通过Ctrl+Num跳转。这避免了多次使用前/下一编辑位置Ctrl+Left/Right来回跳转的麻烦,而且此快捷键默认与Windows热键冲突(默认多了Alt,与Windows改变显示器显示方向冲突,一不小心显示器就变成倒着显式的了,冏啊)。 推荐:Intellij IDEA 撸码最头大的问题。。

5 查找打开:

类似Eclipse,Intellij的Ctrl+N/Ctrl+Shift+N可以打开类或资源,但Intellij更加智能一些,我们输入的任何字符都将看作模糊匹配,省却了Eclipse中还有输入*的麻烦。最新版本的IDEA还加入了Search Everywhere功能,只需按Shift+Shift即可在一个弹出框中搜索任何东西,包括类、资源、配置项、方法等等。

类的继承关系则可用Ctrl+H打开类层次窗口,在继承层次上跳转则用Ctrl+B/Ctrl+Alt+B分别对应父类或父方法定义和子类或子方法实现,查看当前类的所有方法用Ctrl+F12。

要找类或方法的使用也很简单,Alt+F7。要查找文本的出现位置就用Ctrl+F/Ctrl+Shift+F在当前窗口或全工程中查找,再配合F3/Shift+F3前后移动到下一匹配处。

Intellij更加智能的又一佐证是在任意菜单或显示窗口,都可以直接输入你要找的单词,Intellij就会自动为你过滤。 关注Java技术栈微信公众号,在后台回复关键字:IDEA,可以获取一份栈长整理的 IDEA 最新技术干货。

6 其他辅助:

以上这些神键配上一些辅助快捷键,即可让你的双手90%以上的时间摆脱鼠标,专注于键盘仿佛在进行钢琴表演。这些不起眼却是至关重要的最后一块拼图有:

Ø  命令:Ctrl+Shift+A可以查找所有Intellij的命令,并且每个命令后面还有其快捷键。所以它不仅是一大神键,也是查找学习快捷键的工具。

Ø  新建:Alt+Insert可以新建类、方法等任何东西。

Ø  格式化代码:格式化import列表Ctrl+Alt+O,格式化代码Ctrl+Alt+L。

Ø  切换窗口:Alt+Num,常用的有1-项目结构,3-搜索结果,4/5-运行调试。Ctrl+Tab切换标签页,Ctrl+E/Ctrl+Shift+E打开最近打开过的或编辑过的文件。

Ø  单元测试:Ctrl+Alt+T创建单元测试用例。

Ø  运行:Alt+Shift+F10运行程序,Shift+F9启动调试,Ctrl+F2停止。

Ø  调试:F7/F8/F9分别对应Step into,Step over,Continue。

此外还有些我自定义的,例如水平分屏Ctrl+|等,和一些神奇的小功能Ctrl+Shift+V粘贴很早以前拷贝过的,Alt+Shift+Insert进入到列模式进行按列选中。

Ø  Top #10切来切去:Ctrl+Tab

Ø  Top #9选你所想:Ctrl+W

Ø  Top #8代码生成:Template/Postfix +Tab

Ø  Top #7发号施令:Ctrl+Shift+A

Ø  Top #6无处藏身:Shift+Shift

Ø  Top #5自动完成:Ctrl+Shift+Enter

Ø  Top #4创造万物:Alt+Insert

太难割舍,前三名并列吧!

Ø  Top #1智能补全:Ctrl+Shift+Space

Ø  Top #1自我修复:Alt+Enter

Ø  Top #1重构一切:Ctrl+Shift+Alt+T

CTRL+ALT+ left/right 前后导航编辑过的地方 _
Ctrl+Shift+Backspace可以跳转到上次编辑的地方_

关注Java技术栈微信公众号,在后台回复关键字:IDEA,可以获取一份栈长整理的 IDEA 最新技术干货。

推荐去我的博客阅读更多:

1.Java JVM、集合、多线程、新特性系列教程

2.Spring MVC、Spring Boot、Spring Cloud 系列教程

3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程

4.Java、后端、架构、阿里巴巴等大厂最新面试题

觉得不错,别忘了点赞+转发哦!

查看原文

赞 9 收藏 7 评论 2

qeesung 收藏了文章 · 4月19日

Raft 算法之日志复制

原文地址: https://qeesung.github.io/202...
Raft 论文地址:https://ramcloud.atlassian.ne...

Raft论文中分为三块:

  • 领导选举
  • 日志复制
  • 安全性

本文中主要介绍日志复制

领导人必须从客户端接收日志然后复制到集群中的其他节点,并且强制要求其他节点的日志保持和自己相同。

鉴于日志复制这一块比较复杂,可以结合下面两个网页来理解:

复制状态机

复制状态机通常都是基于复制日志实现的,每一个服务器存储一个包含一系列指令的日志,并且按照日志的顺序进行执行。如下图所示:

  1. 客户端请求服务器,请求的信息就是一系列的指明,比如PUT KEY VALUE
  2. 服务器在收到请求以后,将操作指令同步到所有的服务器中
  3. 服务器收到同步的指令以后,就将指令应用到状态机中
  4. 最后响应客户端操作成功

raft-state-machine.png

状态机是按照同步到服务器上的指令的顺序,一个一个的去Apply指令,所以指令的顺序很重要,如果指令Apply的顺序不一致,或者丢失部分指令,那么最终状态机的状态也会不一致。

而我们知道网络是不稳定的,比如延迟,分区,丢包,荣誉和乱序等错误。如果不保证状态机Apply指令完全一模一样,那么将会导致不一致的结果。而Raft的日志复制机制则能保证在发生上述网络问题的时候,所有的服务器都能同步到完全一模一样的日志,也就是说能保证每一个服务器的状态机Apply到完全一模一样的指令。

附加日志RPC

请求体结构

所有服务器上都维持的状态:

  • commitIndex:最大的已经被提交的日志索引
  • lastApplied:最后被应用到状态的日志条目索引

lastApplied应该小于等于commitIndex,因为只有在日志被提交以后才能被Apply到状态机中

领导人经常改变的:

  • nextIndex[]:对于每一个服务器,需要发送给他的下一个日志条目的索引值,初始化为当前领导人的最后的日志索引值加一
  • matchIndex[]:对于每一个服务器,已经复制给他的日志的最高索引值

nextIndex[]matchIndex[]的长度等于整个集群中的服务器数量。

附加日志RPC的请求结构:

  • term:附加日志的领导人任期号
  • leaderId:当前领导人的Id
  • preLogIndex:当前要附加的日志entries的上一条的日志索引
  • preLogTerm:当前要附加的日志entries的上一条的日志任期号
  • entries[]:需要附加的日志条目(心跳时为空)
  • leaderCommit:当前领导人已经提交的最大的日志索引值

preLogIndexpreLogTerm主要是用于跟随者检测当前领导人要附加的日志是否和跟随者当前的日志匹配,如果不匹配的话,那么就需要继续向前搜寻和领导人匹配的日志(下面章节会介绍)

leaderCommit的话用于告诉跟随者当前提交到什么位置了(因为收到附加日志还不能马上提交,否则可能存在日志丢失的情况),以便跟随者将已经提交的日志Apply到状态机中

附加日志RPC的响应结构:

  • term:跟随者的当前的任务号
  • success:跟随者是否接收了当前的日志,在preLogIndexpreLogTerm匹配的情况下为true,否则返回false

请求的流程

每一个日志条目存储一条状态机指令和从领导人收到这条指令时的任期号。日志中的任期号用来检查是否出现不一致的情况,每一条日志条目同时也都有一个整数索引值来表明它在日志中的位置。

raft-commit-entries.png

领导人附加日志

一旦成为领导人,那么领导人就会在固定在一定时间内发送空的附加日志,也就是心跳,以组织更随着超时。

如果领导人收到来自己客户端的请求,那么首先将请求的指令附加到自己的日志队列中,然后领导人会将新附加的日志条目通过附加日志的RPC发送到所有的跟随者。

领导人需要附加那些日志?

领导人中维护了两个经常变动的属性nextIndex[]matchIndex[],用于记录要发什么日志和跟随者收到了什么日志。

其中nextIndex[]记录了需要发送给每一个服务器的日志的下一个索引值,这个数组会在领导人被选举出来的时候初始化为领导人最后的索引加一。注意这个nextIndex[]只是记录了要发给下一次附加日志要发给服务器的索引值,这个索引值可能并不一定准确,比如在跟随者和领导人的日志不一致的情况下,nextIndex[]的值就需要进行递减以找到和跟随者最大的匹配的日志,具体流程后文中会解释。

matchIndex[]主要是用来记录跟随者收到了那些日志,以方便领导人确认是否当前的日志可以进行提交,因为必须要至少N/2+1的集群成员(包括领导人自己)都确认收到了日志才可以进行的日志的提交。

领导人收到收到跟随者附加日志的响应该如何处理?

我们知道领导人在附加日志的时候是并发的向所有跟随者发起的,并且是在固定周期内定时发送的,所以可能在上一个RPC没有收到响应的情况下,发出下一个RPC请求,或者是收到过期的日志追加请求等等。

  1. 比对响应的Term和当前的Term以确认自己是否过期:

    • 如果响应的Term大于当前的Term,那么说明当前的领导人已经过期,马上将自己切换为跟随者
    • 如果响应的Term小于当前的Term,那么说明当前的收到了过期的响应(可能网路延迟导致),那么忽略
    • 否则进行步骤2
  2. 判断附加RPC时的preLogIndex和当前的nextIndex[peer]-1 是否相等,用于判断是否是过期的响应,或者是nextIndex[peer]是否被更改了:

    • 如果不相等,那么直接忽略
    • 否则进行步骤3
  3. 判断响应的success是否为真:

    • 如果为真:那么说明附加日志成功,进行步骤4
    • 如果为假:那么说明附加日志失败,preLogIndexpreLogTerm和跟随者的日志不匹配,进行步骤5
  4. 附加日志成功以后,需要更新matchIndex[peer]中的值为已经追加日志的索引值,表示追加日志成功,判断整个matchIndex[]数组中的是否有一半都大于等于追加日志的索引:

    • 如果是:那么更新commitIndex,进行日志的提交
    • 否则跳过处理
  5. 日志不匹配,那么需要找到下一个和跟随者匹配的日志索引,简单一点可以通过递减nextIndex[peer]来实现。

跟随者接收日志

跟随者收到附加日志的请求,不能简单的将日志追加到自己的日志后面,因为跟随者的日志可能和领导人有冲突,或者跟随者缺失更多的日志,入下图所示。

raft-conflict-log.png

那么一定要确保本次附加日志的之前的所有日志都相同,也就是说附加当前的日志之前,缺日志就要把缺失的日志补上,日志冲突了,就要把冲突的日志覆盖(领导人可以强行覆盖跟随者的日志)

  1. 判断附加日志任期Term和当前的Term是否相同:

    • 如果请求的Term小于当前的Term,那么说明收到了来自过期的领导人的附加日志请求,那么拒接处理。
    • 如果请求的Term大于当前的Term,那么更新当前的Term为请求的Term,进行步骤2
    • 如果请求的Term和当前的Term相等,那么说明请求合法,进行步骤2
  2. 判断preLogIndex是否大于当前的日志长度或者preLogIndex位置处的任期是否和preLogTerm相等以检测要附加的日志之前的日志是否匹配:

    • 如果preLogIndex的长度大于当前的日志的长度,那么说明跟随者缺失日志,那么拒绝附加日志,返回false
    • 如果preLogIndex处的任期和preLogTerm不相等,那么说明日志有冲突,拒绝附加日志,返回false
    • 否则说明之前的日志全都匹配,那么进行步骤3,检测preLogIndex之后的日志是否匹配。
  3. 逐一比对要附加的日志entries[]是否和自己preLogIndex之后的日志是否有冲突:

    • 如果entries[]中的任何一位置的日志发生冲突,那么需要将之后的日志进行截断,并追加为entries[]中之后的日志
    • 如果没有发生冲突,那么存在两种情况:

      • 跟随者的日志和entries[]的日志全部匹配,这种情况可能是重复的附加日志RPC,那么这种情况只会简单的校验一遍所有日志
      • entries[]的日志匹配当前的所有日志,那么将没有匹配的日志全都追加到当前的日志后面。

上述的步骤3是很重要的,如果不对现有的日志进行比对,而且简单的进行截断追加日志,那么是很危险,因为可能收到延时的重复日志附加请求而导致日志不必要的截断,从而导致已经提交的日志丢失。

另外还有一个优化点,如果存在大量的冲突日志,那么如果通过递减nextIndex[peer]将会很慢,所以可以通过批量跳过冲突日志方式来做到,可以再响应中添加conflictIndexconflictTerm来做到,这里不展开详细讨论。

日志的提交和应用

所有的服务器都有两个经常变动的值commitIndexlastAppliedcommitIndex表示已经提交的日志索引,而lastApplied表示最后Apply到状态机中的日志索引。commtiIndex会在日志成功附加到集群中的N/2+1的节点上更新。

一般应用日志到状态机中是通过一个独立的线程来做到的,通过监控是否有新提交的日志,如果有新提交的日志,那么就将日志Apply到状态机中,并且更新lastApplied。所以一般commitIndex >= lastApplied

在实际的应用中,一般将Raft单独作为独立的一层共识模块,上层模块将需要达到共识的指令下发给Raft共识模块,在Raft模块达到共识以后,就将达成共识的指令Apply到上层模块中。比如EtcdTiKV等等

查看原文

qeesung 发布了文章 · 4月19日

Raft 算法之日志复制

原文地址: https://qeesung.github.io/202...
Raft 论文地址:https://ramcloud.atlassian.ne...

Raft论文中分为三块:

  • 领导选举
  • 日志复制
  • 安全性

本文中主要介绍日志复制

领导人必须从客户端接收日志然后复制到集群中的其他节点,并且强制要求其他节点的日志保持和自己相同。

鉴于日志复制这一块比较复杂,可以结合下面两个网页来理解:

复制状态机

复制状态机通常都是基于复制日志实现的,每一个服务器存储一个包含一系列指令的日志,并且按照日志的顺序进行执行。如下图所示:

  1. 客户端请求服务器,请求的信息就是一系列的指明,比如PUT KEY VALUE
  2. 服务器在收到请求以后,将操作指令同步到所有的服务器中
  3. 服务器收到同步的指令以后,就将指令应用到状态机中
  4. 最后响应客户端操作成功

raft-state-machine.png

状态机是按照同步到服务器上的指令的顺序,一个一个的去Apply指令,所以指令的顺序很重要,如果指令Apply的顺序不一致,或者丢失部分指令,那么最终状态机的状态也会不一致。

而我们知道网络是不稳定的,比如延迟,分区,丢包,荣誉和乱序等错误。如果不保证状态机Apply指令完全一模一样,那么将会导致不一致的结果。而Raft的日志复制机制则能保证在发生上述网络问题的时候,所有的服务器都能同步到完全一模一样的日志,也就是说能保证每一个服务器的状态机Apply到完全一模一样的指令。

附加日志RPC

请求体结构

所有服务器上都维持的状态:

  • commitIndex:最大的已经被提交的日志索引
  • lastApplied:最后被应用到状态的日志条目索引

lastApplied应该小于等于commitIndex,因为只有在日志被提交以后才能被Apply到状态机中

领导人经常改变的:

  • nextIndex[]:对于每一个服务器,需要发送给他的下一个日志条目的索引值,初始化为当前领导人的最后的日志索引值加一
  • matchIndex[]:对于每一个服务器,已经复制给他的日志的最高索引值

nextIndex[]matchIndex[]的长度等于整个集群中的服务器数量。

附加日志RPC的请求结构:

  • term:附加日志的领导人任期号
  • leaderId:当前领导人的Id
  • preLogIndex:当前要附加的日志entries的上一条的日志索引
  • preLogTerm:当前要附加的日志entries的上一条的日志任期号
  • entries[]:需要附加的日志条目(心跳时为空)
  • leaderCommit:当前领导人已经提交的最大的日志索引值

preLogIndexpreLogTerm主要是用于跟随者检测当前领导人要附加的日志是否和跟随者当前的日志匹配,如果不匹配的话,那么就需要继续向前搜寻和领导人匹配的日志(下面章节会介绍)

leaderCommit的话用于告诉跟随者当前提交到什么位置了(因为收到附加日志还不能马上提交,否则可能存在日志丢失的情况),以便跟随者将已经提交的日志Apply到状态机中

附加日志RPC的响应结构:

  • term:跟随者的当前的任务号
  • success:跟随者是否接收了当前的日志,在preLogIndexpreLogTerm匹配的情况下为true,否则返回false

请求的流程

每一个日志条目存储一条状态机指令和从领导人收到这条指令时的任期号。日志中的任期号用来检查是否出现不一致的情况,每一条日志条目同时也都有一个整数索引值来表明它在日志中的位置。

raft-commit-entries.png

领导人附加日志

一旦成为领导人,那么领导人就会在固定在一定时间内发送空的附加日志,也就是心跳,以组织更随着超时。

如果领导人收到来自己客户端的请求,那么首先将请求的指令附加到自己的日志队列中,然后领导人会将新附加的日志条目通过附加日志的RPC发送到所有的跟随者。

领导人需要附加那些日志?

领导人中维护了两个经常变动的属性nextIndex[]matchIndex[],用于记录要发什么日志和跟随者收到了什么日志。

其中nextIndex[]记录了需要发送给每一个服务器的日志的下一个索引值,这个数组会在领导人被选举出来的时候初始化为领导人最后的索引加一。注意这个nextIndex[]只是记录了要发给下一次附加日志要发给服务器的索引值,这个索引值可能并不一定准确,比如在跟随者和领导人的日志不一致的情况下,nextIndex[]的值就需要进行递减以找到和跟随者最大的匹配的日志,具体流程后文中会解释。

matchIndex[]主要是用来记录跟随者收到了那些日志,以方便领导人确认是否当前的日志可以进行提交,因为必须要至少N/2+1的集群成员(包括领导人自己)都确认收到了日志才可以进行的日志的提交。

领导人收到收到跟随者附加日志的响应该如何处理?

我们知道领导人在附加日志的时候是并发的向所有跟随者发起的,并且是在固定周期内定时发送的,所以可能在上一个RPC没有收到响应的情况下,发出下一个RPC请求,或者是收到过期的日志追加请求等等。

  1. 比对响应的Term和当前的Term以确认自己是否过期:

    • 如果响应的Term大于当前的Term,那么说明当前的领导人已经过期,马上将自己切换为跟随者
    • 如果响应的Term小于当前的Term,那么说明当前的收到了过期的响应(可能网路延迟导致),那么忽略
    • 否则进行步骤2
  2. 判断附加RPC时的preLogIndex和当前的nextIndex[peer]-1 是否相等,用于判断是否是过期的响应,或者是nextIndex[peer]是否被更改了:

    • 如果不相等,那么直接忽略
    • 否则进行步骤3
  3. 判断响应的success是否为真:

    • 如果为真:那么说明附加日志成功,进行步骤4
    • 如果为假:那么说明附加日志失败,preLogIndexpreLogTerm和跟随者的日志不匹配,进行步骤5
  4. 附加日志成功以后,需要更新matchIndex[peer]中的值为已经追加日志的索引值,表示追加日志成功,判断整个matchIndex[]数组中的是否有一半都大于等于追加日志的索引:

    • 如果是:那么更新commitIndex,进行日志的提交
    • 否则跳过处理
  5. 日志不匹配,那么需要找到下一个和跟随者匹配的日志索引,简单一点可以通过递减nextIndex[peer]来实现。

跟随者接收日志

跟随者收到附加日志的请求,不能简单的将日志追加到自己的日志后面,因为跟随者的日志可能和领导人有冲突,或者跟随者缺失更多的日志,入下图所示。

raft-conflict-log.png

那么一定要确保本次附加日志的之前的所有日志都相同,也就是说附加当前的日志之前,缺日志就要把缺失的日志补上,日志冲突了,就要把冲突的日志覆盖(领导人可以强行覆盖跟随者的日志)

  1. 判断附加日志任期Term和当前的Term是否相同:

    • 如果请求的Term小于当前的Term,那么说明收到了来自过期的领导人的附加日志请求,那么拒接处理。
    • 如果请求的Term大于当前的Term,那么更新当前的Term为请求的Term,进行步骤2
    • 如果请求的Term和当前的Term相等,那么说明请求合法,进行步骤2
  2. 判断preLogIndex是否大于当前的日志长度或者preLogIndex位置处的任期是否和preLogTerm相等以检测要附加的日志之前的日志是否匹配:

    • 如果preLogIndex的长度大于当前的日志的长度,那么说明跟随者缺失日志,那么拒绝附加日志,返回false
    • 如果preLogIndex处的任期和preLogTerm不相等,那么说明日志有冲突,拒绝附加日志,返回false
    • 否则说明之前的日志全都匹配,那么进行步骤3,检测preLogIndex之后的日志是否匹配。
  3. 逐一比对要附加的日志entries[]是否和自己preLogIndex之后的日志是否有冲突:

    • 如果entries[]中的任何一位置的日志发生冲突,那么需要将之后的日志进行截断,并追加为entries[]中之后的日志
    • 如果没有发生冲突,那么存在两种情况:

      • 跟随者的日志和entries[]的日志全部匹配,这种情况可能是重复的附加日志RPC,那么这种情况只会简单的校验一遍所有日志
      • entries[]的日志匹配当前的所有日志,那么将没有匹配的日志全都追加到当前的日志后面。

上述的步骤3是很重要的,如果不对现有的日志进行比对,而且简单的进行截断追加日志,那么是很危险,因为可能收到延时的重复日志附加请求而导致日志不必要的截断,从而导致已经提交的日志丢失。

另外还有一个优化点,如果存在大量的冲突日志,那么如果通过递减nextIndex[peer]将会很慢,所以可以通过批量跳过冲突日志方式来做到,可以再响应中添加conflictIndexconflictTerm来做到,这里不展开详细讨论。

日志的提交和应用

所有的服务器都有两个经常变动的值commitIndexlastAppliedcommitIndex表示已经提交的日志索引,而lastApplied表示最后Apply到状态机中的日志索引。commtiIndex会在日志成功附加到集群中的N/2+1的节点上更新。

一般应用日志到状态机中是通过一个独立的线程来做到的,通过监控是否有新提交的日志,如果有新提交的日志,那么就将日志Apply到状态机中,并且更新lastApplied。所以一般commitIndex >= lastApplied

在实际的应用中,一般将Raft单独作为独立的一层共识模块,上层模块将需要达到共识的指令下发给Raft共识模块,在Raft模块达到共识以后,就将达成共识的指令Apply到上层模块中。比如EtcdTiKV等等

查看原文

赞 1 收藏 1 评论 0

qeesung 收藏了文章 · 4月19日

Raft 算法之领导人选举

原文地址:https://qeesung.github.io/202...
Raft 论文地址:https://ramcloud.atlassian.ne...

Raft论文中分为三块:

  • 领导选举
  • 日志复制
  • 安全性

本文中主要介绍领导人选举

Raft中的节点状态

Raft中的节点有三种状态:

  • 领导人状态:Leader
  • 跟随者状态:Follower
  • 候选人状态:Candidate

每一个节点都是一个状态机,Raft会根据当前的心跳,任期等状态来进行状态的迁移转化,就如下图所示:

raft-role.png

首先,在Raft节点启动的时候,所有任务都是Follower状态, 因为此时没有Leader,所有Follower都在固定的超时时间内都收不到来自Leader的心跳,从而变成了Candidate状态,开始选举Leader

当节点处于Candidate状态的时候,会并发的向所有的节点发出请求投票请求RequestVote(后面章节会向详细介绍),在Candidate状态下,节点可能会发生三种状态的迁移变化:

  • 开始下一轮新的选举:发出的投票请求在固定时间内没有收到其他节点的响应,或者是收到响应(或者同意投票)的节点数量没有达到 N/2+1,那么选取超时,进入下一轮选举
  • 选举成功,成为新的Leader:如果选举过程中收到大于N/2+1数量的节点的投票,那么选举成功,当前的节点成为新的Leader
  • 成为Follower:如果选举过程中收到来及其他节点的Leader心跳,或者是请求投票响应的Term大于当前的节点Term,那么说明有新任期的Leader

如果节点选举成功,成为了Leader,那么Leader将会在固定周期内发送心跳到所有的节点,但是如果心跳请求收到的响应的Term大于当前节点的Term,那么当前节点的就会成为Follower。比如Leader节点的网络不稳定,掉线了一段时间,网络恢复的时候,肯定有其他节点被选为了新的Leader,但是当前节点在掉线的时候并没有发现其他节点选为Leader,仍然发送心跳给其他节点,其他节点就会把当前的新的Term响应给已经过时的Leader,从而转变成Follower

领导人选举

整个集群必须要在丢包,乱序,延时等诸多不稳定因素的情况下,能够选举出唯一一个Leader

请求投票RPC

就如上文中提到的,如果Follower在一定时间内没有收到心跳请求,那么将会切换到Candidate状态,开始一轮新的选取,选举过程中会向集群中的所有节点发送请求投票的RPC

RPC请求参数:

  • term:当前候选人的任期号
  • candidateId:候选人的Id
  • lastLogIndex:候选人的最后日志条目索引值
  • lastLogTerm:候选人的最后日志条目的任期号

其中lastLogIndexlastLogTerm用来判断候选人的日志是否和服务器的日志一样新(后文中会解释),必须要至少一样新才能投票。

RPC响应值:

  • term:被请求节点的任期号
  • voteGranted:是否同意投票给候选人

Candidate发送请求投票RPC

Candidate何时发送请求投票RPC?

如果Leader发生异常,那么基本上所有的Follower在同一时间切换为Candidate,并同时发出请求投票的RPC,那么就有可能导致选票被均衡的瓜分,从而需要重新发起新一轮的投票。为了避免选票被瓜分的问题,选举超时的是可以可以从一个固定的区间(例如150-300毫秒)随机选择。

Candidate如何发送投票RPC?
  1. 自增当前节点的任期号
  2. 给自己投票
  3. 重置选举超时计时器
  4. 发送请求投票的RPC给其他服务器
节点收到请求投票的RPC该如何处理?
  1. 判断当前的Term的和请求投票参数中的Term

    • 如果当前的Term > 请求投票参数中的Term,那么拒绝投票(设置voteGrantedfalse),并返回当前的Term
    • 否则就更新当前Term为请求投票参数中的Term, 并将自身状态切换成Follower
  2. 检测当前节点的投票状态:

    • 如果当前的节点没有给任何其他节点投过票,或者是已经投过票给当前节点,那么继续检测日志的匹配状态(步骤3)
    • 否则,那么拒绝投票(设置voteGrantedfalse), 因为一个节点在一个任期内不能同时投票给多个节点
  3. 检测候选人的日志是否至少比当前节点的日志新,通过比较候选人的lastLogIndexlastLogTerm和当前节点的日志,确保新选举出来的Leader不会丢失已经提交的日志:

    • 如果日志匹配,即当前的任期和候选人的任期相同,且候选人的日志长度比当前的日志长度 或者 候选人的任期比比当前节点的任期高,那么就为候选人投票(设置votedGrantedtrue),并成为Follower
    • 否则,那么就拒绝投票(设置voteGrantedfalse
Candidate收到请求投票的响应该如何处理?

每一个候选人在每一个任期内都会发出一轮投票请求,如果在指定时间内,收到大于N/2+1个节点的同意投票的响应,那么说明投票成功,晋升为Leader

因为在整个投票过程中,假设网络是不稳定的,那么就有可能导致投票请求和请求的响应丢失,乱序,延时等,从而导致收到和当前任期不相匹配的响应,所以如果收到和当前任期不匹配的响应,那么就直接丢弃不处理。

完整的处理流程如下:

  1. 检查响应的Term是否大于当前候选人的Term

    • 如果是,那么说明有其他节点开始了新一轮的选举或者是有新的Leader被选举出来,那么就把当前节点从Candidate切换为Follower状态,并更新当前节点的Term
    • 否则,进行步骤2
  2. 检查响应的Term是否和当前的节点的Term是否相等:

    • 如果相等,那么说明在指定时间内收到了投票请求的响应,那么就进行步骤3
    • 否则说明这是一个过期的投票请求响应,直接丢弃
  3. 检查响应是否同意投票:

    • 如果同意,那么增加当前任期的同意投票节点数量,并检查同意投票的节点数量大于 N/2+1,那么就切换为Leader
    • 如果不同意,可能是日志不匹配,因为Leader的日志至少要比Follower的日志新

领导人选举的安全性

从上文的请求投票RPC的处理流程中得知,Leader不是随便选一个节点都可以成为的,如果候选人不满足要求,那么其他节点就不会给候选人投票。

如果集群中的任何一个节点不经过判断就能成为Leader,那么将会发生什么?这种情况可能导致已经被提交的日志被覆盖,如果状态机已经Apply了被覆盖的日志,将会导致不一致的结果。所以为了选举的安全性,Raft添加了以下的限制:

  1. Leader不会覆盖自己的任何日志,Follower严格按照Leader的日志进行复制(必要时强行覆盖)
  2. 选举Leader的时候,Candidate的日志至少要比当前节点新(这个“新”稍后解释),否则就拒绝投票;因为已经提交的日志肯定是存在大于等于N/2+1个节点上的,而投票至少也需要N/2+1个节点同意,所以整个投票过程中肯定会有包含有所有已经提交日志的节点存在的。

上文中的“新”就是:即当前的任期和候选人的任期相同,且候选人的日志长度比当前的日志长度 或者 候选人的任期比比当前节点的任期高

查看原文

qeesung 收藏了文章 · 4月17日

Raft 算法之领导人选举

原文地址:https://qeesung.github.io/202...
Raft 论文地址:https://ramcloud.atlassian.ne...

Raft论文中分为三块:

  • 领导选举
  • 日志复制
  • 安全性

本文中主要介绍领导人选举

Raft中的节点状态

Raft中的节点有三种状态:

  • 领导人状态:Leader
  • 跟随者状态:Follower
  • 候选人状态:Candidate

每一个节点都是一个状态机,Raft会根据当前的心跳,任期等状态来进行状态的迁移转化,就如下图所示:

raft-role.png

首先,在Raft节点启动的时候,所有任务都是Follower状态, 因为此时没有Leader,所有Follower都在固定的超时时间内都收不到来自Leader的心跳,从而变成了Candidate状态,开始选举Leader

当节点处于Candidate状态的时候,会并发的向所有的节点发出请求投票请求RequestVote(后面章节会向详细介绍),在Candidate状态下,节点可能会发生三种状态的迁移变化:

  • 开始下一轮新的选举:发出的投票请求在固定时间内没有收到其他节点的响应,或者是收到响应(或者同意投票)的节点数量没有达到 N/2+1,那么选取超时,进入下一轮选举
  • 选举成功,成为新的Leader:如果选举过程中收到大于N/2+1数量的节点的投票,那么选举成功,当前的节点成为新的Leader
  • 成为Follower:如果选举过程中收到来及其他节点的Leader心跳,或者是请求投票响应的Term大于当前的节点Term,那么说明有新任期的Leader

如果节点选举成功,成为了Leader,那么Leader将会在固定周期内发送心跳到所有的节点,但是如果心跳请求收到的响应的Term大于当前节点的Term,那么当前节点的就会成为Follower。比如Leader节点的网络不稳定,掉线了一段时间,网络恢复的时候,肯定有其他节点被选为了新的Leader,但是当前节点在掉线的时候并没有发现其他节点选为Leader,仍然发送心跳给其他节点,其他节点就会把当前的新的Term响应给已经过时的Leader,从而转变成Follower

领导人选举

整个集群必须要在丢包,乱序,延时等诸多不稳定因素的情况下,能够选举出唯一一个Leader

请求投票RPC

就如上文中提到的,如果Follower在一定时间内没有收到心跳请求,那么将会切换到Candidate状态,开始一轮新的选取,选举过程中会向集群中的所有节点发送请求投票的RPC

RPC请求参数:

  • term:当前候选人的任期号
  • candidateId:候选人的Id
  • lastLogIndex:候选人的最后日志条目索引值
  • lastLogTerm:候选人的最后日志条目的任期号

其中lastLogIndexlastLogTerm用来判断候选人的日志是否和服务器的日志一样新(后文中会解释),必须要至少一样新才能投票。

RPC响应值:

  • term:被请求节点的任期号
  • voteGranted:是否同意投票给候选人

Candidate发送请求投票RPC

Candidate何时发送请求投票RPC?

如果Leader发生异常,那么基本上所有的Follower在同一时间切换为Candidate,并同时发出请求投票的RPC,那么就有可能导致选票被均衡的瓜分,从而需要重新发起新一轮的投票。为了避免选票被瓜分的问题,选举超时的是可以可以从一个固定的区间(例如150-300毫秒)随机选择。

Candidate如何发送投票RPC?
  1. 自增当前节点的任期号
  2. 给自己投票
  3. 重置选举超时计时器
  4. 发送请求投票的RPC给其他服务器
节点收到请求投票的RPC该如何处理?
  1. 判断当前的Term的和请求投票参数中的Term

    • 如果当前的Term > 请求投票参数中的Term,那么拒绝投票(设置voteGrantedfalse),并返回当前的Term
    • 否则就更新当前Term为请求投票参数中的Term, 并将自身状态切换成Follower
  2. 检测当前节点的投票状态:

    • 如果当前的节点没有给任何其他节点投过票,或者是已经投过票给当前节点,那么继续检测日志的匹配状态(步骤3)
    • 否则,那么拒绝投票(设置voteGrantedfalse), 因为一个节点在一个任期内不能同时投票给多个节点
  3. 检测候选人的日志是否至少比当前节点的日志新,通过比较候选人的lastLogIndexlastLogTerm和当前节点的日志,确保新选举出来的Leader不会丢失已经提交的日志:

    • 如果日志匹配,即当前的任期和候选人的任期相同,且候选人的日志长度比当前的日志长度 或者 候选人的任期比比当前节点的任期高,那么就为候选人投票(设置votedGrantedtrue),并成为Follower
    • 否则,那么就拒绝投票(设置voteGrantedfalse
Candidate收到请求投票的响应该如何处理?

每一个候选人在每一个任期内都会发出一轮投票请求,如果在指定时间内,收到大于N/2+1个节点的同意投票的响应,那么说明投票成功,晋升为Leader

因为在整个投票过程中,假设网络是不稳定的,那么就有可能导致投票请求和请求的响应丢失,乱序,延时等,从而导致收到和当前任期不相匹配的响应,所以如果收到和当前任期不匹配的响应,那么就直接丢弃不处理。

完整的处理流程如下:

  1. 检查响应的Term是否大于当前候选人的Term

    • 如果是,那么说明有其他节点开始了新一轮的选举或者是有新的Leader被选举出来,那么就把当前节点从Candidate切换为Follower状态,并更新当前节点的Term
    • 否则,进行步骤2
  2. 检查响应的Term是否和当前的节点的Term是否相等:

    • 如果相等,那么说明在指定时间内收到了投票请求的响应,那么就进行步骤3
    • 否则说明这是一个过期的投票请求响应,直接丢弃
  3. 检查响应是否同意投票:

    • 如果同意,那么增加当前任期的同意投票节点数量,并检查同意投票的节点数量大于 N/2+1,那么就切换为Leader
    • 如果不同意,可能是日志不匹配,因为Leader的日志至少要比Follower的日志新

领导人选举的安全性

从上文的请求投票RPC的处理流程中得知,Leader不是随便选一个节点都可以成为的,如果候选人不满足要求,那么其他节点就不会给候选人投票。

如果集群中的任何一个节点不经过判断就能成为Leader,那么将会发生什么?这种情况可能导致已经被提交的日志被覆盖,如果状态机已经Apply了被覆盖的日志,将会导致不一致的结果。所以为了选举的安全性,Raft添加了以下的限制:

  1. Leader不会覆盖自己的任何日志,Follower严格按照Leader的日志进行复制(必要时强行覆盖)
  2. 选举Leader的时候,Candidate的日志至少要比当前节点新(这个“新”稍后解释),否则就拒绝投票;因为已经提交的日志肯定是存在大于等于N/2+1个节点上的,而投票至少也需要N/2+1个节点同意,所以整个投票过程中肯定会有包含有所有已经提交日志的节点存在的。

上文中的“新”就是:即当前的任期和候选人的任期相同,且候选人的日志长度比当前的日志长度 或者 候选人的任期比比当前节点的任期高

查看原文

qeesung 发布了文章 · 4月16日

Raft 算法之领导人选举

原文地址:https://qeesung.github.io/202...
Raft 论文地址:https://ramcloud.atlassian.ne...

Raft论文中分为三块:

  • 领导选举
  • 日志复制
  • 安全性

本文中主要介绍领导人选举

Raft中的节点状态

Raft中的节点有三种状态:

  • 领导人状态:Leader
  • 跟随者状态:Follower
  • 候选人状态:Candidate

每一个节点都是一个状态机,Raft会根据当前的心跳,任期等状态来进行状态的迁移转化,就如下图所示:

raft-role.png

首先,在Raft节点启动的时候,所有任务都是Follower状态, 因为此时没有Leader,所有Follower都在固定的超时时间内都收不到来自Leader的心跳,从而变成了Candidate状态,开始选举Leader

当节点处于Candidate状态的时候,会并发的向所有的节点发出请求投票请求RequestVote(后面章节会向详细介绍),在Candidate状态下,节点可能会发生三种状态的迁移变化:

  • 开始下一轮新的选举:发出的投票请求在固定时间内没有收到其他节点的响应,或者是收到响应(或者同意投票)的节点数量没有达到 N/2+1,那么选取超时,进入下一轮选举
  • 选举成功,成为新的Leader:如果选举过程中收到大于N/2+1数量的节点的投票,那么选举成功,当前的节点成为新的Leader
  • 成为Follower:如果选举过程中收到来及其他节点的Leader心跳,或者是请求投票响应的Term大于当前的节点Term,那么说明有新任期的Leader

如果节点选举成功,成为了Leader,那么Leader将会在固定周期内发送心跳到所有的节点,但是如果心跳请求收到的响应的Term大于当前节点的Term,那么当前节点的就会成为Follower。比如Leader节点的网络不稳定,掉线了一段时间,网络恢复的时候,肯定有其他节点被选为了新的Leader,但是当前节点在掉线的时候并没有发现其他节点选为Leader,仍然发送心跳给其他节点,其他节点就会把当前的新的Term响应给已经过时的Leader,从而转变成Follower

领导人选举

整个集群必须要在丢包,乱序,延时等诸多不稳定因素的情况下,能够选举出唯一一个Leader

请求投票RPC

就如上文中提到的,如果Follower在一定时间内没有收到心跳请求,那么将会切换到Candidate状态,开始一轮新的选取,选举过程中会向集群中的所有节点发送请求投票的RPC

RPC请求参数:

  • term:当前候选人的任期号
  • candidateId:候选人的Id
  • lastLogIndex:候选人的最后日志条目索引值
  • lastLogTerm:候选人的最后日志条目的任期号

其中lastLogIndexlastLogTerm用来判断候选人的日志是否和服务器的日志一样新(后文中会解释),必须要至少一样新才能投票。

RPC响应值:

  • term:被请求节点的任期号
  • voteGranted:是否同意投票给候选人

Candidate发送请求投票RPC

Candidate何时发送请求投票RPC?

如果Leader发生异常,那么基本上所有的Follower在同一时间切换为Candidate,并同时发出请求投票的RPC,那么就有可能导致选票被均衡的瓜分,从而需要重新发起新一轮的投票。为了避免选票被瓜分的问题,选举超时的是可以可以从一个固定的区间(例如150-300毫秒)随机选择。

Candidate如何发送投票RPC?
  1. 自增当前节点的任期号
  2. 给自己投票
  3. 重置选举超时计时器
  4. 发送请求投票的RPC给其他服务器
节点收到请求投票的RPC该如何处理?
  1. 判断当前的Term的和请求投票参数中的Term

    • 如果当前的Term > 请求投票参数中的Term,那么拒绝投票(设置voteGrantedfalse),并返回当前的Term
    • 否则就更新当前Term为请求投票参数中的Term, 并将自身状态切换成Follower
  2. 检测当前节点的投票状态:

    • 如果当前的节点没有给任何其他节点投过票,或者是已经投过票给当前节点,那么继续检测日志的匹配状态(步骤3)
    • 否则,那么拒绝投票(设置voteGrantedfalse), 因为一个节点在一个任期内不能同时投票给多个节点
  3. 检测候选人的日志是否至少比当前节点的日志新,通过比较候选人的lastLogIndexlastLogTerm和当前节点的日志,确保新选举出来的Leader不会丢失已经提交的日志:

    • 如果日志匹配,即当前的任期和候选人的任期相同,且候选人的日志长度比当前的日志长度 或者 候选人的任期比比当前节点的任期高,那么就为候选人投票(设置votedGrantedtrue),并成为Follower
    • 否则,那么就拒绝投票(设置voteGrantedfalse
Candidate收到请求投票的响应该如何处理?

每一个候选人在每一个任期内都会发出一轮投票请求,如果在指定时间内,收到大于N/2+1个节点的同意投票的响应,那么说明投票成功,晋升为Leader

因为在整个投票过程中,假设网络是不稳定的,那么就有可能导致投票请求和请求的响应丢失,乱序,延时等,从而导致收到和当前任期不相匹配的响应,所以如果收到和当前任期不匹配的响应,那么就直接丢弃不处理。

完整的处理流程如下:

  1. 检查响应的Term是否大于当前候选人的Term

    • 如果是,那么说明有其他节点开始了新一轮的选举或者是有新的Leader被选举出来,那么就把当前节点从Candidate切换为Follower状态,并更新当前节点的Term
    • 否则,进行步骤2
  2. 检查响应的Term是否和当前的节点的Term是否相等:

    • 如果相等,那么说明在指定时间内收到了投票请求的响应,那么就进行步骤3
    • 否则说明这是一个过期的投票请求响应,直接丢弃
  3. 检查响应是否同意投票:

    • 如果同意,那么增加当前任期的同意投票节点数量,并检查同意投票的节点数量大于 N/2+1,那么就切换为Leader
    • 如果不同意,可能是日志不匹配,因为Leader的日志至少要比Follower的日志新

领导人选举的安全性

从上文的请求投票RPC的处理流程中得知,Leader不是随便选一个节点都可以成为的,如果候选人不满足要求,那么其他节点就不会给候选人投票。

如果集群中的任何一个节点不经过判断就能成为Leader,那么将会发生什么?这种情况可能导致已经被提交的日志被覆盖,如果状态机已经Apply了被覆盖的日志,将会导致不一致的结果。所以为了选举的安全性,Raft添加了以下的限制:

  1. Leader不会覆盖自己的任何日志,Follower严格按照Leader的日志进行复制(必要时强行覆盖)
  2. 选举Leader的时候,Candidate的日志至少要比当前节点新(这个“新”稍后解释),否则就拒绝投票;因为已经提交的日志肯定是存在大于等于N/2+1个节点上的,而投票至少也需要N/2+1个节点同意,所以整个投票过程中肯定会有包含有所有已经提交日志的节点存在的。

上文中的“新”就是:即当前的任期和候选人的任期相同,且候选人的日志长度比当前的日志长度 或者 候选人的任期比比当前节点的任期高

查看原文

赞 0 收藏 1 评论 4

qeesung 赞了文章 · 2019-11-25

🔥如何写一篇技术博客,谈谈我的看法

前言

只有光头才能变强。

文本已收录至我的GitHub精选文章,欢迎Starhttps://github.com/ZhongFuCheng3y/3y

我一直推崇学技术可以写技术博客去沉淀自己的知识,因为知识点实在是太多太多了,通过自己的博客可以帮助自己快速回顾自己学过的东西。

我最开始的时候也是只记笔记,认为自己能看得懂就好。但如果想验证自己是不是懂了,可以写成技术博客。在写技术博客的过程中肯定会发现:“这个知识点,我好像还有些没搞懂”。

从梳理/编写的过程,自己也会成长不少

有不少的小伙伴曾经问过我:

  • “3y你是怎么记笔记的阿?我一边看视频一边记笔记,顾头不顾腚的”
  • “你写的博客都是怎么思考的阿?我不会写博客”

不会写技术博客/笔记

下面来谈谈我一些观点(个人/主观),每个人都有自己的方法论,我的观点不适用于所有人。

开局一张图,内容全靠编:

一、如何写一篇技术博客

首先,我认为要把自己当做是一个分享者,而读者是一个小白。然后模拟这个场景:如果你要把你学习到的技术分享给小白,你需要怎么去做。

按照我的思路,我可能会这样做:

  • 首先,这项技术是什么你得先告诉他。
  • 接着,为什么要学习这项技术,学习了这项技术有什么好处。有没有以前的技术跟这个较为相似的,为什么我不用以前的,而要学新的。(这一步非常非常重要
  • 然后,这项技术的核心用法是什么,给出一些小案例,让小白体验到这项技术。
  • 最后,用这项技术可能会出现什么问题,官方有没有提供方案解决,如果没有,可能的解决方案有哪些。

大白话来说,其实就是:

  • 是什么
  • 为什么
  • 怎么做

一般来说,我会把重点放在 为什么 ,因为我一直认为学习一门技术一定得知道:为什么要学

举个例子,我当初写【消息队列】的思路:

  • 回顾一下什么是队列,Java已经支持各种类型的队列了,跟消息队列有什么不同。
  • 为什么要用消息队列,不用行不行?用消息队列的好处有哪些
  • 用了消息队列可能会带来的问题

如果遇到逻辑可能较为复杂,或者认为读者会看不太懂的时候,可以画图来描述一下,这样整一篇文章看下来就不会太枯燥。

其实吧,我写文章都是按自己学习时的思路来写。如果我在学习时中途某个地方卡住了,我就认为可能读者在学习的时候也会存在同样的问题。所以,我就把我的理解给记录下来,如果有必要就画图来讲解。

我写了两百多篇Java技术博客,有需要的同学可以关注我的GitHub,欢迎一起学习和交流:https://github.com/ZhongFuCheng3y/3y

关注我的公众号,看一下我是怎么写技术博客的

二、文章需要有自己的风格

每个人写出来的博客都有自己的风格。

比如说有漫画型的:

比如说有故事型的:

故事型讲解

比如说有骚气型的:

骚气型

比如说有纯干货型的:

纯干货

说了这么多,其实我想说的是:写博客应该要带有属于自己的风格。而不是把网上的资料直接复制粘贴就完事了,这样没有感情,没有灵魂。(当然了,如果网上可能的确有写得很好的,可以这么做,但不可能整一篇都是相同的嘛)

三、关于排版

一篇好的技术文章,它的排版一般不会差。我认为写技术文章有几点可以提高一下阅读体验

  1. 整篇文章不要90%都是代码,适当减少代码,贴关键的部分就好了。(完整的代码可以上传到GitHub)
  2. 多分段,技术文章可能就相对枯燥,如果将技术的说明挤在一块,可能阅读起来没那么好的体验
  3. 增加图示说明 or 插入相关的图片

程序员写文章一般都用Markdown语法了,使用恰当的话,文章的排版自然也不会太差啦。所以还没用Markdown语法的同学可以赶紧用起来,这玩意特好学,从学到用起来就几分钟。

  • 我当时连简历都是Markdown语法...

四、关于工具

Markdown编辑器我推荐使用Typora,无论是在Mac还是在Windows上都很好用。使用Markdown就肯定要考虑一个问题:图床用哪家

无论使用哪家图都有可能会挂,自己搞个图床可能会安全一些(相对来说会比较麻烦),我自己是使用掘金的图床的。而我的文章往往会分发到几个博客网站上,像简书/知乎这种会单独将图片上传到他们的服务器上。

所以,从我个人的使用角度而言,并不会担心图床会挂问题。如果你将文章发送到一个平台中,还是得关注一下图床是否可能失效的问题。

至于你发哪个平台,我曾经写过一篇【程序员可以选择哪些平台写技术博客?】,当时的总结:

  • 如果不嫌弃博客园的风格,可以选择博客园。否则建议选择:掘金/SegmentFault
  • 只想管理自己写过的文章,选择:GitHub/GitBook
  • 喜欢折腾:Hexo+GitHub或者WordPress

简单的流程图/思维导图/..可以使用ProcessOn就解决掉了。

最后

有的没的扯了一些,希望能够对「有想要写技术博客,但无从下手的同学」一些帮助。

本已收录至我的GitHub精选文章,欢迎Starhttps://github.com/ZhongFuCheng3y/3y

乐于输出干货的Java技术公众号:Java3y。公众号内有300多篇原创技术文章、海量视频资源、精美脑图,关注即可获取!

转发到朋友圈是对我最大的支持!

非常感谢人才们能看到这里,如果这个文章写得还不错,觉得「三歪」我有点东西的话 求点赞求关注️求分享👥求留言💬 对暖男我来说真的 非常有用!!!

创作不易,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!

查看原文

赞 12 收藏 3 评论 0

认证与成就

  • 获得 149 次点赞
  • 获得 7 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 7 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

注册于 2015-01-07
个人主页被 4.8k 人浏览