Если вам нравится читать статьи на Medium и вы заинтересованы в том, чтобы стать участником, я буду рад поделиться с вами своей реферальной ссылкой!



Сложность управления распределенными системами, особенно во время обработки транзакций, привела к появлению различных шаблонов и протоколов для управления этими сценариями. Хотя шаблон двухфазной фиксации (2PC) предлагает решение, у него есть свои недостатки. Одной из критических уязвимостей является его подверженность сбою в случае сетевого раздела. Для решения этой проблемы был введен шаблон трехфазной фиксации (3PC), который сделал протокол неблокирующим, тем самым устранив неопределенное ожидание ответов от других узлов. В этой статье представлен общий обзор шаблона трехфазной фиксации и пример кода Go, иллюстрирующий его работу.

Шаблон трехфазной фиксации (3PC)

Шаблон трехфазной фиксации является усовершенствованием шаблона двухфазной фиксации. Он вводит дополнительную фазу, фазу PreCommit, чтобы избежать проблемы блокировки в 2PC. Три этапа протокола следующие:

  1. Фаза 1 — Фаза голосования. Как и в 2PC, узел-координатор отправляет запрос на фиксацию транзакции всем узлам-участникам. Затем каждый участник проголосует «Да», чтобы зафиксировать, или «Нет», чтобы отменить транзакцию.
  2. Фаза 2 — Фаза PreCommit: если все участники проголосовали за, координатор отправляет сообщение PreCommit всем узлам, указывающее, что транзакция скоро будет зафиксирована. Если какой-либо узел проголосовал «Нет», координатор отправляет всем узлам сообщение Abort.
  3. Фаза 3 — Фаза фиксации/отмены: после фазы предварительной фиксации координатор отправляет сообщение фиксации всем узлам-участникам, подтверждая фиксацию транзакции. При возникновении каких-либо ошибок координатор отправляет сообщение о прерывании.

Реализация шаблона трехфазной фиксации (3PC) в Go

Go с его эффективной обработкой параллелизма через горутины и каналы — отличный выбор для иллюстрации алгоритмов распределенных систем. Упрощенный пример кода Go для демонстрации шаблона трехфазной фиксации выглядит следующим образом:

package main

import (
 "fmt"
 "sync"
)

type Transaction struct {
 Data string
}

type Node struct {
 ID int
}

type Coordinator struct {
 Nodes []*Node
}

func (c *Coordinator) ThreePhaseCommit(transaction *Transaction) bool {
 var wg sync.WaitGroup
 results := make(chan bool, len(c.Nodes))

 for _, node := range c.Nodes {
  wg.Add(1)

  go func(node *Node) {
   defer wg.Done()

   // Phase 1: Voting phase
   fmt.Printf("Node %v: received VOTE-REQ for transaction\n", node.ID)
   vote := vote(node, transaction)

   // Sending vote to the coordinator
   results <- vote
  }(node)
 }

 go func() {
  wg.Wait()
  close(results)
 }()

 // The coordinator collects votes
 for result := range results {
  if !result {
   fmt.Println("Transaction abort")
   return false
  }
 }

 // Phase 2: PreCommit phase
 for _, node := range c.Nodes {
  fmt.Printf("Node %v: received PRE-COMMIT for transaction\n", node.ID)
 }

 // Phase 3: Commit phase
 for _, node := range c.Nodes {
  fmt.Printf("Node %v: COMMIT transaction\n", node.ID)
 }

 fmt.Println("Transaction commit")
 return true
}

func vote(node *Node, transaction *Transaction) bool {
 // Here logic of voting for a transaction would be implemented
 // e.g. checking constraints or locking resources
 return true
}

func main() {
 nodes := []*Node{{ID: 1}, {ID: 2}, {ID: 3}}
 coordinator := &Coordinator{Nodes: nodes}

 transaction := &Transaction{Data: "example transaction"}

 coordinator.ThreePhaseCommit(transaction)
}

В этой функции main мы создаем несколько узлов и координатора, а затем запускаем транзакцию с помощью метода ThreePhaseCommit.

Заключение

Шаблон трехфазной фиксации (3PC), являющийся расширением шаблона двухфазной фиксации (2PC), вводит дополнительную фазу предварительной фиксации для решения проблемы блокировки, обнаруженной в 2PC. Протокол 3PC, хотя и более сложный, обеспечивает лучшую отказоустойчивость в распределенных системах. Однако важно отметить, что этот шаблон не является полностью отказоустойчивым. Он подвержен сбоям, когда сеть разделена на несколько сегментов, где каждый сегмент может взаимодействовать внутри себя, но не с другими сегментами. В таких случаях алгоритмы консенсуса, такие как Paxos или Raft, могут быть лучшим решением. Этот пример Go призван проиллюстрировать основную работу протокола 3PC в его простейшей форме, реальная реализация может включать в себя более сложные функции, такие как сетевые коммуникации, отказоустойчивость и постоянное хранилище.