Как FoundationDB обрабатывает конфликтующие транзакции? - PullRequest
0 голосов
/ 25 мая 2018

Мне интересно, как FoundationDB обрабатывает ситуацию, когда несколько транзакций пытаются обновить один и тот же ключ?

Если один клиент выполняет эту транзакцию:

db.run((Transaction tr) -> {
  tr.set(Tuple.from("key").pack(), Tuple.from("valueA").pack());
  return null;
});

В то время как другойклиент выполняет конфликтующую транзакцию:

db.run((Transaction tr) -> {
  tr.set(Tuple.from("key").pack(), Tuple.from("valueB").pack());
  return null;
});

Что будет происходить внутри FoundationDB для разрешения этого конфликта?

1 Ответ

0 голосов
/ 25 мая 2018

Недавно я изучал и тестировал FoundationDB (я думаю, что все сейчас играют с ним), и в рамках своих исследований я провел несколько простых тестов.Один из них должен ответить на ваши вопросы:

Итак, ниже приведен пример (надеюсь, вы не возражаете против Scala):

import com.apple.foundationdb._
import com.apple.foundationdb.tuple._
import resource.managed

import scala.collection.mutable
import scala.util.Random

object Example {

  val THREAD_COUNT = 1

  @volatile var v0: Long = 0
  @volatile var v1: Long = 0
  @volatile var v2: Long = 0
  @volatile var v3: Long = 0
  @volatile var v4: Long = 0

  def doJob(db: Database, x: Int): Unit = {
    db.run((tr) => {
      val key = Tuple.from("OBJ", Long.box(100)).pack()

      val current = Tuple.fromBytes(tr.get(key).join())
      if (Random.nextInt(100) < 2) {
        out(current)
      }

      val next = mutable.ArrayBuffer(current.getLong(0), current.getLong(1), current.getLong(2), current.getLong(3), current.getLong(4))

      if (x == 1 && v1 == next(1)) { println(s"again: $v1, v0=$v0, 0=${next(0)}")}
      if (x == 0 && v0 > next(0)) { out(current); ??? } else { v0 = next(0)}
      if (x == 1 && v1 > next(1)) { out(current); ??? } else { v1 = next(1)}
      if (x == 2 && v2 > next(2)) { out(current); ??? } else { v2 = next(2)}
      if (x == 3 && v3 > next(3)) { out(current); ??? } else { v3 = next(3)}
      if (x == 4 && v4 > next(4)) { out(current); ??? } else { v4 = next(4)}

      next.update(x, next(x) + 1)
      val nv = Tuple.from(next.map(v => Long.box(v)) :_*)

      tr.set(key, nv.pack())
    })

  }

  def main(args: Array[String]): Unit = {
    if (THREAD_COUNT > 5) {
      throw new IllegalArgumentException("")
    }

    val fdb: FDB = FDB.selectAPIVersion(510)
    for (db <- managed(fdb.open())) {
      // Run an operation on the database
      db.run((tr) => {
        for (x <- 0 to 10000) {
          val k = Tuple.from(s"OBJ", x.toLong.underlying()).pack()
          val v = Tuple.from(Long.box(0), Long.box(0), Long.box(0), Long.box(0), Long.box(0)).pack()
          tr.set(k, v)
          null
        }
      })


      val threads = (0 to THREAD_COUNT).map { x =>
        new Thread(new Runnable {
          override def run(): Unit = {
            while (true) {
              try {
                doJob(db, x)
              } catch {
                case t: Throwable =>
                  t.printStackTrace()
              }
            }
          }
        })
      }

      threads.foreach(_.start())
      threads.foreach(_.join())


    }
  }

  private def out(current: Tuple) = {
    println("===")
    println((v0, v1, v2, v3, v4))
    println((Thread.currentThread().getId, current))
  }
}

Итак, эта вещь позволяет вам начать запись нескольких потоковв тот же объект.Есть некоторый ненужный код, оставленный от других экспериментов, игнорируйте его (или используйте для своих собственных экспериментов).

Этот код порождает ваши потоки, затем каждый поток читает кортеж из пяти long, например, (0,1,0,0,0) из ключа ("OBJ", 100), затем увеличивает значение, соответствующее номеру потока, затем записывает его обратно и увеличивает один из энергозависимых счетчиков.

И это мои наблюдения:

  1. При запуске этого примера, настроенного сВ одном потоке вы увидите, что он пишет очень быстро,
  2. Когда вы увеличите параллелизм, вы заметите, что ваши записи замедляются (ожидается) ...
  3. ... И вы увидите, чтоэтот код выполняется время от времени: println(s"again: $v1, v0=$v0, 0=${next(0)}")

Таким образом, по существу, когда возникает конфликт, клиенты FoundationDB пытаются зафиксировать транзакции, пока они не завершатся успешно.Вы можете найти более подробную информацию в этой главе документации.Затем посмотрите на обзорную диаграмму архитектуры .

Кроме того, транзакции не являются просто функциями.Надеемся - идемпотентные функции .

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

Hopeэто отвечает на ваш вопрос.

Я бы посоветовал вам прочитать все официальные документы , чтобы вы могли найти там много интересного, включая то, как разработчики баз данных рассматривают теорему CAP , хорошие примеры классные распределенные структуры данных и много других технических деталей и интересных вещей.

...