[TOC]
**本文思維導圖**

### 前言
為了高可用和數據安全起見,zk集群一般都是由幾個節點構成(由n/2+1,投票機制決定,肯定是奇數個節點)。多節點證明它們之間肯定會有數據的通信,同時,為了能夠使zk集群對外是透明的,一個整體對外提供服務,那么客戶端訪問zk服務器的數據肯定是要數據同步,也即**數據一致性**。
zk集群是Leader/Follower模式來保證數據同步的。整個集群同一時刻只能有一個Leader,其他都是Follower或Observer。Leader是通過選舉選出來的,這里涉及到ZAB協議(原子消息廣播協議)。
### 1.ZAB協議
#### 1.1 概念理解
為了更好理解下文,先說ZAB協議,它是選舉過程和數據寫入過程的基石。ZAB的核心是定義會改變zk服務器數據狀態的事務請求的處理方式。
ZAB的理解:所有事務請求是由一個全局唯一的服務器來協調處理,這個的服務器就是Leader服務器,
其它服務器都是Follower服務器或Observer服務器。Leader服務器負責將一個客戶端的請求轉換成那個一個**事務Proposal?(提議)**,將該Proposal分發給集群中所有的Follower服務器。然后Leader服務器需要等待所有Follower服務器的應答,當Leader服務器收到超過**半數**的Follower服務器進行了明確的應答后,Leader會再次向所有的Follower服務器分發Commit消息,要求其將前一個Proposal進行提交。
注意**事務提議**這個詞,就類似 **人大代表大會提議** ,提議就代表會有應答,之間有通信。因此在zk的ZAB協議為了可靠性和可用性,會有**投票**,**應答**等操作來保證整個zk集群的正常運行。
總的來說就是,涉及到客戶端對zk集群數據改變的行為都先由Leader統一響應,然后再把請求轉換為事務轉發給其他所有的Follower,Follower應答并處理事務,最后再反饋。如果客戶端只是讀請求,那么zk集群所有的節點都可以響應這個請求。
#### 1.2 ZAB協議三個階段
* 1.發現(選舉Leader過程)
* 2.同步(選出Leader后,Follower和Observer需進行數據同步)
* 3.廣播(同步之后,集群對外工作響應請求,并進行消息廣播,實現數據在集群節點的副本存儲)
下面會逐點分析,但是在這之前先來了解了解zookeeper服務器的知識吧。
### 2.Zookeeper服務器
#### 2.1 zk服務器角色
* Leader
* 事務請求的唯一調度和處理者,保證集群事務處理的順序序性
* 集群內部各服務器的調度者
* Follower
* 處理客戶端非事務請求,轉發事務請求給Leader服務器
* 參與事務請求Proposal的投票
* 參與Leader的選舉投票
* Observer
* 處理客戶端非事務請求,轉發事務請求給Leader服務器
* 不參加任何形式的投票,包括選舉和事務投票(超過半數確認)
* Observer的存在是為了提高zk集群對外提供讀性能的能力
整個zk集群的角色作用如下圖:

#### 2.2 zk服務器狀態
* LOOKING
* 尋找Leader狀態
* 當服務器處于這種狀態時,表示當前沒有Leader,需要進入選舉流程
* FOLLOWING
* 從機狀態,表明當前服務器角色是Follower
* OBSERVING
* 觀察者狀態,表明當前服務器角色是Observer
* LEADING
* 領導者狀態,表明當前服務器角色是Leader
* ServerState 類維護服務器四種狀態。

zk服務器的狀態是隨著機器的變化而變化的。比如Leader宕機了,服務器狀態就變為LOOKING,通過選舉后,某機器成為Leader,服務器狀態就轉換為LEADING。其他情況類似。
#### 2.3 zk服務器通信
集群嘛,節點之間肯定是要通信的。zokeeper通信有兩個特點:
* 1.使用的通信協議是**TCP協議**。在集群中到底是怎么連接的呢?還記得在配置zookeeper時要創建一個data目錄并在其他創建一個myid文件并寫入唯一的數字嗎?zk服務器的TCP連接方向就是依賴這個myid文件里面的數字大小排列。數小的向數大的發起TCP連接。比如有3個節點,myid文件內容分別為1,2,3。zk集群的tcp連接順序是1向2發起TCP連接,2向3發起TCP連接。如果有n個節點,那么tcp連接順序也以此類推。這樣整個zk集群就會連接起來。
* 2.zk服務器是多端口的。例如配置如下:
~~~jsx
tickTime=2000
dataDir=/home/liangjf/app/zookeeper/data
dataLogDir=/home/liangjf/app/zookeeper/log
clientPort=2181
initLimit=5
syncLimit=2
server.1=192.168.1.1:2888:3888
server.2=192.168.1.2:2888:3888
server.3=192.168.1.3:2888:3888
~~~
* 第1個端口是通信和數據同步端口,默認是2888
* 第2個端口是投票端口,默認是3888
### 3.選舉機制
#### 3.1 選舉算法
從zookeeper開始發布以來,選舉的算法也慢慢優化。現在為了可靠性和高可用,從3.4.0版本開始zookeeper只支持基于**Tcp**的**FastLeaderElection**選舉協議。
* LeaderElection
* Udp協議
* AuthFastLeaderElection
* udp
* **FastLeaderElection**
* Udp
* **Tcp**
**FastLeaderElection選舉協議**使用TCP實現**Leader投票選舉算法**。它使用了類對象`quorumcnxmanager`管理連接。該算法是基于推送的,可以通過調節參數來改變選舉的過程。第一,`finalizewait`決定等到決定Leader的時間。這是Leader選舉算法的一部分。
`final static int finalizeWait = 200;`(選舉Leader過程的進程時間)
`final static int maxNotificationInterval = 60000;`(通知檢查選中Leader的時間間隔)
`final static int IGNOREVALUE = -1;`
這里先不詳細分析,下面**3.5 選舉算法源碼分析及舉栗子**才分析。
#### 3.2 何時觸發選舉
選舉Leader不是隨時選舉的,畢竟選舉有產生大量的通信,造成網絡IO的消耗。因此下面情況才會出現選舉:
* 集群啟動
* 服務器處于尋找Leader狀態
* 當服務器處于LOOKING狀態時,表示當前沒有Leader,需要進入選舉流程
* 崩潰恢復
* Leader宕機
* 網絡原因導致過半節點與Leader心跳中斷
#### 3.3 如何成為Leader
* 數據新舊程度
* 只有擁有最新數據的節點才能有機會成為Leader
* 通過zxid的大小來表示數據的新,zxid越大代表數據越新
* myid
* 集群啟動時,會在data目錄下配置myid文件,里面的數字代表當前zk服務器節點的編號
* 當zk服務器節點數據一樣新時, myid中數字越大的就會被選舉成ОLeader
* 當集群中已經有Leader時,新加入的節點不會影響原來的集群
* 投票數量
* 只有得到集群中多半的投票,才能成為Leader
* 多半即:n/2+1,其中n為集群中的節點數量
#### 3.4 重要的zxid
由3.3知道zxid是判斷能否成為Leader的條件之一,它代表服務器的數據版本的新舊程度。
zxid由兩部分構成:主進程周期epoch和事務單調遞增的計數器。zxid是一個64位的數,高32位代表**主進程周期epoch**,低32位代表**事務單調遞增的計數器**。
**主進程周期epoch**也叫epoch,是選舉的輪次,每選舉一次就遞增1。**事務單調遞增的計數器**在每次選舉完成之后就會從0開始。
如果是比較數據新舊的話,直接比較就可以了。因為如果是主進程周期越大,即高32位越大,那么低32位就不用再看了。如果主進程周期一致,低32位越大,整個zxid就越大。所以直接比較整個64位就可以了,不必高32位于高32位對比,低32位與低32位比較。
#### 3.5 選舉算法源碼分析及舉栗子
##### 3.5.1 舉栗子
zookeeper選舉有兩種情況:
* 1.集群首次啟動
* 2.集群在工作時Leader宕機
選主原則如下(在選舉時,對比次序是從上往下)
* 1.`New epoch is higher`
* 主周期更大,代所有一切是最新,就成為leader
* 2.`New epoch is the same as current epoch, but new zxid is higher`
* 主周期一致就是在同一輪選票中,zxid越大就成為leader,因為數據更新
* 3.`New epoch is the same as current epoch, new zxid is the same as current zxid, but server id is higher`
* 主周期和zxid一致,就看機器的id(myid),myid越大就成為leader
同時,在選舉的時候是投票方式進行的,除主進程周期外,投票格式為(myid,zxid)。
第一種情況,比較容易理解,下面以3臺機器為例子。
* 三個zk節點A,B,C,三者開始都沒有數據,即Zxid一致,對應的myid為1,2,3。
* A啟動myid為1的節點,zxid為0,此時只有一臺服務器無法選舉出Leader
* B啟動myid為2的節點,zxid為0,B的zxid與A一樣,比較myid,B的myid為2比A為1大,B成Leader
* C啟動myid為3的節點,因為已經有Leader節點,則C直接加入集群,承認B是leader
第二種情況,已5臺機器為例子。
* 五個節點A,B,C,D,E,B是Leader,其他是Follower,myid分別為1,2,3,4,5,zxid分別為3,4,5,6,6。運行到某個時刻時A,B掉線或宕機,此時剩下C D E。在同一輪選舉中,C,D,E分別投自己和交叉投票。
* 第一次投票,都是投自己。
* 投票情況為:C:(3,5) D:(4,6) E:(5,6)。
* 同時也會收到其他機器的投票。
* 投票情況為:C:(3,5)(4,6)(5,6),D:(4,6)(3,5)(5,6),E:(5,6)(4,6)(3,5)
* 機器內部會根據選主原則對比投票,變更投票,投票情況為:C:(3,5)(4,6)(5,6)【不變更】。 D:(4,6)(4,6)(5,6)【變更】。E:(5,6)(5,6)(5,6)【變更】
* 統計票數,C-1票,D-3票,E-5票。因此E成為Leader。
接下來就是對新Leader節點的檢查,數據同步,廣播,對外提供服務。
##### 3.5.1 選舉算法源碼分析
選舉算法的全部代碼在`FastLeaderElection`類中。其他的`lookForLeader`函數是選舉Leader的入口函數。
~~~java
//每一輪選舉就會增大一次邏輯時鐘,同時更新事務
synchronized(this){
logicalclock++;
updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch());
}
~~~
//一直循環選舉直到找到leader,這里把打印和不相關的都刪除了,方便分析。
~~~php
while ((self.getPeerState() == ServerState.LOOKING) &&
(!stop)){
//從通知隊列拉取一個投票通知
Notification n = recvqueue.poll(notTimeout,
TimeUnit.MILLISECONDS);
if(n == null){
//看是否選舉時通知發送/接收超時
int tmpTimeOut = notTimeout*2;
notTimeout = (tmpTimeOut < maxNotificationInterval?
tmpTimeOut : maxNotificationInterval);
}
else if(self.getVotingView().containsKey(n.sid)) {
switch (n.state) {
case LOOKING://只有zk服務器狀態為LOOKING時才會進行選舉
// If notification > current, replace and send messages out
if (n.electionEpoch > logicalclock) {
//如果選舉時的邏輯時鐘大于發送通知來源的機器的邏輯時鐘,就把對方的修改為自己的。
logicalclock = n.electionEpoch;
recvset.clear();
//并統計票數,如果能成為leader就更新事務
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) {
//如果通知來演的機器的邏輯時鐘比本次我的選舉時鐘低,直接返回,什么都不做。因為對方沒機會成為leader
if(LOG.isDebugEnabled()){
LOG.debug("Notification election epoch is smaller than logicalclock. n.electionEpoch = 0x"
+ Long.toHexString(n.electionEpoch)
+ ", logicalclock=0x" + Long.toHexString(logicalclock));
}
break;
} else if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
proposedLeader, proposedZxid, proposedEpoch)) {
//如果Epoch一樣,就看zxid的比較。不過還是會更新事務和回傳通知
updateProposal(n.leader, n.zxid, n.peerEpoch);
sendNotifications();
}
//把所有接收到的投票信息都放到recvset集合
recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));
//統計誰的投票超過半數,就成為leader
if (termPredicate(recvset,
new Vote(proposedLeader, proposedZxid,
logicalclock, proposedEpoch))) {
//驗證一下,被選舉的leader是否有變化,就是看符不符合
while((n = recvqueue.poll(finalizeWait,
TimeUnit.MILLISECONDS)) != null){
if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
proposedLeader, proposedZxid, proposedEpoch)){
//符合就放進recvqueue集合
recvqueue.put(n);
break;
}
}
//改變選舉為leader的機器的狀態為LEADING
if (n == null) {
self.setPeerState((proposedLeader == self.getId()) ?
ServerState.LEADING: learningState());
Vote endVote = new Vote(proposedLeader,
proposedZxid, proposedEpoch);
leaveInstance(endVote);
return endVote;
}
}
break;
case FOLLOWING:
case LEADING:
//在同一輪選舉中,判斷所有的通知,并確認自己是leader
if(n.electionEpoch == logicalclock){
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;
}
}
//在對外提供服務前,先廣播一次自己是leader的消息給所有follower,讓大家認同我為leader。
outofelection.put(n.sid, new Vote(n.leader, n.zxid,
n.electionEpoch, n.peerEpoch, n.state));
if (termPredicate(outofelection, new Vote(n.leader,
n.zxid, n.electionEpoch, n.peerEpoch, n.state))
&& checkLeader(outofelection, n.leader, n.electionEpoch)) {
synchronized(this){
logicalclock = 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;
}
}
}
~~~
比較重要的子函數有以下這些:
* 1.totalOrderPredicate。(投票比較變更原則,選舉的核心)
* * *
~~~java
protected boolean totalOrderPredicate(long newId, long newZxid, long newEpoch, long curId, long curZxid, long curEpoch) {
LOG.debug("id: " + newId + ", proposed id: " + curId + ", zxid: 0x" +
Long.toHexString(newZxid) + ", proposed zxid: 0x" + Long.toHexString(curZxid));
if(self.getQuorumVerifier().getWeight(newId) == 0){
return false;
}
//按照這樣的順序比較優先:Epoch > Zxid > myid
return ((newEpoch > curEpoch) ||
((newEpoch == curEpoch) &&
((newZxid > curZxid) || ((newZxid == curZxid) && (newId > curId)))));
}
~~~
* 2.termPredicate。(最終的計算票數。先把投票放到集合中,然后再統計。集合能去重)
* * *
~~~kotlin
private boolean termPredicate(
HashMap<Long, Vote> votes,
Vote vote) {
HashSet<Long> set = new HashSet<Long>();
for (Map.Entry<Long,Vote> entry : votes.entrySet()) {
if (vote.equals(entry.getValue())){
set.add(entry.getKey());
}
}
return self.getQuorumVerifier().containsQuorum(set);
}
~~~
* 3.Messenger。(構造Messenger的時候創建2條線程WorkerSender和WorkerReceiver用于整個選舉的集群投票通信)
* * *
~~~cpp
Messenger(QuorumCnxManager manager) {
this.ws = new WorkerSender(manager);
Thread t = new Thread(this.ws,
"WorkerSender[myid=" + self.getId() + "]");
t.setDaemon(true);
t.start();
this.wr = new WorkerReceiver(manager);
t = new Thread(this.wr,
"WorkerReceiver[myid=" + self.getId() + "]");
t.setDaemon(true);
t.start();
}
~~~
其他細節不多說了,主要是sendqueue和recvqueue隊列存放待發送投票通知和接收投票通知,WorkerSender和WorkerReceiver兩條線程用于投票的通信,QuorumCnxManager manager用于真正和其他機器的tcp連接維護管理,Messenger是整個投票通信的管理者。
### 3.數據同步機制
#### 3.1 同步準備
完成選舉之后,為了數據一致性,需要進行數據同步流程。
##### 3.1.1 Leader準備
* Leader告訴其它follower當前最新數據是什么即zxid
* Leader構建一個NEWLEADER的包,包括當前最大的zxid,發送給所有的follower或者Observer
* Leader給每個follower創建一個線程LearnerHandler來負責處理每個follower的數據同步請求,同時主線程開始阻塞,等到超過一半的follwer同步完成,同步過程才完成,leader才真正成為leader
##### 3.1.2 Follower準備
* 選舉完成后,嘗試與leader建立同步連接,如果一段時間沒有連接上就報連接超時,重新回到選舉狀態FOLLOWING
* 向leader發送FOLLOWERINFO包,帶上follower自己最大的zxid
#### 3.2 同步初始化
同步初始化涉及到三個東西:minCommittedLog、maxCommittedLog、zxid
– minCommittedLog:最小的事務日志id,即zxid沒有被快照存儲的日志文件的第一條,每次快照存儲
完,會重新生成一個事務日志文件
– maxCommittedLog:事務日志中最大的事務,即zxid
### 4.數據同步場景
* 直接差異化同步(DIFF同步)
* 僅回滾同步TRUNC?,即刪除多余的事務日志,比如原來的Leader宕機后又重新加入,可能存在它自己寫
入提交但是別的節點還沒來得及提交
* 先回滾再差異化同步(TRUNC+DIFF同步)
* 全量同步(SNAP同步)
不同的數據同步算法適用不同的場景。
### 5.廣播流程
* 集群選舉完成,并且完成數據同步后,開始對外服務,接收讀寫請求
* 當leader接收到客戶端新的事務請求后,會生成對新的事務proposal,并根據zxid的順序向所有的
follower分發事務proposal
* 當follower收到leader的proposal時,根據接收的先后順序處理proposal
* 當Leader收到follower針對某個proposal過半的ack后,則發起事務提交,重新發起一個commit的
proposal
* Follower收到commit的proposal后,記錄事務提交,并把數據更新到內存數據庫
* 補充說明
* 由于只有過半的機器給出反饋,則可能存在某時刻某些節點數據不是最新的
* 如果需要確定讀取到的數據是最新的,則可以在讀取之前,調用sync方法進行數據同步
### 6.小結
在zookeeper中,除了watcher機制,會話管理,最重要的就是選舉了。它是zookeeper集群的核心,也是廣泛應用在商業中的前提。
作者:dandan的微笑
鏈接:https://www.jianshu.com/p/57fecbe70540
來源:簡書
- 一.JVM
- 1.1 java代碼是怎么運行的
- 1.2 JVM的內存區域
- 1.3 JVM運行時內存
- 1.4 JVM內存分配策略
- 1.5 JVM類加載機制與對象的生命周期
- 1.6 常用的垃圾回收算法
- 1.7 JVM垃圾收集器
- 1.8 CMS垃圾收集器
- 1.9 G1垃圾收集器
- 2.面試相關文章
- 2.1 可能是把Java內存區域講得最清楚的一篇文章
- 2.0 GC調優參數
- 2.1GC排查系列
- 2.2 內存泄漏和內存溢出
- 2.2.3 深入理解JVM-hotspot虛擬機對象探秘
- 1.10 并發的可達性分析相關問題
- 二.Java集合架構
- 1.ArrayList深入源碼分析
- 2.Vector深入源碼分析
- 3.LinkedList深入源碼分析
- 4.HashMap深入源碼分析
- 5.ConcurrentHashMap深入源碼分析
- 6.HashSet,LinkedHashSet 和 LinkedHashMap
- 7.容器中的設計模式
- 8.集合架構之面試指南
- 9.TreeSet和TreeMap
- 三.Java基礎
- 1.基礎概念
- 1.1 Java程序初始化的順序是怎么樣的
- 1.2 Java和C++的區別
- 1.3 反射
- 1.4 注解
- 1.5 泛型
- 1.6 字節與字符的區別以及訪問修飾符
- 1.7 深拷貝與淺拷貝
- 1.8 字符串常量池
- 2.面向對象
- 3.關鍵字
- 4.基本數據類型與運算
- 5.字符串與數組
- 6.異常處理
- 7.Object 通用方法
- 8.Java8
- 8.1 Java 8 Tutorial
- 8.2 Java 8 數據流(Stream)
- 8.3 Java 8 并發教程:線程和執行器
- 8.4 Java 8 并發教程:同步和鎖
- 8.5 Java 8 并發教程:原子變量和 ConcurrentMap
- 8.6 Java 8 API 示例:字符串、數值、算術和文件
- 8.7 在 Java 8 中避免 Null 檢查
- 8.8 使用 Intellij IDEA 解決 Java 8 的數據流問題
- 四.Java 并發編程
- 1.線程的實現/創建
- 2.線程生命周期/狀態轉換
- 3.線程池
- 4.線程中的協作、中斷
- 5.Java鎖
- 5.1 樂觀鎖、悲觀鎖和自旋鎖
- 5.2 Synchronized
- 5.3 ReentrantLock
- 5.4 公平鎖和非公平鎖
- 5.3.1 說說ReentrantLock的實現原理,以及ReentrantLock的核心源碼是如何實現的?
- 5.5 鎖優化和升級
- 6.多線程的上下文切換
- 7.死鎖的產生和解決
- 8.J.U.C(java.util.concurrent)
- 0.簡化版(快速復習用)
- 9.鎖優化
- 10.Java 內存模型(JMM)
- 11.ThreadLocal詳解
- 12 CAS
- 13.AQS
- 0.ArrayBlockingQueue和LinkedBlockingQueue的實現原理
- 1.DelayQueue的實現原理
- 14.Thread.join()實現原理
- 15.PriorityQueue 的特性和原理
- 16.CyclicBarrier的實際使用場景
- 五.Java I/O NIO
- 1.I/O模型簡述
- 2.Java NIO之緩沖區
- 3.JAVA NIO之文件通道
- 4.Java NIO之套接字通道
- 5.Java NIO之選擇器
- 6.基于 Java NIO 實現簡單的 HTTP 服務器
- 7.BIO-NIO-AIO
- 8.netty(一)
- 9.NIO面試題
- 六.Java設計模式
- 1.單例模式
- 2.策略模式
- 3.模板方法
- 4.適配器模式
- 5.簡單工廠
- 6.門面模式
- 7.代理模式
- 七.數據結構和算法
- 1.什么是紅黑樹
- 2.二叉樹
- 2.1 二叉樹的前序、中序、后序遍歷
- 3.排序算法匯總
- 4.java實現鏈表及鏈表的重用操作
- 4.1算法題-鏈表反轉
- 5.圖的概述
- 6.常見的幾道字符串算法題
- 7.幾道常見的鏈表算法題
- 8.leetcode常見算法題1
- 9.LRU緩存策略
- 10.二進制及位運算
- 10.1.二進制和十進制轉換
- 10.2.位運算
- 11.常見鏈表算法題
- 12.算法好文推薦
- 13.跳表
- 八.Spring 全家桶
- 1.Spring IOC
- 2.Spring AOP
- 3.Spring 事務管理
- 4.SpringMVC 運行流程和手動實現
- 0.Spring 核心技術
- 5.spring如何解決循環依賴問題
- 6.springboot自動裝配原理
- 7.Spring中的循環依賴解決機制中,為什么要三級緩存,用二級緩存不夠嗎
- 8.beanFactory和factoryBean有什么區別
- 九.數據庫
- 1.mybatis
- 1.1 MyBatis-# 與 $ 區別以及 sql 預編譯
- Mybatis系列1-Configuration
- Mybatis系列2-SQL執行過程
- Mybatis系列3-之SqlSession
- Mybatis系列4-之Executor
- Mybatis系列5-StatementHandler
- Mybatis系列6-MappedStatement
- Mybatis系列7-參數設置揭秘(ParameterHandler)
- Mybatis系列8-緩存機制
- 2.淺談聚簇索引和非聚簇索引的區別
- 3.mysql 證明為什么用limit時,offset很大會影響性能
- 4.MySQL中的索引
- 5.數據庫索引2
- 6.面試題收集
- 7.MySQL行鎖、表鎖、間隙鎖詳解
- 8.數據庫MVCC詳解
- 9.一條SQL查詢語句是如何執行的
- 10.MySQL 的 crash-safe 原理解析
- 11.MySQL 性能優化神器 Explain 使用分析
- 12.mysql中,一條update語句執行的過程是怎么樣的?期間用到了mysql的哪些log,分別有什么作用
- 十.Redis
- 0.快速復習回顧Redis
- 1.通俗易懂的Redis數據結構基礎教程
- 2.分布式鎖(一)
- 3.分布式鎖(二)
- 4.延時隊列
- 5.位圖Bitmaps
- 6.Bitmaps(位圖)的使用
- 7.Scan
- 8.redis緩存雪崩、緩存擊穿、緩存穿透
- 9.Redis為什么是單線程、及高并發快的3大原因詳解
- 10.布隆過濾器你值得擁有的開發利器
- 11.Redis哨兵、復制、集群的設計原理與區別
- 12.redis的IO多路復用
- 13.相關redis面試題
- 14.redis集群
- 十一.中間件
- 1.RabbitMQ
- 1.1 RabbitMQ實戰,hello world
- 1.2 RabbitMQ 實戰,工作隊列
- 1.3 RabbitMQ 實戰, 發布訂閱
- 1.4 RabbitMQ 實戰,路由
- 1.5 RabbitMQ 實戰,主題
- 1.6 Spring AMQP 的 AMQP 抽象
- 1.7 Spring AMQP 實戰 – 整合 RabbitMQ 發送郵件
- 1.8 RabbitMQ 的消息持久化與 Spring AMQP 的實現剖析
- 1.9 RabbitMQ必備核心知識
- 2.RocketMQ 的幾個簡單問題與答案
- 2.Kafka
- 2.1 kafka 基礎概念和術語
- 2.2 Kafka的重平衡(Rebalance)
- 2.3.kafka日志機制
- 2.4 kafka是pull還是push的方式傳遞消息的?
- 2.5 Kafka的數據處理流程
- 2.6 Kafka的腦裂預防和處理機制
- 2.7 Kafka中partition副本的Leader選舉機制
- 2.8 如果Leader掛了的時候,follower沒來得及同步,是否會出現數據不一致
- 2.9 kafka的partition副本是否會出現腦裂情況
- 十二.Zookeeper
- 0.什么是Zookeeper(漫畫)
- 1.使用docker安裝Zookeeper偽集群
- 3.ZooKeeper-Plus
- 4.zk實現分布式鎖
- 5.ZooKeeper之Watcher機制
- 6.Zookeeper之選舉及數據一致性
- 十三.計算機網絡
- 1.進制轉換:二進制、八進制、十六進制、十進制之間的轉換
- 2.位運算
- 3.計算機網絡面試題匯總1
- 十四.Docker
- 100.面試題收集合集
- 1.美團面試常見問題總結
- 2.b站部分面試題
- 3.比心面試題
- 4.騰訊面試題
- 5.哈羅部分面試
- 6.筆記
- 十五.Storm
- 1.Storm和流處理簡介
- 2.Storm 核心概念詳解
- 3.Storm 單機版本環境搭建
- 4.Storm 集群環境搭建
- 5.Storm 編程模型詳解
- 6.Storm 項目三種打包方式對比分析
- 7.Storm 集成 Redis 詳解
- 8.Storm 集成 HDFS 和 HBase
- 9.Storm 集成 Kafka
- 十六.Elasticsearch
- 1.初識ElasticSearch
- 2.文檔基本CRUD、集群健康檢查
- 3.shard&replica
- 4.document核心元數據解析及ES的并發控制
- 5.document的批量操作及數據路由原理
- 6.倒排索引
- 十七.分布式相關
- 1.分布式事務解決方案一網打盡
- 2.關于xxx怎么保證高可用的問題
- 3.一致性hash原理與實現
- 4.微服務注冊中心 Nacos 比 Eureka的優勢
- 5.Raft 協議算法
- 6.為什么微服務架構中需要網關
- 0.CAP與BASE理論
- 十八.Dubbo
- 1.快速掌握Dubbo常規應用
- 2.Dubbo應用進階
- 3.Dubbo調用模塊詳解
- 4.Dubbo調用模塊源碼分析
- 6.Dubbo協議模塊