В мрачной тьме далекого будущего есть только потеря данных. Создание простой репликации данных для ваших приложений.

Репликация. Что это?

Репликация данных - это процесс синхронизации данных между вашими сервисами (базами данных, приложениями и т. Д.). Это может помочь вам не потерять данные в критических инцидентах и ​​легко масштабировать возрастающую нагрузку на ваши сервисы.

Replication description from the Wiki:
Replication in computing involves sharing information so as to ensure consistency between redundant resources, such as software or hardware components, to improve reliability, fault-tolerance, or accessibility.

Например, вы также можете сохранить ограничения сторонних служб API, если только один экземпляр вашего приложения делает запросы и передает их другим экземплярам.

Репликация поможет вам сохранить данные при сбое некоторых приложений. В нужный момент это избавит вас от множества стрессов. Просто поверьте.

В порядке. Предположим, я убедил вас, и вы хотите сделать собственную репликацию в своем приложении.

Но как?

Первые шаги. Создание модели транзакции.

Базовая транзакция должна иметь некоторые данные и идентификатор для перевода.

// In my opinion, protobufs is the best solution for the replication.
// Our identifier will be unix nano timestamp 
syntax = "proto3";

message Transaction {
    int64 timestamp = 1;
    map<string, bytes> data = 2;
}

После его компиляции с помощью protoc в коде будет модель транзакции.

Давайте создадим пул транзакций в качестве наилучшей практики.

var transactionPool = sync.Pool{
   New: func() interface{} {
      return new(Transaction)
   },
}

func GetTransaction() *Transaction {
   return transactionPool.Get().(*Transaction)
}

func PutTransaction(transaction *Transaction) {
   transaction.Reset()
   transactionPool.Put(transaction)
}

Теперь мы можем создавать транзакции:

func NewTransaction(data map[string][]byte) *Transaction {
   transaction := GetTransaction()
   transaction.Timestamp = time.Now().UnixNano()
   transaction.Data = data
   return transaction
}

Делаем репликацию.

Простая репликация должна иметь передатчик и приемник (для транзакций отправки и получения), список полученных транзакций и список любых хостов приложений для отправки транзакций.

type Replication struct {
   Transmitter chan *Transaction
   Receiver    chan *Transaction

   Received map[string]int64

   Replicas map[string]string
}

Создадим логику для транзакций передачи:

func (r *Replication) Transmit() {
   for {
      transaction := <-r.Transmitter

      go func() {
         body, err := proto.Marshal(transaction)

         if err == nil {
            for addr, host := range r.Replicas {
              //That's url what you services will listen for transactions
              _, err := http.Post(host + "/replication/receiver", "application/protobuf", bytes.NewReader(body))
               
               if err != nil {
                  log.Println("Master", addr, "is down:", err)
               }
            }
         } else {
            log.Println("Replication error:", err)
         }

         PutTransaction(transaction)
      }()
   }
}

А для приема транзакций:

func (r *Replication) Receive() {
   for {
      transaction := <-r.Receiver

      go func() {
         for key, data := range transaction.Data {
            if timestamp, ok := r.Received[key]; ok && timestamp <= transaction.Timestamp {
               continue
            }
            // Put transaction data to you data store
            err := database.Client.Put(key, data)

            if err == nil {
               r.Received[key] = transaction.Timestamp
            } else {
               log.Println("Replication error:", err)
            }
         }

         PutTransaction(transaction)
      }()
   }
}

Механизм отправки сообщения:

func (r *Replication) SendMessage(data map[string][]byte) {
   if len(r.Replicas) > 0 {
      r.Transmitter <- NewTransaction(data)
   }
}

Теперь мы можем запустить репликацию в приложении:

replication := &Replication{
   Transmitter: make(chan *Transaction, 2048),
   Receiver:    make(chan *Transaction, 2048),
   Received:    make(map[string]int64),
   Replicas:    hosts,
}

Использование репликации

Просто запустите приемник и передатчик в новом потоке

go replication.Receive()
go replication.Transmit()

И создайте простой метод получения новых транзакций для других экземпляров:

http.HandleFunc("/replication/receiver", func(writer http.ResponseWriter, request *http.Request) {
   transaction := GetTransaction()
   body, _ := ioutil.ReadAll(request.Body)
   err := proto.Unmarshal(body, transaction)
   if err == nil {
      replication.Receiver <- transaction
      fmt.Fprintln(writer, "Received transaction")
   } else {
      fmt.Fprintln(writer, err)
   }
})

Теперь вы можете синхронизировать данные между экземплярами вашего приложения с помощью метода:

replication.SendMessage()

Теперь репликация готова к использованию, осталось поднять несколько экземпляров и указать их хосты в реплике!

Вывод

Репликация действительно может спасти жизнь вашему продукту. Но это не панацея.

Потому что все ваши серверы всегда могут выйти из строя одновременно.

Для полной безопасности наймите хороших DevOpses и арендуйте столько серверов, сколько они вам скажут. Это лучшая идея, которую вы можете воплотить в жизнь.

Репозиторий GitHub с реализацией репликации находится здесь.

📝 Прочтите этот рассказ позже в Журнале.

🗞 Просыпайтесь каждое воскресенье утром и слышите самые интересные истории, мнения и новости недели, ожидающие в вашем почтовом ящике: Получите заслуживающий внимания информационный бюллетень›