主頁 > 軟體設計 > 拜占庭將軍問題和 Raft 共識演算法講解

拜占庭將軍問題和 Raft 共識演算法講解

2023-02-18 08:02:04 軟體設計

作者: 京東物流 郭益如

導讀

在分布式系統中, 什么是拜占庭將軍問題?產生的場景和解決方案是什么?什么是 Raft 共識演算法?Raft 演算法是如何解決拜占庭將軍問題的?其核心原理和演算法邏輯是什么?除了 Raft,還有哪些共識演算法?共識問題作為分布式系統的一大難點和痛點,本文主要介紹了其產生的背景、原因,以及通用的 Raft 演算法解決方案,

01 拜占庭將軍問題

【分布式對等網路中的通信容錯問題,

在分布式計算中,不同的計算機通過通訊交換資訊達成共識按照一套協作策略行動,有時候,系統中的成員計算機可能出錯而發送錯誤的資訊,用于傳遞資訊的通訊網路也可能導致資訊損壞,使得網路中不同的成員關于全體協作的策略得出不同結論,從而破壞系統一致性,這就是拜占庭將軍問題,

拜占庭將軍問題被認為是容錯性問題中最難的問題型別之一,】

9 位將軍兵分 9 路去打仗,他們各自有權力觀測敵情并做出行動判斷 —— 進攻或撤退,他們必須行動一致,即所有軍隊一起進攻或者一起撤退,否則部分進攻部分撤退會造成災難性后果,

前提:

  1. 將軍之間只能通過信使互相聯系,每位將軍將自己的判斷發送給其他將軍,并接收其他將軍發送的判斷;

  2. 收到資訊的將軍綜合所有的判斷,當超過半數都選擇進攻時,就決定進攻,當超過半數都選擇撤退時就決定撤退;

問題是,將軍中間可能出現叛徒,他可能會選擇相反的結果進行通信(投票),也可能選擇性的發送資訊,叛徒要達成的目標是:

  1. 選擇性的發送資訊,欺騙某些將軍采取進攻的行動;

  2. 促成一個錯誤的決定,比如將軍們不希望進攻時進攻;

  3. 迷惑某些將軍,使得他們無法做出決定;

如果叛徒達成了其中之一,任何的攻擊結果都是注定要失敗的,只有完全達成一致的努力才能獲得勝利,

比如,可能 9 位將軍中有 8 位忠誠的將軍和一名叛徒,8 位將軍中 4 位選擇進攻,4 位選擇撤退,叛徒分別給選擇進攻的將軍發送進攻的資訊,給選擇撤退的將軍發送撤退資訊,這樣一來,在4 位選擇進攻的將軍看,共 5 位將軍選擇進攻,從而發起進攻;而在 4 位選擇撤退的將軍看,共 5 位將軍選擇撤退,從而發起撤退,這樣各個將軍的一致性就遭到了破壞,

并且,叛徒將軍可能會偽造其他將軍的身份發送信件;

拜占庭將軍問題描述的是,在存在資訊丟失的不可靠信道上試圖通過訊息傳遞的方式達到一致性是不可能的,在系統中除了存在的訊息延遲或不可送達故障外,還可能包括訊息篡改、節點處理例外等潛在性例外,

1.1 拜占庭容錯

在早期的解決方案中,一種是 “拜占庭容錯” ,它遵循“少數服從多數”的共識機制,即使出現了錯誤或偽造的資訊,只要有問題的將軍數量不到 1/3,仍可以達到“拜占庭容錯”,使整個系統便可以正常運作,

為什么是 1/3呢?

其原理是這樣的,假設將軍總數是 N,其中正直的將軍數量是 S,反叛的將軍數量是 T, 那么 N=S+T;

為了保證即使反叛的將軍都不去投票也能產生最終的結果,那么 S 必須要超過半數,這種情況下,S 都做出相同的選擇,依然可以達成共識,即 S>T;

如果叛徒給一半支持進攻的將軍發送進攻資訊,給一半支持撤退的將軍發送撤退資訊,這種情況要保證也能產生最終的投票結果,則 X > S/2 + E;

綜合以上關系,可以得到:

N = S + T

X < S

X > S/2 + T

求解以上不等式,可以得到:

(N-T)/2 > T,即 N > 3T

所以要保證正直的將軍至少占所有將軍總數的 2/3,才有可能達成共識,

1.2 拜占庭演算法

拜占庭演算法是一種共識演算法,確定共識的原則,各個節點通過這個共識原則既可以保證一致性,又能保證基本的磁區容錯性,

共識是可容錯系統中的一個基本問題: 即使面對故障,服務器如何在共享狀態上達成一致?

02 Raft演算法

理解,首先 MCube 會依據模板快取狀態判斷是否需要網路獲取最新模板,當獲取到模板后進行模板加載,加載階段會將產物轉換為視圖樹的結構,轉換完成后將通過運算式引擎決議運算式并取得正確的值,通過事件決議引擎決議用戶自定義事件并完成事件的系結,完成決議賦值以及事件系結后進行視圖的渲染,最終將目標頁面展示到螢屏,

【Raft 演算法解決的是簡化版的拜占庭將軍問題,即在不考慮資料丟失、篡改的情況下的拜占庭將軍問題,】

假設現在有 3 位將軍 A、B、C,將軍中沒有叛徒,信使的資訊可靠,但有可能被暗殺,此時將軍們如何達成進攻的一致性決定?

方案: Raft的方案是,在所有的將軍中選出一個大將軍,用來做出所有的決定,大將軍派信使給其他將軍,如果過一段時間沒有回復(可能被暗殺)就再派一個信使,直到收到回復,

如果大將軍的信使,派出去一個被干掉一個,其他將軍們總也收不到大將軍的資訊,他們如何達成一致性決定?

方案: 每位將軍都有一個隨機時間的計時器,時間一到,他就把自己當成大將軍的候選人,派信使將選舉結果給將軍 B、C, 如果將軍 B、C 還沒有把選舉大將軍結果投給其他人(包括自己)時,他們就會把選舉票投給 A,A 將軍的信使回傳 A 時,A 將軍就知道自己收到了足夠的票數,成為了新的大將軍,

Raft 演算法是一種簡單易懂的共識演算法,它通過首先選舉一個 Leader 主節點,然后讓Leader 完全負責資料同步,依靠狀態機和主從同步的方式,在各個節點之間實作資料的一致性,

通過這種主節點進行資料同步的方式,Raft 將一致性問題拆分成了三個相對獨立的子問題:

1. 主節點選取 Leader Election: 啟動集群時,或者現有主節點失敗時,會啟動新的投票,獲得大多數選票(N/2+1)的節點會成為新的主節點;

2. 復制日志 Log Replication: 主節點從客戶端接收日志資訊,再把資訊復制到其他從節點上,使得日志資訊都能保持資料一致;

3. 安全性: Raft 定義了一系列規范來保證資料安全性,

2.1 Raft節點

Raft 演算法為節點定義了三種角色: Leader(主節點)Follower(從節點)Candidate(參與投票競爭的節點) ,節點的角色是可以轉換的,在任意的時間,每個服務器一定處于三種狀態中的一個,

每個節點上都有一個倒計時器(Election Timeout),隨機值在 150ms ~ 300ms 之間,當節點收到選舉請求,或收到 Leader 的 Heartbeat 時,就會重置倒計時,

2.1.1 主節點 Leader

通常情況下,系統中只有一個主節點,用來發起心跳,處理所有的客戶端請求,創建日志和同步日志,

2.1.2 從節點 Follower

除主節點外,其他的節點都是從節點,用于接收主節點的心跳和日志資料,保證其資料狀態與主節點一致,以及在 Leader 選舉時,投票給 Candidate,

如果有客戶端跟Follower 聯系,那么 Follower 會把請求重定向給 Leader,

2.1.3 候選人 Candidate

候選人 Candidate是在 Leader 選舉程序中的臨時角色,由 Follower 轉換而來,用于發起投票參與競選,

Raft 節點狀態圖:

圖1 Raft 節點狀態圖

啟動時,或 Follower 接收不到 Leader 資訊時,它就會變成 Candidate 并發起一次選舉,獲得集群中大多數選票的Candidate 就成為新的 Leader,

2.1.4 任期 Term

Raft 把時間分割成任意長度的任期 Term,用連續的整數標記,

圖2 各任期 Term 下的狀態演變

每一個任期都會開始一次新的選舉,一個或多個 Candidate 會嘗試成為 Leader,如果一個 Candidate 贏得了選舉,它就會在該任期內擔任 Leader,直到任期結束或者服務器宕機,在某些情況下,沒有選出 Leader(如選票瓜分等),則會開啟下一個任期并立刻開始新的選舉,

任期在 Raft 演算法中充當邏輯時鐘的作用,每一個節點都會存盤當前的 Term 號,這一編號在整個集群時期內單調增長,服務器之間通信的時候也會交換當前的 Term 號:

  • 如果一個節點發現其 Term 號比其他服務器小,那么它會更新自己的 Term 號到較大的值;
  • 如果一個 Candidate 或者 Leader 發現其 Term 號過期了,那么它會立即恢復成 Follower 狀態;
  • 如果一個節點接收到的請求中 Term 號過期了,那么它會直接拒絕此次請求,

Raft 演算法中服務器節點之間通信使用遠程程序呼叫(RPCs),并且基本的一致性演算法只需要兩種型別的 RPCs,請求投票(RequestVote) RPCs 由候選人在選舉期間發起,然后附加條目(AppendEntries)RPCs 由領導者發起,用來復制日志和提供一種心跳機制,如果未及時收到回應,則請求者有責任重試 RPC,

2.1.5 事件 Entry

每一個事件是一個 Entry,只有 Leader 可以創建 Entry,結構為 <term, index, cmd>其中 cmd 是可以應用到狀態機的操作,

2.1.6 日志 Log

日志是 Raft 的核心概念,是一個由 Entry 構成的陣列,只有 Leader 才可以改變其他節點的 Log,Leader 先把 Entry 添加到自己的 Log 陣列中,發起共識請求,獲得同意后,才會將 Entry 提交給狀態機,Follower 只能從 Leader 中獲取新日志和當前的 CommitIndex,然后把對應的 Entry 應用到自己的狀態機中,

2.2 選取主節點 Leader Election

2.2.1 選舉機制

  • raft 通過心跳機制來觸發 Leader 的選舉;
  • Leader 會向所有的 Follower 周期性發送心跳來保證自己的 Leader 地位,
  • 如果服務器能夠收到來自 Leader 或者 Candidate 的有效資訊,那么它會一直保持為 Follower 狀態,并且重繪自己的 electionElapsed,重新計時,
  • 如果一個 Follower 在一個周期內沒有收到任何資訊,也就是選舉超時,它就會認為此時沒有可用的 Leader,開始進行一次選舉以選出一個新的 Leader,
  • 當服務器啟動時,所有的節點都是 Follower,

2.2.2 選舉程序

Follower 自增的 term 號并且轉換狀態為 Candidate,然后他會向所有節點發起 RequestVoteRPC 請求, Candidate 的狀態會持續到以下情況發生:

  • 獲得大多數選票(N/2 +1),贏得選舉,成為 Leader
  • 其他節點贏得選舉
  • 一輪選舉結束,無人勝出

在 Candidate 等待選票的時候,它可能收到其他節點宣告其是 Leader 的心跳:

  • 該 Leader 的 term 號大于等于自己的 term 號,說明對方已經成為 Leader,則自己回退為 Follower,
  • 該 Leader 的 term 號小于自己的 term 號,那么會拒絕該請求并讓該節點更新 term,

為了避免出現“腦裂”,即同一時刻出現多個 Candidate,導致沒有 Candidate 獲得大多數選票的狀況,Raft 增加了隨機選舉超時時間的方法,每一個Candidate 在發起選舉后,都會隨機化一個超時時間( 150-300 毫秒),使得各個服務器分散開來,在大多數情況下只有一個服務器會率先超時,贏得選舉,

相關代碼實作:

【plain】
func (rf *Raft) RequestVote(request *RequestVoteRequest, response *RequestVoteResponse) {
  rf.mu.Lock()
  defer rf.mu.Unlock()
  defer rf.persist()
  defer DPrintf("{Node %v}'s state is {state %v,term %v,commitIndex %v,lastApplied %v,firstLog %v,lastLog %v} before processing requestVoteRequest %v and reply requestVoteResponse %v", rf.me, rf.state, rf.currentTerm, rf.commitIndex, rf.lastApplied, rf.getFirstLog(), rf.getLastLog(), request, response)




  if request.Term < rf.currentTerm || (request.Term == rf.currentTerm && rf.votedFor != -1 && rf.votedFor != request.CandidateId) {
    response.Term, response.VoteGranted = rf.currentTerm, false
    return
  }
  if request.Term > rf.currentTerm {
    rf.ChangeState(StateFollower)
    rf.currentTerm, rf.votedFor = request.Term, -1
  }
  if !rf.isLogUpToDate(request.LastLogTerm, request.LastLogIndex) {
    response.Term, response.VoteGranted = rf.currentTerm, false
    return
  }
  rf.votedFor = request.CandidateId
  rf.electionTimer.Reset(RandomizedElectionTimeout())
  response.Term, response.VoteGranted = rf.currentTerm, true
}


func (rf *Raft) StartElection() {
  request := rf.genRequestVoteRequest()
  DPrintf("{Node %v} starts election with RequestVoteRequest %v", rf.me, request)
  // use Closure
  grantedVotes := 1
  rf.votedFor = rf.me
  rf.persist()
  for peer := range rf.peers {
    if peer == rf.me {
      continue
    }
    go func(peer int) {
      response := new(RequestVoteResponse)
      if rf.sendRequestVote(peer, request, response) {
        rf.mu.Lock()
        defer rf.mu.Unlock()
        DPrintf("{Node %v} receives RequestVoteResponse %v from {Node %v} after sending RequestVoteRequest %v in term %v", rf.me, response, peer, request, rf.currentTerm)
        if rf.currentTerm == request.Term && rf.state == StateCandidate {
          if response.VoteGranted {
            grantedVotes += 1
            if grantedVotes > len(rf.peers)/2 {
              DPrintf("{Node %v} receives majority votes in term %v", rf.me, rf.currentTerm)
              rf.ChangeState(StateLeader)
              rf.BroadcastHeartbeat(true)
            }
          } else if response.Term > rf.currentTerm {
            DPrintf("{Node %v} finds a new leader {Node %v} with term %v and steps down in term %v", rf.me, peer, response.Term, rf.currentTerm)
            rf.ChangeState(StateFollower)
            rf.currentTerm, rf.votedFor = response.Term, -1
            rf.persist()
          }
        }
      }
    }(peer)
  }
}

2.3 日志同步 Log Replication

Raft 通過Leader 向集群中所有 Follower 進行日志同步來保證整個集群資料的最終一致性,

只有 Leader 有權限接受客戶端的請求并且同步資料給集群中其他節點,每一個客戶端的請求都包含一條需要被復制狀態機 RSM(Replicated State Mechine)執行的命令,Leader 收到客戶端請求后,會生成一個 Entry,包含,再將這個 entry 添加到自己的日志末尾后,向所有的節點廣播該 Entry,要求其他服務器復制這條 Entry,

圖3 主從節點進行 Entry 復制

如圖所示,Logs 日志是一個順序存盤的 Entry 陣列,方框內是任期 Term 號,

2.3.1 日志同步流程

例如,在 Term3 中,Leader 最后一個 Entry 的Index 為 7,x 值為 5,收到請求 set x=4時:

圖4 日志同步流程

  1. Leader 收到客戶端請求 x←4 時,Leader 會生成一條新的 Entry<8, 3, set x=4>,并將該 Entry 添加到自己的 Log 陣列最后

  2. Leader 通過 AppendEntries RPC 廣播該 Entry;

  3. 如果 Follower 接受該 Entry,則會將 Entry 添加到其日志后面,同時回傳給 Leader 同意,

  4. 如果 Leader 收到了多數的成功回應,Leader 認為這個 Entry 是 committed,應用到自己的狀態機 RSM 中,并且向客戶端回傳執行結果,之后,該 commited 資訊會隨著之后的 AppendEntryRPC 傳達到其他節點,

  5. committed 表示被 Leader 創建的 Entry 已經復制到了大多數的服務器上,Leader 會跟蹤它記錄的最大索引值 Index,并在之后的 AppendEntries RPC(包括心跳)中,包含該索引值,以此確保其他服務器同步這個 Entry 已經提交,Follower 接收到該資訊后,也會按順序同步更新到本地的狀態機中,

Raft 通過這種日志機制來保證不同服務器上日志的一致性和安全性:

  • 在兩個日志里,有兩個 entry 擁有相同的 index 和 term,那么它們一定有相同的 cmd;
  • 在兩個日志里,有兩個 entry 擁有相同的 index 和 term,那么它們前面的 entry 也一定相同,

2.3.2 Leader Crash

一般情況下,Leader 和 Follower 的日志保持一致,AppendEntries 的一致性檢查通常不會失敗,然后,Leader Crash 可能會導致資料丟失:

圖5 Leader Crash時的資料狀況

當最上面的 Leader 掌權后,Follower 日志可能有 a~f 幾種情況:

1. 日志丟失(a,b);

2. Follower 含有未提交資料(c、d);

3. 日志丟失 + Follower 含有未提交資料(e、f);

場景 f 可能出現的情況為:

如果一臺服務器在 Term2 時是 Leader,并且向它的日志中添加了一些資料條目,然后在資料提交前宕機了;接著該 Leader 很快重啟后,又稱為了任期 3 的 Leader,接著又向它的日志中添加了一些資料,然后在 Term2,Term3 資料條目提交前,又宕機了,之后一直處于宕機狀態,直到有新的 Leader 產生,

當遇到這種一致性檢查失敗的情況時,Leader 通過強制 Follower 復制自己的日志來處理日志的不一致,這就意味著,在 Follower 上的沖突日志會被領導者的日志覆寫

Leader 找到 Follower 與它日志一致的地方(Index=3),然后洗掉 Follower 在該位置之后的日志,接著把這之后的日志發送給 Follower:

Leader 給每一個Follower 維護了一個 nextIndex,它表示 Leader 將要發送給該追隨者的下一條日志條目的索引,當一個 Leader 開始掌權時,它會將 nextIndex 初始化為它的最新的日志條目索引數+1,如果一個 Follower 的日志和 Leader 的不一致,AppendEntries 一致性檢查會在下一次 AppendEntries RPC 時回傳失敗,在失敗之后,Leader 會將 nextIndex 遞減然后重試 AppendEntries RPC,最終 nextIndex 會達到一個 Leader 和 Follower 日志一致的地方,這時,AppendEntries 會回傳成功,Follower 中沖突的日志條目都被移除了,并且添加所缺少的上了 Leader 的日志條目,一旦 AppendEntries 回傳成功,Follower 和 Leader 的日志就一致了,這樣的狀態會保持到該任期結束,

相關實作代碼:

【plain】
func (rf *Raft) replicateOneRound(peer int) {
  rf.mu.RLock()
  if rf.state != StateLeader {
    rf.mu.RUnlock()
    return
  }
  prevLogIndex := rf.nextIndex[peer] - 1
  if prevLogIndex < rf.getFirstLog().Index {
    // only snapshot can catch up
    request := rf.genInstallSnapshotRequest()
    rf.mu.RUnlock()
    response := new(InstallSnapshotResponse)
    if rf.sendInstallSnapshot(peer, request, response) {
      rf.mu.Lock()
      rf.handleInstallSnapshotResponse(peer, request, response)
      rf.mu.Unlock()
    }
  } else {
    // just entries can catch up
    request := rf.genAppendEntriesRequest(prevLogIndex)
    rf.mu.RUnlock()
    response := new(AppendEntriesResponse)
    if rf.sendAppendEntries(peer, request, response) {
      rf.mu.Lock()
      rf.handleAppendEntriesResponse(peer, request, response)
      rf.mu.Unlock()
    }
  }
}


func (rf *Raft) AppendEntries(request *AppendEntriesRequest, response *AppendEntriesResponse) {
  rf.mu.Lock()
  defer rf.mu.Unlock()
  defer rf.persist()
  defer DPrintf("{Node %v}'s state is {state %v,term %v,commitIndex %v,lastApplied %v,firstLog %v,lastLog %v} before processing AppendEntriesRequest %v and reply AppendEntriesResponse %v", rf.me, rf.state, rf.currentTerm, rf.commitIndex, rf.lastApplied, rf.getFirstLog(), rf.getLastLog(), request, response)


  if request.Term < rf.currentTerm {
    response.Term, response.Success = rf.currentTerm, false
    return
  }


  if request.Term > rf.currentTerm {
    rf.currentTerm, rf.votedFor = request.Term, -1
  }


  rf.ChangeState(StateFollower)
  rf.electionTimer.Reset(RandomizedElectionTimeout())


  if request.PrevLogIndex < rf.getFirstLog().Index {
    response.Term, response.Success = 0, false
    DPrintf("{Node %v} receives unexpected AppendEntriesRequest %v from {Node %v} because prevLogIndex %v < firstLogIndex %v", rf.me, request, request.LeaderId, request.PrevLogIndex, rf.getFirstLog().Index)
    return
  }


  if !rf.matchLog(request.PrevLogTerm, request.PrevLogIndex) {
    response.Term, response.Success = rf.currentTerm, false
    lastIndex := rf.getLastLog().Index
    if lastIndex < request.PrevLogIndex {
      response.ConflictTerm, response.ConflictIndex = -1, lastIndex+1
    } else {
      firstIndex := rf.getFirstLog().Index
      response.ConflictTerm = rf.logs[request.PrevLogIndex-firstIndex].Term
      index := request.PrevLogIndex - 1
      for index >= firstIndex && rf.logs[index-firstIndex].Term == response.ConflictTerm {
        index--
      }
      response.ConflictIndex = index
    }
    return
  }


  firstIndex := rf.getFirstLog().Index
  for index, entry := range request.Entries {
    if entry.Index-firstIndex >= len(rf.logs) || rf.logs[entry.Index-firstIndex].Term != entry.Term {
      rf.logs = shrinkEntriesArray(append(rf.logs[:entry.Index-firstIndex], request.Entries[index:]...))
      break
    }
  }


  rf.advanceCommitIndexForFollower(request.LeaderCommit)


  response.Term, response.Success = rf.currentTerm,True
}

2.3.3 安全性

Leader 需要保證其存盤全部已經提交的日志條目,保證日志條目只能從 Leader 流向 Follower,且 Leader 永遠不會覆寫已經存在的日志條目,

每個 Candidate 發送 RequestVoteRPC 時,都會帶上最后一個 Entry 的資訊,所有節點收到投票資訊時,會對該 Entry 進行比較,如果發現自己的更新,則拒絕投票給該 Candidate,

03 其他一致性演算法

理解,首先 MCube 會依據模板快取狀態判斷是否需要網路獲取最新模板,當獲取到模板后進行模板加載,加載階段會將產物轉換為視圖樹的結構,轉換完成后將通過運算式引擎決議運算式并取得正確的值,通過事件決議引擎決議用戶自定義事件并完成事件的系結,完成決議賦值以及事件系結后進行視圖的渲染,最終將目標頁面展示到螢屏,

3.1 Paxos 演算法

早期的共識演算法,由拜占庭將軍問題的提出者 Leslie Lamport 所發明,谷歌的分布式鎖服務 Chubby 就是以 Paxos 演算法為基礎,

3.2 ZAB 演算法

Zookeeper 所使用的一致性演算法,在流程上和 Raft 演算法比較接近,

3.3 PBFT 演算法

區塊鏈技術所使用的共識演算法之一,適用于私有鏈的共識,

04 總結

理解,首先 MCube 會依據模板快取狀態判斷是否需要網路獲取最新模板,當獲取到模板后進行模板加載,加載階段會將產物轉換為視圖樹的結構,轉換完成后將通過運算式引擎決議運算式并取得正確的值,通過事件決議引擎決議用戶自定義事件并完成事件的系結,完成決議賦值以及事件系結后進行視圖的渲染,最終將目標頁面展示到螢屏,

Raft 演算法是很廣泛的強一致性、去中心化和高可用的分布式協議,是一種 leader-based 的共識演算法,通過將共識問題拆分成主節點選舉和主從日志同步,以及安全流程,來提高分布式系統的資料一致性、可靠性和容錯性;首先選舉主節點,然后主節點負責接收外部請求、資料復制、提交,保證系統中資料都是一致的,

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/544228.html

標籤:架構設計

上一篇:多執行緒并發:以AQS中acquire()方法為例來分析多執行緒間的同步與協作

下一篇:拜占庭將軍問題和 Raft 共識演算法講解

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more