Raft共識算法

Raft共識算法在分佈式系統中是常用的共識算法之一,論文原文In Search of an Understandable Consensus Algorithm ,作者在論文中指出Poxas共識算法的兩大問題,其一是難懂,其二是應用到實際系統存在困難。針對Paxos存在的問題,作者的目的是提出一個易懂的共識算法,論文中有單獨一小節論述Raft是一個實用的、安全可用、有效易懂的共識算法。本文描述了Raft共識算法的細節,很多內容描述及引用圖片均摘自論文原文。

Raft概述

我們主要分以下三部分對Raft進行討論:

  • Leader election——a new leader must be chosen when
    an existing leader fails. (領導人選舉)
  • Log replication——the leader must accept log entries from clients and replicate them across the cluster,
    forcing the other logs to agree with its own.(日誌複製)
  • Safety——the key safety property for Raft. (安全性)

正常工作過程中,Raft分為兩部分,首先是leader選舉過程,然後在選舉出來的leader基礎上進行正常操作,比如日誌複製操作等。

一個Raft集群通常包含\(2N+1\)個服務器,允許系統有\(N\)個故障服務器。每個服務器處於3個狀態之一:leaderfollowercandidate。正常操作狀態下,僅有一個leader,其他的服務器均為follower。follower是被動的,不會對自身發出的請求而是對來自leader和candidate的請求做出響應。leader處理所有的client請求(若client聯繫follower,則該follower將轉發給leader)。candidate狀態用來選舉leader。狀態轉換如下圖所示:

為了進行領導人選舉和日誌複製等,需要服務器節點存儲如下狀態信息:

狀態 所有服務器上持久存在的
currentTerm 服務器最後一次知道的任期號(初始化為 0,持續遞增)
votedFor 在當前獲得選票的候選人的 Id
log[] 日誌條目集;每一個條目包含一個用戶狀態機執行的指令,和收到時的任期號
狀態 所有服務器上經常變的
commitIndex 已知的最大的已經被提交的日誌條目的索引值
lastApplied 最後被應用到狀態機的日誌條目索引值(初始化為 0,持續遞增)
狀態 在領導人里經常改變的 (選舉后重新初始化)
nextIndex[] 對於每一個服務器,需要發送給他的下一個日誌條目的索引值(初始化為領導人最後索引值加一)
matchIndex[] 對於每一個服務器,已經複製給他的日誌的最高索引值

Raft在任何時刻都滿足如下特性:

  • Election Safety:在一個任期中只能有一個leader;
  • Leader Append-Only:leader不會覆蓋或刪除日誌中的entry,只有添加entry(follower存在依據leader回滾日誌的情況);
  • Log Matching:如果兩個日誌包含了一條具有相同index和term的entry,那麼這兩個日誌在這個index之前的所有entry都相同;
  • Leader Completeness: 如果在某一任期一條entry被提交committed了,那麼在更高任期的leader中這條entry一定存在;(領導人選舉時會保證這一性質,後面會講到這個問題)
  • State Machine Safety:如果一個節點將一條entry應用到狀態機中,那麼任何節點也不會再次將該index的entry應用到狀態機里;

下面我們詳細討論這幾部分。

Leader選舉(Leader election)

一個節點初始狀態為follower,當follower在選舉超時時間內未收到leader的心跳消息,則轉換為candidate狀態。為了避免選舉衝突,這個超時時間是一個隨機數(一般為150~300ms)。超時成為candidate后,向其他節點發出RequestVoteRPC請求,假設有\(2N+1\)個節點,收到\(N+1\)個節點以上的同意回應,即被選舉為leader節點,開始下一階段的工作。如果在選舉期間接收到eader發來的心跳信息,則candidate轉為follower狀態。

在選舉期間,可能會出現多個candidate的情況,可能在一輪選舉過程中都沒有收到多數的同意票,此時再次隨機超時,進入第二輪選舉過程,直至選出leader或着重新收到leader心跳信息,轉為follower狀態。

正常狀態下,leader會不斷的廣播心跳信息,follower收到leader的心跳信息後會重置超時。當leader崩潰或者出現異常離線,此時網絡中follower節點接收不到心跳信息,超時再次進入選舉流程,選舉出一個leader。

這裏還有補充一些細節,每個leader可以理解為都是有自己的任期(term)的,每一期起始於選舉階段,直到因節點失效等原因任期結束。每一期選舉期間,每個follower節點只能投票一次。圖中t3可能是因為沒有獲得超半數票等造成選舉失敗,須進行下一輪選舉,此時follower可以再次對最先到達的candidate發出的RequestVote請求投票(先到先得)。

對所有的請求(RequestVote、AppendEntry等請求),如果發現其Term小於當前節點,則拒絕請求,如果是candidate選舉期間,收到不小於當前節點任期的leader節點發來的AppendEntry請求,則認可該leader,candidate轉換為follower。

日誌複製(Log replication)

leader選舉成功后,將進入有效工作階段,即日誌複製階段,其中日誌複製過程會分記錄日誌和提交數據兩個階段。

整個過程如下:

  1. 首先client向leader發出command指令;(每一次command指令都可以認為是一個entry,或者說是日誌項)
  2. leader收到client的command指令后,將這個command entry追加到本地日誌中,此時這個command是uncommitted狀態,因此並沒有更新節點的當前狀態;
  3. 之後,leader向所有follower發送這條entry,也就是通過日誌複製AppendEntries消息 (可以是一條也可以是多條日誌項) 將日誌項複製到集群其他節點上,follower接收到后 (這裡有判斷條件的,並不是所有leader發送來的日誌項都無條件接收,而且還可能存在本地與leader日誌不一致的情況,後面會詳細說明,這裏先看正常情況) 追加到本地日誌中,並回應leader成功或者失敗;
  4. leader收到大多數follower的確認回應后,此entry在leader節點由uncommitted變為committed狀態,此時按這條command更新leader狀態,或者說將該日誌項應用到狀態機,然後向client返回執行結果;
  5. 在下一心跳中(這裏也可以是或者說多數情況下是新的日誌複製AppendEntries消息,會帶有相關信息,後面后詳細的字段說明會講到),leader會通知所有follower更新確認的entry,follower收到后,更新狀態,這樣,所有節點都完成client指定command的狀態更新。

可以看到client每次提交command指令,服務節點都先將該指令entry追加記錄到日誌中,等leader確認大多數節點已追加記錄此條日誌后,在進行提交確認,更新節點狀態。如果還對這個過程有些模糊的話,可以參考Raft動畫演示,較為直觀的演示了領導人選舉及日誌複製的過程。

安全(Safety)

前面描述了Raft算法是如何選舉和複製日誌的。然而,到目前為止描述的機制並不能充分的保證每一個狀態機會按照相同的順序執行相同的指令。我們需要再繼續深入思考以下幾個問題:

  • 第一個問題,leader選舉時follower收到candidate發起的投票請求,如果同意就進行回應,但具體的規則是什麼呢?是所有的follower都有可能被選舉為領導人嗎?
  • 第二個問題,leader可能在任何時刻掛掉,新任期的leader怎麼提交之前任期的日誌條目呢?

選舉限制

針對第一個問題,之前並沒有細講,如果當前leader節點掛了,需要重新選舉一個新leader,此時follower節點的狀態可能是不同的,有的follower可能狀態與剛剛掛掉的leader相同,狀態較新,有的follower可能記錄的當前index比原leader節點的少很多,狀態更新相對滯后,此時,從系統最優的角度看,選狀態最新的candidate為佳,從正確性的角度看,要確保Leader Completeness,即如果在某一任期一條entry被提交成功了,那麼在更高任期的leader中這條entry一定存在,反過來講就是如果一個candidate的狀態舊於目前被committed的狀態,它一定不能被選為leader。具體到投票規則:
1) 節點只投給擁有不比自己日誌狀態舊的節點;
2)每個節點在一個term內只能投一次,在滿足1的條件下,先到先得;

我們看一下請求投票 RPC(由候選人負責調用用來徵集選票)的定義:

參數 解釋
term 候選人的任期號
candidateId 請求選票的候選人的 Id
lastLogIndex 候選人的最後日誌條目的索引值
lastLogTerm 候選人最後日誌條目的任期號
返回值 解釋
term 當前任期號,以便於候選人去更新自己的任期號
voteGranted 候選人贏得了此張選票時為真

接收者實現:

  1. 如果term < currentTerm返回 false
  2. 如果 votedFor 為空或者為 candidateId,並且候選人的日誌至少和自己一樣新,那麼就投票給他

可以看到RequestVote投票請求中包含了lastLogIndex和lastLogTerm用於比較日誌狀態。這樣,雖然不能保證最新狀態的candidate成為leader,但能夠保證被選為leader的節點一定擁有最新被committed的狀態,但不能保證擁有最新uncommitted狀態entries。

提交之前任期的日誌條目

領導人知道一條當前任期內的日誌記錄是可以被提交的,只要它被存儲到了大多數的服務器上。但是之前任期的未提交的日誌條目,即使已經被存儲到大多數節點上,也依然有可能會被後續任期的領導人覆蓋掉。下圖說明了這種情況:

如圖的時間序列展示了為什麼領導人無法決定對老任期號的日誌條目進行提交。在 (a) 中,S1 是領導者,部分的複製了索引位置 2 的日誌條目。在 (b) 中,S1崩潰了,然後S5在任期3里通過S3、S4和自己的選票贏得選舉,然後從客戶端接收了一條不一樣的日誌條目放在了索引 2 處。然後到 (c),S5又崩潰了;S1重新啟動,選舉成功,開始複製日誌。在這時,來自任期2的那條日誌已經被複制到了集群中的大多數機器上,但是還沒有被提交。如果S1在(d)中又崩潰了,S5可以重新被選舉成功(通過來自S2,S3和S4的選票),然後覆蓋了他們在索引 2 處的日誌。反之,如果在崩潰之前,S1 把自己主導的新任期里產生的日誌條目複製到了大多數機器上,就如 (e) 中那樣,那麼在後面任期裏面這些新的日誌條目就會被提交(因為S5 就不可能選舉成功)。 這樣在同一時刻就同時保證了,之前的所有老的日誌條目就會被提交。

為了消除上圖裡描述的情況,Raft永遠不會通過計算副本數目的方式去提交一個之前任期內的日誌條目。只有領導人當前任期里的日誌條目通過計算副本數目可以被提交;一旦當前任期的日誌條目以這種方式被提交,那麼由於日誌匹配特性,之前的日誌條目也都會被間接的提交。

當領導人複製之前任期里的日誌時,Raft 會為所有日誌保留原始的任期號。

對Raft中幾種情況的思考

follower節點與leader日誌內容不一致時怎麼處理?

我們先舉例說明:正常情況下,follower節點應該向B節點一樣與leader節點日誌內容一致,但也會出現A、C等情況,出現了不一致,以A、B節點為例,當leader節點向follower節點發送AppendEntries<prevLogIndex=7,prevLogTerm=3,entries=[x<-4]>,leaderCommit=7時,我們分析一下發生了什麼,B節點日誌與prevLogIndex=7,prevLogTerm=3相匹配,將index=7x<-5)這條entry提交committed,並在日誌中新加入entryx<-4,處於uncommitted狀態;A節點接收到時,當前日誌index<prevLogIndexprevLogIndex=7,prevLogTerm=3不相匹配,拒接該請求,不會將x<-4添加到日誌中,當leader知道A節點因日誌不一致拒接了該請求后,不斷遞減preLogIndex重新發送請求,直到A節點index,termprevLogIndex,prevLogTerm相匹配,將leader的entries複製到A節點中,達成日誌狀態一致。

我們看一下附加日誌AppendEntries RPC(由領導人負責調用複製日誌指令;也會用作heartbeat)的定義:

參數 解釋
term 領導人的任期號
leaderId 領導人的 Id,以便於跟隨者重定向請求
prevLogIndex 新的日誌條目緊隨之前的索引值
prevLogTerm prevLogIndex 條目的任期號
entries[] 準備存儲的日誌條目(表示心跳時為空;一次性發送多個是為了提高效率)
leaderCommit 領導人已經提交的日誌的索引值
返回值 解釋
term 當前的任期號,用於領導人去更新自己
success 跟隨者包含了匹配上 prevLogIndex 和 prevLogTerm 的日誌時為真

接收者實現:

  1. 如果 term < currentTerm 就返回 false;
  2. 如果日誌在 prevLogIndex 位置處的日誌條目的任期號和 prevLogTerm 不匹配,則返回 false;
  3. 如果已經存在的日誌條目和新的產生衝突(索引值相同但是任期號不同),刪除這一條和之後所有的;(raft中follower處理不一致的一個原則就是一切聽從leader)
  4. 附加日誌中尚未存在的任何新條目;
  5. 如果 leaderCommit > commitIndex,令 commitIndex 等於 leaderCommit 和 新日誌條目索引值中較小的一個;

簡單總結一下,出現不一致時核心的處理原則是一切遵從leader。當leader向follower發送AppendEntry請求,follower對AppendEntry進行一致性檢查,如果通過,則更新狀態信息,如果發現不一致,則拒絕請求,leader發現follower拒絕請求,出現了不一致,此時將遞減nextIndex,並重新給該follower節點發送日誌複製請求,直到找到日誌一致的地方為止。然後把follower節點的日誌覆蓋為leader節點的日誌內容。

leader掛掉了,怎麼處理?

前面可能斷斷續續的提到這種情況的處理方法,首要的就是選出新leader,選出新leader后,可能上一任期還有一些entries並沒有提交,處於uncommitted狀態,該怎麼辦呢?處理方法是新leader只處理提交新任期的entries,上一任期未提交的entries,如果在新leader選舉前已經被大多數節點記錄在日誌中,則新leader在提交最新entry時,之前處於未提交狀態的entries也被committed了,因為如果兩個日誌包含了一條具有相同index和term的entry,那麼這兩個日誌在這個index之前的所有entry都相同;如果在新leader選舉前沒有被大多數節點記錄在日誌中,則原有未提交的entries有可能被新leader的entries覆蓋掉。

出現網絡分區時怎麼處理?

分佈式系統中網絡分區的情況基本無法避免,出現網絡分區時,原有leader在分區的一側,此時如果客戶端發來指令,舊leader依舊在分區一測進行日誌複製的過程,但因收不到大多數節點的確認,客戶端所提交的指令entry只能記錄在日誌中,無法進行提交確認,處於uncommitted狀態。而在分區的另一側,此時收不到心跳信息,會進入選舉流程重新選舉一個leader,新leader負責分區零一側的請求,進行日誌複製等操作。因為新leader可以收到大多數follower確認,客戶端的指令entry可以被提交,並更新節點狀態,當網絡分區恢復時,此時兩個leader會收到彼此廣播的心跳信息,此時,舊leader發現更大term的leader,舊leader轉為follower,此時舊leader分區一側的所有操作都要回滾,接受新leader的更新。

成員變更

在分佈式系統中,節點數量或者說服務器數量不是一成不變的,我們有可能會隨時增減節點數量,當增加節點時,有可能會出現兩個leader選舉成功的情況,主要是新舊配置不一致造成的,怎麼處理呢?最簡單粗暴的就是把目前所有節點都停掉,更新配置,再重啟所有節點,但會造成一段時間服務不可用,很多情況下這是不能被允許的。raft的解決辦法原論文中是聯合共識(Joint Consensus)的辦法,後來又提出了單節點變更(single-server changes)的方法。我們下面詳細描述一下這個問題。

Raft要求,在任一任期內,只能有一個leader,而成員變更的麻煩就在於,成員變更時可能會出現兩個leader,以一個例子說明:原系統有3個節點,成員為[1,2,3],現新增成員4、5。假設在成員變更時,1、2與3發生分區,此時,[1,2]為一組,1通過1、2兩節點選舉為leader,而5通過3、4、5選舉為leader,就形成了2個leader並存的情況。

因為每個節點新舊配置更新的時間不同,造成了在某一時刻,可能會存在新舊配置的兩個大多數情況的存在,上圖中,舊配置的大多數是兩個節點,而新配置的大多數是三個節點,在圖中紅線頭的時刻存在兩個大多數的情況,如果此時出現網絡分區進行選舉時就會出現兩個leader的情況。

怎麼解決呢?用什麼辦法才能不讓上面兩個大多少情況的出現呢?可通過單節點變更解決,即通過一次變更一個節點實現成員變更。主要思想是利用“一次變更一個節點,不會同時存在舊配置和新配置的兩個大多數”的特性,實現成員變更。比如上面的情況,就可先將3節點集群[A,B,C]變更為4節點集群[A,B,C,D],再將4節點集群變更為5節點集群[A,B,C,D]。

為什麼單節點變更不會造成兩個大多數情況的出現呢?我們可以進行如下推理:假設原節點數為2n+1,則舊配置的大多數major_old=n+1,新加入1個節點,新配置節點數為2n+2,則新配置的大多數為major_new=n+2,同時存在兩個大多數所需節點數目為major=major_old+major_new=n+1+n+2=2n+3>2n+2,也就是兩個大多數所需節點數超出了節點總數,故不存在這種情況,如何是刪除成員,其推理過程類似,結論相同。

具體的,我們依舊以這個3節點集群變更為5節點集群為例進行說明。假設現3節點集群[A,B,C],節點A為leader,配置為[A,B,C],我們先向集群加入節點D,新的配置為[A,B,C,D],成員變更通過以下兩步實現:

  • 第一步,leader節點A向新節點D同步數據;
  • 第二步,leader將新配置[A,B,C,D]作為一個日誌項複製到新配置中的所有節點(A,B,C,D)上,然後將新配置的日誌項應用到本地狀態機,完成單節點變更。

在變更后,現有集群的配置項就是[A,B,C,D],添加E節點也是同樣的步驟。上面的描述如果理解的比較模糊的話,其實raft是採用將修改集群配置的命令放在日誌條目中來處理的,其修改配置項,就是一條日誌項,其流程與普通的日誌項相同,只不過最後狀態機執行的結果是配置變更。

日誌壓縮

日誌壓縮主要是為了解決無限增長的日誌與有限的存貯空間的矛盾,可以想一個問題:對於已經committed的日誌項,是否有必要一直保存下去?如果沒有必要的話,是否可以對部分已committed的日誌項刪減或壓縮呢?raft的主要的解決辦法是採用快照進行日誌壓縮。

如上圖所示,對於日誌索引5之前的日誌項可以刪除,只保留一個快照(保存有當前狀態以及一些任期索引號等元信息)即可。

具體工程實現時,一般每個節點獨立打快照,當日誌超過一定量會觸發快照操作,具體實現以及更多細節待以後深究。

Client Protocol

raft共識算法真正工作時還需有一個客戶端協議(client protocol),綜合解決一些列的問題。比如會遇到下面這些問題:client怎麼和集群交互呢?client如果知道leader節點的話,可以直接將command發給leader節點,如果不知道的話,可以隨意發給集群中已知的節點,節點會將client的請求轉給leader。其實上面還有個問題,client發送請求(或者command)給leader,但是leader遲遲不給回應怎麼辦?重試是一個辦法。連接的leader崩潰了client怎麼辦?如果client超時重發command,怎麼保證command不被狀態機執行兩次?client生成command的時候要給加上唯一ID,當server的日誌中已存在相同command時會忽略。

附錄

這裏附加一張論文中的截圖,裏面詳細講明了不同節點需要維護什麼信息,每個消息是怎麼定義的,以及消息該如何處理等,不包含日誌壓縮以及成員變更部分:

這裏補充一點,raft共識算法與pbft共識算法解決的是不同的問題,即raft節點不能存在惡意節點,節點消息可以延遲、丟失,但不能造假或作惡,即不能存在拜占庭節點。

本文對raft共識算法做了一個整體的梳理學習,可能會存在某些細節描述不清晰的地方,在真正工程代碼實現時,還會存在更多的細節問題,同時,這裏缺少證明為什麼raft算法是正確的證明,有待今後更深一步理解共識算法后再行補充。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

使用git暢遊代碼的海洋

如果把互聯網上的紛繁代碼比作一片海洋,那麼git就是在這片海洋上航行的船隻,正所謂“水可載舟,亦可覆舟”,git使用恰當可以遠征星辰,不然可能會墜入無窮無盡的代碼海洋無法自拔。書回正傳,我們的征途是星辰大海!

揚帆起航

git的下載安裝暫且不表,可參考網站https://git-scm.com/downloads。

git的安裝只是個入門條件,下面如何使用git命令控制代碼才是遠征的基礎。那麼從何開始呢?讓我們一步步說起。

要想揚帆起航,首先得有艘帆船吧。我們要在本地建立一個項目,之後將本地項目初始化為git倉庫,可以使用

git init [–bare]

初始化本地項目,執行後會在當前執行目錄下生成.git文件夾,這也就是我們的帆船了。[–bare]可選參數,可初始化裸倉庫,裸倉庫可以與源碼項目分離,此時在當前目錄下不會生成.git文件夾,而是直接生成.git文件夾下的包括hooks、info、objects、refs共四個文件夾和config、description、HEAD三個文件。

在初始化裸倉庫后若想關聯源碼,只需進入hooks目錄新建post-receive.sample文件,並添加如下內容:

git –work-tree=<project-dir> –git-dir=<local-url> checkout -f

其中<project-dir>為本地項目文件路徑,<local-url>為本地git倉庫路徑。

 

對於當前目錄下的.git文件夾所代表的本地倉庫,不同的帆船有不同的配置,我們使用

git config –list

显示當前git配置。如果內容過多可以使用上下鍵翻頁,查看結束按q退出即可。同時使用

git config –e [–global]

以vim形式編輯修改git配置文件。

git config [–global] user.name “name”

形式修改指定配置,其中[–global]可選,為全局配置,name為用戶名配置。

 

 

git儀錶盤

 

 

 

 

 

 

 

圖 1 Git指令關係圖

 

建立git倉庫作為帆船后,就可以在當前目錄下任意使用git指令遨遊了。我們需要先認識下git控制的命令儀錶盤。

如圖1所示,git整艘船主要分為四大區域,包括Remote遠程倉庫,Repository本地倉庫,Index/Stage暫存區,Workspace工作區,不同區域之間可以使用相關指令進行代碼操作。在上節使用git init創建的文件夾下,會生成名為.git的隱藏文件夾,四大區域的配置都儲存在該文件夾下。其中在遠程倉庫和本地倉庫中,儲存有不同的branch分支,工作區和暫存區的文件只能針對某一分支進行修改提交操作,當然也可以使用分支操作指令對不同的分支進行增刪合併等操作。

針對一些倉庫的操作指令,使用前請務必保持頭腦清醒,不然你的一個蝴蝶煽動翅膀似的操作,可能會引發倉庫里的一場代碼風暴,造成不可挽回的結果。

根據不同命令操作區域,可以將git命令大致分為全局显示信息、工作區與遠程倉庫交互、工作區與本地倉庫交互、工作區與暫存區交互、暫存區與本地倉庫交互、本地倉庫與遠程倉庫交互、以及倉庫內部分支操作。下面對這些命令進行了粗略的統計介紹,詳細使用方式可參考git官方文檔介紹,如有不足還請補充。

显示信息

git help [command]

獲取命令的幫助信息。

 

git status

显示所有變更文件

 

git log [–stat] [–graph]

显示當前分支的版本信息。[–stat]參數指定显示commit發生變更的文件。[–graph]參數以數據圖形式查看合併分支記錄。

 

git blame <file>

显示文件的每一行最後修改的版本和作者

 

git show [commit][:filename]

显示某次提交的變更內容。其中[commit]為某次提交版本,也可在:後邊加參數[filename]指定查看某個文件內容。

 

git diff [HEAD] [first-branch] [second-branch]

显示文件差異。無參時比較緩存區和上一次commit的差異;[HEAD]為工作區與當前分支最新commit的差異;或者兩個分支之間的差異。

 

git reflog

显示已執行過的所有git動作日誌。

 

工作區與遠程倉庫

git pull <remote> <branch>

拉取遠程倉庫的變化,並與本地分支合併。<remote>為遠程倉庫名,<branch>為遠程倉庫中的某一分支名。

工作區與本地倉庫

git checkout [–b] <branch> [tag]

將暫存區切換到分支名。其中[-b]參數可選,當分支不存在時則創建,<branch >必須,為本地倉庫分支,[tag]可選,指定切換到倉庫分支中的某條標籤,不標註則默認為切換分支的最近一次提交。

工作區與暫存區

git add <dir>

添加指定目錄到暫存區,<dir>為添加路徑,允許多個,包括子目錄都會添加到暫存區中等待提交。

 

git rm [–cached] <file>

刪除暫存區中的文件,<file>為暫存區中要刪除的文件全路徑,[–cache]可選參數,只會停止繼續追蹤指定文件,但該文件目前仍然保留在暫存區。

 

git mv <file-old> <file-new>

修改暫存區中的文件名,<file-old>為原文件全路徑,<file-new>為修改后的文件全路徑。

 

 

git tag

查看暫存區中的所有標籤信息。

 

git tag –a <tag> [commit]

新建一個標籤。[tag]為標籤名;[commit]為指定的一次從暫存區到本地倉庫的提交中,默認為最新一次提交。

 

git tag –d [tag]

刪除本地標籤。

暫存區與本地倉庫

git commit [–amend] [–m <message>] [file] [-a] [-v]

從暫存區提交到本地倉庫。其中[–amend]重做上次從本地項目到暫存區的commit,當代碼與上次提交相比無變化時使用,只修改上次commit的<message>內容;[-m]參數為提交信息,<message>必寫且詳寫,以區別提交代碼的修改內容;[file]為指定暫存區中的文件;[-a]可直接提交項目中的變化到本地倉庫,在沒有新增文件時不需每次先git add提交到暫存區再提交到本地倉庫;[-v]可以在提交時显示所有變化文件的diff信息。

 

git cherry-pick [commit]

選擇一個commit版本合併到當前分支,[commit]為暫存區中的commit版本。

 

本地倉庫與遠程倉庫

git remote [-v]

查看關聯的遠程倉庫信息。[-v]可以查看詳細信息。

 

git remote add <remote-name> <remote-url>

本地路徑關聯遠程倉庫。

<remote-name>必要參數,為遠程倉庫的名字,默認是origin。

<remote-url>必要參數,為遠程倉庫的地址,git服務器通常都是以.git結尾。

 

git remote remove <remote-name>

刪除關聯的遠程倉庫。其中<remote-name>為遠程倉庫的名字。

 

git push [remote] [branch] [–force]

推送本地指定分支到遠程倉庫。[remote]為遠程倉庫名,[branch]為本地分支名,[–force]為強制推送本地到遠程,如有衝突則覆蓋。

 

git fetch <remote>

將遠程倉庫拉到本地倉庫。<remote>為遠程倉庫名。

 

git clone <url> [name]

創建一個本地倉庫。<url>必須,可以是遠程git服務器上的倉庫,也可以是本地倉庫。[name]選填是創建的新倉庫名,默認與原倉庫名一致。

 

分支指令

git branch [-r] [-a]

查看分支信息,無參只會查看本地倉庫所有分支,[-r]是遠程倉庫所有分支,[-a]則是包括本地和遠程倉庫所有的所有分支。

git branch [branch-name] [commit]

在本地倉庫新建分支,但暫存區仍然指向當前分支。其中[branch-name]為新建的本地倉庫分支;[commit]可將分支指向指定commit版本。

 

git branch –track [local-branch-name] [remote-branch]

新建一個分支,並連接指定的遠程分支。其中[local-branch-name]為本地倉庫新建分支,[remote-branch]為遠程倉庫分支。

 

git branch –set-upstream [local-branch] [remote-branch]

連接本地倉庫分支與遠程倉庫分支,其中[local-branch]為本地倉庫已有分支,[remote-branch]為遠程倉庫分支。

 

git branch –d [branch]

刪除本地倉庫分支,[branch]為本地倉庫中的已有分支。

 

git branch -m [branch-old] [branch-new]

修改本地倉庫分支,其中[branch-old]為原分支,[branch-new]為改名后的分支。

 

git branch –dr [remote-branch]

刪除遠程倉庫分支,[remote-branch]為遠程倉庫中的分支。不推薦使用,如果遠程倉庫未更新,可能會執行失敗,推薦使用git push origin-delete [remote-branch]。

 

git merge <local-branch>

合併指定分支到當前分支,<local-branch>為本地倉庫中的已有分支。

 

git rebase <remote-branch>

將當前分支的提交複製到指定的遠程分支上,<remote-branch>為指定遠程倉庫中的已有分支。

 

git reset [–mixed|–soft|–hard] [commit]

重置倉庫索引,重置一旦清空后的內容不會在倉庫歷史版本中留下歷史記錄。[–mixed]為默認參數,重置后只在工作區保留原節點修改文件,清空暫存區和本地倉庫並均恢復到指定重置節點;[–soft]為軟重置,重置后在工作區和暫存區均保留原節點修改文件,清空本地倉庫並恢復到指定重置節點;[–hard]為硬重置,重置后均不會保留原節點修改文件。[commit]為要重置的節點號。

 

git revert [commit]

還原文件到之前修改提交節點時,會在倉庫歷史版本中留下歷史記錄。[commit]為要還原的節點號。

常規操作

git這艘大船雖然功能繁雜,但是用起來是有章可循的。入門之後就駕駛下這艘大船來試試吧。

一般git有三種工作流程,包括Git flow,項目存在兩個長期分支(主分支master和開發分支develop),適用於基於版本發布的普通項目;Github flow,只有一個長期分支(主分支master),適用於持續發布的小型項目;Gitlab flow,項目存在多個長期分支,其中主分支master是其他所有分支的上游,只有上游分支採納的代碼才能應用到其下游分支,適用於長期維護的大型項目。

圖2展示了一次項目git流程演變過程,在master主分支上有Tag1-Tag4四次代碼更新,其中基於Tag2對應的版本1號創建了新的branch1分支,新分支創建后自動生成了版本2號並打上了Tag2-1標籤,之後master主分支和branch1分支都同時進行了代碼演變,在branch1分支提交的版本4號及標籤Tag2-3之後,branch1分支合併到了master主分支,合併前master主分支位於版本5號,合併后可能重新生成版本6號,並打上新的tag4標籤,之後master主分支修改提交為版本7號,而branch1分支則停留在tag2-3標籤的位置處。

 

 

 

 

 圖 2 Git流程示意圖

 

在這份項目流程中,分支創建之後,可能在不同的階段修改提交文件,根據對文檔的讀取權限範圍,我們可以形象地將這些階段劃分為三種身份類型,船長、水手和遊客。船長身份,作為項目管理者,主要負責遠程倉庫和本地倉庫之間的分支操作,協調分支衝突;水手身份,作為項目貢獻者,主要負責某一分支的迭代更新;遊客身份,作為項目使用者,只是訪問使用倉庫及其分支內容,不能提交任何修改。同一人在項目的不同階段可以是其中任意一種身份,下面以這三種身份為維度簡單介紹下使用到的相關git指令步驟,並輔以示意圖方式直觀解釋git指令執行前後git項目變化。

 

 

項目使用者-遊客

        作為git項目的遊客,當然只能將項目從遠程倉庫拉取到本地使用,期間除了切換倉庫分支外不會涉及其他遠程操作。

 

git pull origin master

拉取遠程倉庫origin的master分支到本地。

 

git checkout branch1 tag1

切換到分支branch1。

 

 

項目貢獻者-水手

        作為項目的水手,除了可以使用遊客的功能指令外,還會涉及到修改工作區文件,並將工作區文件提交到暫存區和倉庫等任務。通常水手只需要維護倉庫中的某一條分支並只對該分支負責,因此水手更注重工作區的代碼文件修改工作。

 

git pull origin dev:branch1

拉取遠程倉庫origin的dev分支到本地,並與本地branch1分支合併。工作區中文件即显示branch1分支,可在工作區做文件修改操作。

 

git add .

在工作區的文件修改之後,可先添加當前目錄所有文件到暫存區,之後可繼續修改工作區其他文件,也可將暫存區文件提交到本地倉庫。

 

git rm –cached file

針對工作區編譯生成的配置file文件,一般不需提交到倉庫,可使用該命令將file文件從暫存區刪除並停止後續追蹤。另外一種添加忽略文件的方式,在.git文件夾的同級目錄下新建.gitignore文件,在該文件中根據規則增加要忽略的文件路徑,之後將該文件提交到本地倉庫中。

 

git commit –m “commit message 1”

在確保工作區的所有修改文件均已提交到暫存區后,便將暫存區的修改提交到本地倉庫,同時附帶當次提交信息。每次提交都會在本地倉庫生成一個新的提交commit版本號,由於提交的commit版本號是冗長的sha1碼,所以為了方便後期溯源,通常會在主要的commit號版本上再打一個鮮明的標籤以作標記。

 

 

 

git tag tag1 1

在提交的commit版本號為1的節點上打標籤,打上標籤后的commit號便可使用簡短的標籤名tag1訪問,以便後期對該節點溯源。

 

 

 

git push origin branch1:dev –tags

將本地branch1分支及相關標籤推送到遠程倉庫origin的dev分支。如果本地branch1分支已經與遠程dev分支建立追蹤關係,也可直接使用

git push origin branch1 –tags

指令。

 

git branch –set-upstream-to=origin/dev branch1

設置本地倉庫的branch1分支與遠程倉庫origin的dev分支的追蹤關係。通常從遠程分支pull到本地的分支都已經建立了追蹤關係,不需要手動修改。

 

git branch –vv

查看本地分支及追蹤的遠程分支信息。

 

項目管理者-船長

        作為項目的船長,自然擁有整個git這艘大船的項目所有權限,除了使用水手的操作指令外,另需完成分支增刪合併等任務。通常船長是項目倉庫的創建者,負責管理維護倉庫的各分支關係,對項目的整個倉庫負責,因此相較於水手,船長更注重倉庫的分支管理相關工作。

 

git clone https://github.com/xxx.git -b dev

克隆遠程倉庫的dev分支到本地,默認會將文件更新到本地倉庫建立的同名dev分支。

 

git checkout –b branch1 origin/dev

在本地倉庫新建branch1分支,與遠程倉庫origin中的dev分支對應,並在本地切換到branch1分支。如果不指定遠程倉庫及分支信息origin/dev,則默認從本地倉庫dev分支創建。至此可以切換為水手身份,從該分支更新代碼,並將修改文件提交到該branch1分支。

 

git add .

git commit –m “modify file commit”

在完成對工作區文件的修改之後,使用水手身份將工作區的修改提交到本地branch1分支。

 

git checkout dev

切換到本地倉庫的dev主分支,作為本地倉庫與遠程倉庫代碼合併的操作分支。

 

git fetch origin dev

拉取遠程倉庫origin中的dev分支到本地倉庫當前dev分支

 

git pull

將本地倉庫dev分支的文件修改合併到工作區。

 

git merge branch1

將本地倉庫的branch1分支合併到當前dev分支。如果當前dev分支與branch1分支有衝突,需要根據衝突文件提示分別修改,之後再重新合併該分支。

 

git checkout branch1 build/files

或者只將branch1分支的build/files目錄下所有文件合併到本地倉庫的當前dev分支。同樣需要做衝突處理。

 

git push origin dev:dev –tags

向遠程倉庫origin中的dev分支並推送本地倉庫dev分支。推送時如果確認以本地分支覆蓋遠程分支,則可使用

 

git push —force origin dev:dev –tags

強制推送。最後如果想刪除遠程分支,有以下兩條指令

 

git push origin –delete dev

git push origin :dev

這兩種方式都可以刪除指定的遠程倉庫origin中的dev分支。

 

應急預案

上面的圖2Git流程圖簡單涉及了一次版本演變過程中的一些git信息,包括分支切換,打標籤等,看上去簡單易懂,而實際我們工作中駕船航行時卻並不總是風平浪靜。

        通常出現的緊急情況需要修改分支版本,包括變基、還原、重置等操作,針對不同場景需要選擇不同的操作方式。

變基

git rebase master

變基會將當前分支的修改文件複製到master分支,同時創建新的commit版本號並修改項目的歷史記錄。當master分支已經更新,且確認當前分支與master分支沒有衝突,那可以使用變基以便當前分支獲取master分支的更新。

 

 

 

 

重置

git reset 1

將HEAD重置到歷史提交版本1的狀態,還原倉庫和暫存區的文件與提交版本1一致,工作區維持修改文件狀態。

 

 

 

 

還原

 

git revert 1

重新創建一次提交版本節點,文件狀態與歷史提交版本1一致,工作區、暫存區與倉庫均保持一致。提交版本2相對於提交版本1新增了file.txt文件,執行該指令后,在提交版本3中將恢復到提交版本1的狀態,因此提交版本3相對於提交版本2則刪除了file.txt文件。

 

 

強制遠程覆蓋本地

git fetch –all

拉取遠程所有倉庫到本地倉庫,工作區不會有任何合併更新。

 

git reset –hard origin/dev

把工作區HEAD指向最新的遠程倉庫origin中的dev版本。

待補充

除此之外在駕馭git這艘大船時肯定還會出現各種意外,屆時將酌情補充。

 

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

我從LongAdder中窺探到了高併發的秘籍,上面只寫了兩個字…

這是why的第 53 篇原創文章

荒腔走板

大家好,我是why。

時間過的真是快,一周又要結束了。那麼,你比上周更博學了嗎?先來一個簡短的荒腔走板,給冰冷的技術文注入一絲色彩。

上面這圖是我之前拼的一副拼圖,一共劃分了800塊,背面無提示,難度極高,我花了兩周的時間才拼完。

拼的是壇城,傳說中佛祖居住生活的地方。

第一次知道這個名詞是 2015 年,窩在寢室看紀錄片《第三極》。

其中有一個片段講的就是僧人為了某個節日用沙繪畫壇城,他們的那種專註,虔誠,真摯深深的打動了我,當宏偉的壇城畫完之後,他靜靜的等待節日的到來。

本以為節日當天眾人會對壇城頂禮膜拜,而實際情況是大家手握一炷香,看着眾僧人快速的摧毀壇城。

還沒來得及仔細欣賞那複雜的美麗的圖案,卻又用掃把掃的乾乾凈凈。

掃把掃下去的那一瞬間,我的心受到了一種強烈的撞擊:可以辛苦地拿起,也可以輕鬆地放下。

看到摧毀壇城的片段的時候,有一個彈幕是這樣說的:

一切有為法,如夢幻泡影,如露亦如電,應作如是觀。

這句話出自《金剛般若波羅蜜經》第三十二品,應化非真分。

因為之前翻閱過幾次《金剛經》,看到這句話的時候我一下就想起了它。

因為讀的時候我就覺得這句話很有哲理,但是也似懂非懂。所以印象比較深刻。

當他再次在壇城這個畫面上以彈幕的形式展現在我的眼前的時候,我一下就懂了其中的哲理,不敢說大徹大悟,至少領悟一二。

觀看摧毀壇城,這個色彩斑斕的世界變幻消失的過程,正常人的感受都是震撼,轉而覺得可惜,心裏久久不能平靜。

但是僧人卻風輕雲淡的說:一切有為法,如夢幻泡影,如露亦如電,應作如是觀。

好了,說迴文章。

先說AtomicLong

關於 AtomicLong 我就不進行詳細的介紹了。

先寫這一小節的目的是預熱一下,拋出一個問題,而這個問題是關於 CAS 操作和 volatile 關鍵字的。

我不知道源碼為什麼這樣寫,希望知道答案的朋友指點一二。

抱拳了,老鐵。

為了順利的拋出這個問題,我就得先用《Java併發編程的藝術》一書做引子,引出這個問題。

首先在書的第 2.3 章節《原子操作的實現原理》中介紹處理器是如何實現原子操作時提到了兩點:

  • 使用總線鎖保證原子性。

  • 使用緩存鎖保證原子性。

所謂總線鎖就是使用處理器提供一個提供的一個 LOCK # 信號,當一個處理器在總線上輸出此信號時,其他處理器的請求將被阻塞住,那麼該處理器可以獨佔共享內存。

總線鎖保證原子性的操作有點簡單粗暴直接了,導致總線鎖定的開銷比較大。

所以,目前處理器在某些場合下使用緩存鎖來進行優化。

緩存鎖的概念可以看一下書裏面怎麼寫的:

其中提到的圖 2-3 是這樣的:

其實關鍵 Lock 前綴指令。

被 Lock 前綴指令操作的內存區域就會加鎖,導致其他處理器不能同時訪問。

而根據 IA-32 架構軟件開發者手冊可以知道,Lock 前綴的指令在多核處理器下會引發兩件事情:

  • 將當前處理器緩存行的數據寫回系統內存。
  • 這個寫回內存的操作會使在其他 CPU 里緩存了該內存地址的數據無效。

對於 volatile 關鍵字,毫無疑問,我們是知道它是使用了 Lock 前綴指令的。

那麼問題來了,JVM 的 CAS 操作使用了 Lock 前綴指令嗎?

是的,使用了。

JVM 中的 CAS 操作使用的是處理器通過的 CMPXCHG 指令實現的。這也是一個 Lock 前綴指令。

好,接下來我們看一個方法:

java.util.concurrent.locks.AbstractQueuedLongSynchronizer#compareAndSetState

這個方法位於 AQS 包裏面,就是一個 CAS 的操作。現在只需要關心我框起來的部分。

英文部分翻譯過來是:這個操作具有 volatile 讀和寫的內存語言。

而這個操作是什麼操作?

就是 344 行 unsafe 的 compareAndSwapLong 操作,這個方法是一個 native 方法。

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

為什麼這個操作具有 volatile 讀和寫的內存語言呢?

書裏面是這樣寫的:

這個本地方法的最終實現在 openjdk 的如下位置:
openjdk-7-fcs-src-b147- 27_jun_2011\openjdk\hotspot\src\os_cpu\windows_x86\vm\atomic_windows_x86.inline.hpp(對應於Windows操作系統,X86處理器)

intel 的手冊對 Lock 前綴的說明如下。

  • 確保對內存的讀-改-寫操作原子執行。在 Pentium 及 Pentium 之前的處理器中,帶有 Lock 前綴的指令在執行期間會鎖住總線,使得其他處理器暫時無法通過總線訪問內存。很顯然,這會帶來昂貴的開銷。從Pentium 4、Intel Xeon及P6處理器開始,Intel使用緩存鎖定(Cache Locking) 來保證指令執行的原子性。緩存鎖定將大大降低lock前綴指令的執行開銷。

  • 禁止該指令,與之前和之後的讀和寫指令重排序。

  • 把寫緩衝區中的所有數據刷新到內存中。

上面的第2點和第3點所具有的內存屏障效果,足以同時實現 volatile 讀和volatile 寫的內存語義。

好,如果你說你對書上的內容存疑。那麼我帶大家再看看官方文檔:

https://docs.oracle.com/javase/8/docs/api/

我框起來的部分:

compareAndSet 和所有其他的諸如 getAndIncrement 這種讀然後更新的操作擁有和 volatile 讀、寫一樣的內存語義。

原因就是用的到了 Lock 指令。

好,到這裏我們可以得出結論了:

compareAndSet 同時具有volatile讀和volatile寫的內存語義。

那麼問題就來了!

這個操作,在 AtomicLong 裏面也有調用:

而 AtomicLong 裏面的 value 又是被 volatile 修飾了的:

請問:為什麼 compareAndSwapLong 操作已經同時具有 volatile 讀和 volatile 寫的內存語義了,其操作的 value 還需要被 volatile 修飾呢?

這個問題也是一個朋友拋出來探討的,探討的結果是,我們都不知道為什麼:

我猜測會不會是由於操作系統不同而不同。在 x86 上面運行是這樣,其他的操作系統就不一定了,但是沒有證據。

希望知道為什麼這樣做的朋友能指點一下。

好,那麼前面說到 CAS ,那麼一個經典的面試題就來了:

請問,CAS 實現原子操作有哪些問題呢?

  • ABA問題。

  • 循環時間開銷大。

  • 只能保證一個共享變量的原子操作。

如果上面這三點你不知道,或者你說不明白,那我建議你看完本文後一定去了解一下,屬於面試常問系列。

我主要說說這個循環時間開銷大的問題。自旋 CAS 如果長時間不成功,就會對 CPU 帶來比較大的執行開銷。

而回答這個問題的朋友,大多數舉例的時候都會說: “AtomicLong 就是基於自旋 CAS 做的,會帶來一定的性能問題。巴拉巴拉……”

而我作為面試官的時候只是微笑着看着你,讓你錯以為自己答的很完美。

我知道你為什麼這樣答,因為你看了幾篇博客,刷了刷常見面試題,那裡面都是這樣寫的 :AtomicLong 就是基於自旋 CAS 做的。

但是,朋友,你可以這樣說,但是回答不完美。這題得分別從 JDK 7 和 JDK 8 去答:

JDK 7 的 AtomicLong 是基於自旋 CAS 做的,比如下面這個方法:

while(true) 就是自旋,自旋裏面純粹依賴於 compareAndSet 方法:

這個方法裏面調用的 native 的 comareAndSwapLong 方法,對應的 Lock 前綴指令就是我們前面說到的 cmpxchg。

而在 JDK 8 裏面 AtomicLong 裏面的一些方法也是自旋,但是就不僅僅依賴於 cmpxchg 指令做了,比如還是上面這個方法:

可以看到這裏面還是有一個 do-while 的循環,還是調用的 compareAndSwapLong 方法:

這個方法對應的 Lock 前綴指令是我們前面提到過的 xadd 指令。

從 Java 代碼的角度來看,都是自旋,都是 compareAndSwapLong 方法。沒有什麼差異。

但是從這篇 oracle 官網的文章,我們可以窺見 JDK 8 在 x86 平台上對 compareAndSwapLong 方法做了一些操作,使用了 xadd 彙編指令代替 CAS 操作。

xadd 指令是 fetch and add。

cmpxchg 指令是 compare and swap。

xadd 指令的性能是優於 cmpxchg 指令的。

具體可以看看這篇 oracle 官網的文章:

https://blogs.oracle.com/dave/atomic-fetch-and-add-vs-compare-and-swap

文章下面的評論,可以多注意一下,我截取其中兩個,大家品一品:

然後是這個:

總之就是:這篇文章說的有道理,我們(Dave and Doug)也在思考這個問題。所以我們會在 JIT 上面搞事情,在 x86 平台上把 CAS 操作替換為 LOCK:XADD 指令。

(這個地方我之前理解的有問題,經過朋友的指正後才修改過來。)

所以,JDK 8 之後的 AtomicLong 裏面的方法都是經過改良后, xadd+cmpxchg 雙重加持的方法。

另外需要注意的是,我怕有的朋友懵逼,專門多提一嘴:CAS 是指一次比較並交換的過程,成功了就返回 true,失敗了則返回 false,強調的是一次。而自旋 CAS 是在死循環裏面進行比較並交換,只要不返回 true 就一直循環。

所以,不要一提到 CAS 就說循環時間開銷大。前面記得加上“自旋”和“競爭大”兩個條件。

至於 JDK 8 使用 xadd 彙編指令代替 CAS 操作的是否真的是性能更好了,可以看看這篇 oracle 官網的文章:

https://blogs.oracle.com/dave/atomic-fetch-and-add-vs-compare-and-swap

文章下面的評論,可以多注意一下,我截取其中一個,大家品一品:

經過我們前面的分析,AtomicLong 從 JDK 7 到 JDK 8 是有一定程度上的性能優化的,但是改動並不大。

還是存在一個問題:雖然它可以實現原子性的增減操作,但是當競爭非常大的時候,被操作的這個 value 就是一個熱點數據,所有線程都要去對其進行爭搶,導致併發修改時衝突很大。

所以,歸根到底它的主要問題還是出在共享熱點數據上。

為了解決這個問題,Doug Lea 在 JDK 8 裏面引入了 LongAdder 類。

更加牛逼的LongAdder

大家先看一下官網上的介紹:

上面的截圖一共兩段話,是對 LongAdder 的簡介,我給大家翻譯並解讀一下。

首先第一段:當有多線程競爭的情況下,有個叫做變量集合(set of variables)的東西會動態的增加,以減少競爭。sum() 方法返回的是某個時刻的這些變量的總和。

所以,我們知道了它的返回值,不論是 sum() 方法還是 longValue() 方法,都是那個時刻的,不是一個準確的值。

意思就是你拿到這個值的那一刻,這個值其實已經變了。

這點是非常重要的,為什麼會是這樣呢?

我們對比一下 AtomicLong 和 LongAdder 的自增方法就可以知道了:

AtomicLong 的自增是有返回值的,就是一個這次調用之後的準確的值,這是一個原子性的操作。

LongAdder 的自增是沒有返回值的,你要獲取當前值的時候,只能調用 sum 方法。

你想這個操作:先自增,再獲取值,這就不是原子操作了。

所以,當多線程併發調用的時候,sum 方法返回的值必定不是一個準確的值。除非你加鎖。

該方法上的說明也是這樣的:

至於為什麼不能返回一個準確的值,這就是和它的設計相關了,這點放在後面去說。

然後第二段:當在多線程的情況下對一個共享數據進行更新(寫)操作,比如實現一些統計信息類的需求,LongAdder 的表現比它的老大哥 AtomicLong 表現的更好。在併發不高的時候,兩個類都差不多。但是高併發時 LongAdder 的吞吐量明顯高一點,它也佔用更多的空間。這是一種空間換時間的思想。

這段話其實是接着第一段話在進行描述的。

因為它在多線程併發情況下,沒有一個準確的返回值,所以當你需要根據返回值去搞事情的時候,你就要仔細思考思考,這個返回值你是要精準的,還是大概的統計類的數據就行。

比如說,如果你是用來做序號生成器,所以你需要一個準確的返回值,那麼還是用 AtomicLong 更加合適

如果你是用來做計數器,這種寫多讀少的場景。比如接口訪問次數的統計類需求,不需要時時刻刻的返回一個準確的值,那就上 LongAdder 吧

總之,AtomicLong 是可以保證每次都有準確值,而 LongAdder 是可以保證最終數據是準確的。高併發的場景下 LongAdder 的寫性能比 AtomicLong 高。

接下來探討三個問題:

  • LongAdder 是怎麼解決多線程操作熱點 value 導致併發修改衝突很大這個問題的?

  • 為什麼高併發場景下 LongAdder 的 sum 方法不能返回一個準確的值?

  • 為什麼高併發場景下 LongAdder 的寫性能比 AtomicLong 高?

先帶大家看個圖片,看不懂沒有關係,先有個大概的印象:

接下來我們就去探索源碼,源碼之下無秘密。

從源碼我們可以看到 add 方法是關鍵:

裏面有 cells 、base 這樣的變量,所以在解釋 add 方法之前,我們先看一下 這幾個成員變量。

這幾個變量是 Striped64 裏面的。

LongAdder 是 Striped64 的子類:

其中的四個變量如下:

  • NCPU:cpu 的個數,用來決定 cells 數組的大小。

  • cells:一個數組,當不為 null 的時候大小是 2 的次冪。裏面放的是 cell 對象。

  • base : 基數值,當沒有競爭的時候直接把值累加到 base 裏面。還有一個作用就是在 cells 初始化時,由於 cells 只能初始化一次,所以其他競爭初始化操作失敗線程會把值累加到 base 裏面。

  • cellsBusy:當 cells 在擴容或者初始化的時候的鎖標識。

之前,文檔裏面說的 set of variables 就是這裏的 cells。

好了,我們再回到 add 方法裏面:

cells 沒有被初始化過,說明是第一次調用或者競爭不大,導致 CAS 操作每次都是成功的。

casBase 方法就是進行 CAS 操作。

當由於競爭激烈導致 casBase 方法返回了 false 后,進入 if 分支判斷。

這個 if 分子判斷有 4 個條件,做了 3 種情況的判斷

  • 標號為 ① 的地方是再次判斷 cells 數組是否為 null 或者 size 為 0 。as 就是 cells 數組。

  • 標號為 ② 的地方是判斷當前線程對 cells 數組大小取模后的值,在 cells 數組裡面是否能取到 cell 對象。

  • 標號為 ③ 的地方是對取到的 cell 對象進行 CAS 操作是否能成功。

這三個操作的含義為:當 cells 數組裡面有東西,並且通過 getProbe() & m算出來的值,在 cells 數組裡面能取到東西(cell)時,就再次對取到的 cell 對象進行 CAS 操作。

如果不滿足上面的條件,則進入 longAccumulate 函數。

這個方法主要是對 cells 數組進行操作,你想一個數組它可以有三個狀態:未初始化、初始化中、已初始化,所以下面就是對這三種狀態的分別處理:

  • 標號為 ① 的地方是 cells 已經初始化過了,那麼這個裡面可以進行在 cell 裏面累加的操作,或者擴容的操作。

  • 標號為 ② 的地方是 cells 沒有初始化,也還沒有被加鎖,那就對 cellsBusy 標識進行 CAS 操作,嘗試加鎖。加鎖成功了就可以在這裏面進行一些初始化的事情。

  • 標號為 ③ 的地方是 cells 正在進行初始化,這個時候就在 base 基數上進行 CAS 的累加操作。

上面三步是在一個死循環裏面的。

所以如果 cells 還沒有進行初始化,由於有鎖的標誌位,所以就算併發非常大的時候一定只有一個線程去做初始化 cells 的操作,然後對 cells 進行初始化或者擴容的時候,其他線程的值就在 base 上進行累加操作。

上面就是 sum 方法的工作過程。

感受到了嗎,其實這就是一個分段操作的思想,不知道你有沒有想到 ConcurrentHashMap,也不奇怪,畢竟這兩個東西都是 Doug Lea 寫的。

然後再補充說明一下,cells 的初始化大小為 2:

cells 的最大值為 CPU 核數:

cell 是被 Contended 註解修飾了,為了解決偽共享的問題:

說起偽共享,我想起了之前的《一個困擾我122天的技術問題,我好像知道答案了》這篇文章中提到的一個猜想:

後來,我也用這個註解去解決偽共享的問題了,可惜最終的實驗結果表明不是這個原因。

那篇文章發布後有很多朋友給我反饋他們的看法,而更多的是在這條路上發現了更多更多的玄學問題,但是最終這些問題的背後都指向了同一個東西:JIT。

扯遠了,說回本文的這個 LongAdder。

總的來說,就是當沒有衝突的時候 LongAdder 表現的和 AtomicLong 一樣。當有衝突的時候,才是 LongAdder 表現的時候,然後我們再回去看這個圖,就能明白怎麼回事了:

好了,現在我們回到前面提出的三個問題:

  • LongAdder 是怎麼解決多線程操作熱點 value 導致併發修改衝突很大這個問題的?

  • 為什麼高併發場景下 LongAdder 的 sum 方法不能返回一個準確的值?

  • 為什麼高併發場景下 LongAdder 的寫性能比 AtomicLong 高?

它們其實是一個問題。

因為 LongAdder 把熱點 value 拆分了,放到了各個 cell 裏面去操作。這樣就相當於把衝突分散到了 cell 裏面。所以解決了併發修改衝突很大這個問題。

當發生衝突時 sum= base+cells。高併發的情況下當你獲取 sum 的時候,cells 極有可能正在被其他的線程改變。一個在高併發場景下實時變化的值,你要它怎麼給你個準確值?當然,你也可以通過加鎖操作拿到當前的一個準確值,但是這種場景你還用啥 LongAdder,是 AtomicLong 不香了嗎?

為什麼高併發場景下 LongAdder 的寫性能比 AtomicLong 高?

你發動你的小腦殼想一想,朋友。

AtomicLong 不管有沒有衝突,它寫的都是一個共享的 value,有衝突的時候它就在自旋。

LongAdder 沒有衝突的時候表現的和 AtomicLong 一樣,有衝突的時候就把衝突分散到各個 cell 裏面了,衝突分散了,寫的當然更快了。

一點思考

本文的題目是《我從LongAdder中窺探到了高併發的秘籍,上面就寫了兩個字……》。

那麼這兩個字是什麼呢?

就是拆分。我淺顯的覺得分佈式、高併發都是基於拆分思想的。

本文的 LongAdder 就不說了。

微服務化、分庫分表、讀寫分離……這些東西都是在拆分,把集中的壓力分散開來。

我們常常說性能不行了,那就堆機器解決,這就是在做拆分。

當然,拆分了帶來好處的同時也是有一定的問題的。

比如老大難的分佈式事務、數據聚合查詢等需求。

舉一個我遇到過的例子吧。

在寫這篇文章之前,我看了 LongAdder 源碼,了解到它這樣的結構后,知道了它和 AtomicLong 之間的差異后,我想起了之前做過的一個需求。

就是賬戶服務,有個大商戶的賬戶是一個熱點賬戶,交易非常的頻繁。

這個賬戶上的金額就相當於是一個共享的熱點數據。

我們當時的做法是把這個賬戶拆分為多個影子賬戶,這樣就把熱點賬戶拆分成了多個影子賬戶,壓力就分攤了。

其實這個思想和 LongAdder 是一脈相承的。

這個場景下拆分帶來的問題是什麼呢?

其中一個問題就是這個賬戶的總餘額是多個影子賬戶之和,而每個影子賬戶上的餘額是時刻在變化的,所以我們不能保證餘額是一個實時準確的值。

但是商戶不關心這個呀。他只關心上日餘額是準確的,每日對賬都能對上就行了。

我們在滿足需求的同時,性能還上去了。

還有一個簡單的思考是如果我們把“實現原子操作進行加減”這句話當做一個需求。

我個人拙見是這樣的,AtomicLong 類就是實現了這個需求,交付出去后,它能用,能正常工作,而且還附送了一個功能是每次都給你返回一個準確的值。

而 LongAdder 就是更加優雅的實現了這個需求,它是在原有的基礎上進行了迭代開發,功能還是能一樣的實現,沒有附加功能,但是針對某些場景來說,更好用了。

它們傳遞給我的思想不是我們常說的:先上,能跑就行,後期再迭代。

而是:它確實能跑,但是還有更加快,更加優雅的實現方式,我們可以實現它。

這是我們需要學習的地方。

最後說兩句(求關注)

才疏學淺,難免會有紕漏,如果你發現了錯誤的地方,還請你留言指出來,我對其加以修改。

感謝您的閱讀,我堅持原創,十分歡迎並感謝您的關注。

我是 why,一個被代碼耽誤的文學創作者,不是大佬,但是喜歡分享,是一個又暖又有料的四川好男人。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

美國八大州擬擴建充電站等 提升電動汽車普及率

為增加電動汽車普及率,美國加州、紐約及其它6州政府昨(24) 日聯合發布聲明,將廣設充電站、修改營建法條,務求使一般市民也能輕鬆擁有、享受電動汽車。

加州、紐約、麻州、康乃狄克、馬里蘭、羅德島、佛蒙特、奧勒崗等州,合計銷售佔比已超過美國汽車市場的25%。其中,加州是美國最大的汽車銷售市場。

據紐約時報報導,到2025年,州政府希望零碳排放汽車(包含電動汽車與燃料電池汽車)銷量至少能達到330萬輛的目標。

美國電動汽車製造商特斯拉(Tesla)昨天宣布,成功挖腳蘋果產品設計副總裁Doug Field,他在蘋果公司任職時,曾是MacBook Air、MacBook Pro、與iMac等明星級產品的幕後推手。Field之前還在福特汽車任職過,未來他將領導特斯拉汽車研發部門。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

戴姆勒與歐洲五集團合作 擬在德國大幅增設加氫站

戴姆勒集團日前宣佈,將與法國液化空氣集團、德國林德集團、奧地利石油天然氣集團(OMV)、荷蘭皇家殼牌集團、法國道達爾集團合作,啟動一項名為“H2 流動”的計畫,到2017年將德國現有的15座加氫站增加到100座,到2023年增加到400座。以效刺激氫燃料電池車市場。

除了在城市中心區大量設置加氫站之外,還將在德國主要高速公路沿線設置加氫站,兩座加氫站之間的距離不超過90公里。這項加氫站建設計畫耗資巨大。戴姆勒集團預計,為了修建400座加氫站,大約需要3.5億歐元。

加氫站是發展氫燃料電池車的必備條件,各國都在陸續興建,但此前一直進展緩慢,去年全球僅新增27座加氫站。根據美國能源部最新公佈的資料,目前美國境內只有10座公共加氫站。

由於氫燃料電池車實現了零排放,有利於保護環境,同時降低對石油的依賴,被稱為“終極環保車”。此外,氫燃料電池車續駛里程較長,且幾分鐘內就可以完成加氫工作。目前氫燃料電池車市場處於起步階段,大多採用租賃方式。

今年1月,戴姆勒與福特、日產簽署了戰略聯盟協定,在燃料電池車領域展開深度合作,聯合研發可以共用的燃料電池堆、燃料電池系統及其他燃料電池車部件,並計畫2017年推出各自的量產燃料電池車。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

雷諾擬與LG Chem合作開發時速400公里的電動車

全球最大可充電電池製造商樂金化學公司(LG Chem),和歐洲電動車先驅雷諾汽車(Renault)聯手,希望未來幾年將電動車的最高時速加快一倍。

據韓國總統朴槿惠的首席經濟幕僚趙源東(Cho Won Dong)透露,這兩家公司正在考慮開發最快時速可達400公里的電動車。目前電動車的最高時速為每小時200公里。

為加強雙邊合作,朴槿惠4日還拜會了雷諾在巴黎南方設立的電動車測試中心。

據悉,兩家公司目前還不會簽署了解備忘錄(MOU),但原則上同意互相合作,正在協商細節上的歧異。由於要開發高速電動車,鋰電池是最重要的環節之一,因此對於雷諾來說,與樂金化學的合作十分重要。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

日產結盟三菱 衝刺北美及新興電動車市場

日產和三菱汽車周二(5)宣布策略結盟,將藉由共同開發電動車,共享工廠、產能和技術,並提升北美和新興市場汽車銷量。日產在聲明中表示,「日產已與三菱達成策略結盟,此舉將擴大雷諾與日產的盟友關係。」

三菱加入後,雷諾與日產的盟友將增加至5名,還包括德國戴姆勒和俄羅斯的AvtoVAZ,共同抵禦德國福斯和日本豐田的競爭。此外,雷諾的本國對手標緻雪鐵龍(PSA Peugeot Citroen)也與通用結盟,以節省成本。

2011年,日產和三菱成立合資公司NMKV,共同研發迷你車,本次的聲明表示,「預期現有的NMKV公司將共同研發新款小車,其中包括預計在全球銷售、特定版本的電動車。」

雷諾、日產和三菱表示,結盟後的首批新車之一,將是由雷諾子公司-雷諾三星汽車在南韓生產的大型車,該車款將掛上三菱的品牌在北美市場販售。第2款較小型的車款也是為三菱設計,係由雷諾研發,生產地點尚未決定。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

中國高新汽車國際峰會 – 汽車行業思想領袖的國際峰會

  探索新一代節能智聯車輛的創新業務與技術戰略   第二屆中國高新汽車國際峰會由法蘭克福展覽(上海)有限公司和中國國家發展和改革委員會 國際合作中心聯合主辦,是真正國際化的行業思想領袖盛會。演講嘉賓和與會代表將相互交流,分享理念,推廣新策略、新協作、新技術、新服務與新應用。 與會代表在峰會中將獲得有關商業模式、產品和服務戰略、技術創新的寶貴新創意和洞察力, 將推動OEM廠商、供應商和服務提供商朝著更加可持續、更具盈利能力的未來繼續前進。    本屆峰會的演講和討論會將深入探索各種熱門主題,包括:    1.
中國汽車行業和相關市場實現可持續發展的政策、首要任務與積極措施演變 2.
國外經驗與案例分析:為汽車行業發展而採用的商業模式和技術創新 3.
促進中國國有和民營汽車企業的發展:朝著真正合作、技術轉讓和全球市場渠道邁進 4.
電動方程式賽車(Formula E)來臨中國 – 對電動車的研發,性能,安全性,可靠性和消費者的接受程度帶來潛在附帶利益 5.
個人交通的新方向如何與中國未來智能生態城市發展的目標接軌 6.
電動交通和電動化 – 使市場增長的關鍵步驟 7.
未來高增長售後市場的機會:實現盈利發展和客戶滿意度 8.
經銷商和分銷商將業務擴大至新維修市場的潛力 9.
將威力強大的數字營銷和社交媒體戰略用於汽車市場的發展:OEM廠商及零售商的最佳實 踐 10.
評估乘用車和商用車市場中的綜合機遇 11.
在中國的商用車輛和運輸車隊中減排,實現更清潔高效的運作 12.
汽車智聯技術創新實現遠程信息處理之外的新商機   聆聽各大OEM商、供應商和政府政策制定者的新見解,目前,來自國內外的多位高級別演講嘉賓業已確認出席:   ‧
中國國家發展和改革委員會:高級司長 ‧
中國科技部:高級司長 ‧
王成先生,中國汽車技術研究中心 (CATARC), 汽車產業發展研究所副 總工程師及清潔能源汽車生產力促進中心執行主任 ‧
呂洋先生, 中國海億汽車賽事管理(北京)有限公司副總裁及CEO ‧
Thomas Hajek先生, 德國菲亞特克萊斯勒集團董事會成員 ‧
Boriana Lambreva女士,大眾汽車(中國)公司新能源汽車集團戰略與規劃高 級經理 ‧
原誠寅博士, 北京汽車新能源汽車有限公司產品工程院副院長 ‧
Ridzuan Yusof先生, 馬來西亞Proton電動 車輛項目總監 ‧
Martin Rosell先生, 瑞典WirelessCar公司董事總經理 ‧
Christophe Aufrere先生,法國佛吉亞集團首席技術官 ‧
Christian Heep先生,德國電動交通協會(BEM)首席執行官兼營銷總監 ‧
董揚先生, 中國汽車工業協會(CAAM) 秘書長 ‧
Francois Schoentgen先生,大陸汽車集團動力總成變速箱控制董事總經理 ‧
Klaus Paur先生,中國益普索公司全球汽車業總監製 ‧
Ing Bernd Kraemmer先生, 奧地利iO Vehicles/ iO-E-Scooter公司董事總經理 ‧
Rebecca Zhu女士, 中國HTI業務發展總監(休斯通信公司) ‧
Rick Longobart先生,美國聖塔阿娜市 設施與車隊經理兼市政設備維護協會 (MEMA)主席 ‧
宋健教授,清華大學汽車技術研究院院長及汽車工程系教授 ‧
Madani Sahari先生, 馬來西亞汽車研究院(MAI)首席執行官 ‧
林程博士,北京理工大學電動車輛國家工程實驗室(NELEV)教授兼副主任   詳細內容請上 

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

Model S銷量不如預期 Tesla三季度財測不佳

美國電動車製造商特斯拉(Tesla Motors)於美股5日收盤後公佈的上季汽車銷量不如預期、賣給其他車商的碳權抵換交易收入下滑,加上第4季盈餘財測欠佳,導致該公司盤後股價重挫逾12%。

特斯拉表示,Q3期間電動轎車每週已可生產550台、整季出貨量達創記錄的5,500台,其中歐洲地區的出貨量超過了1,000台。不過,Model S的Q3出貨量仍不如市場樂觀的預估,市場曾預期Model S的Q3銷售量有望上升至5,850台。

展望Q4,特斯拉預估Model S的出貨量有望接近6,000台,這會讓2013年一整年的出貨量達到21,500台,而當季的本業毛利率則可望上升至25%。此外,特斯拉已在Q3開始接受來自中國大陸客戶對Model S下達的訂單,預計可在明年Q1首度交貨。

特斯拉去年的Model S出貨量僅約2,650台,不如該公司原本設定的5,000台。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

※教你寫出一流的銷售文案?

Model S爆出第三起電池起火事件 Tesla股價大跌

美國知名電動車大廠特斯拉 (Tesla)熱門車款 Model S,週三 (6 日) 傳出近日第三起電池起火事件,引發外界對於爭議性高的電動車輛安全再掀疑慮。Tesla 週三股價才因前日盤後發布第三季車輛銷售不如預期而下挫,週四股價再因上述消息大跌 7.53%,收 139.77 美元。

這起 Tesla 電池起火事件發生在美國田納西州的 Murfreesboro 市,駕駛者 Juris Shibayama 週三下午開著 Model S 駛過路上遺落的拖車掛鉤,因刺破了車輛的電池部位而造成車輛起火。所幸駕駛者在起火之前逃出,並未受傷。

據CNBC報導,這已是過去6 週 Model S 傳出的第 3 起電池起火事件。第 1 次事件發生在西雅圖,當時也是車輛駛過路上一大塊金屬物體,導致電池部分被刺穿而起火。美國國家公路交通安全管理局 (NHTSA)針對上述事件的立場是,他們正在監視情況發展,但並未展開正式調查。

而最新週三的這次車輛起火事件過後,NHTSA 仍表示目前處於搜集資料階段,將諮詢事發當局來確定事故是否與車輛安全相關。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!