Ведение данных из базы данных в горизонтальном масштабе - PullRequest
0 голосов
/ 04 сентября 2018

Допустим, у нас есть микросервис "А". Теперь мы масштабируем его по горизонтали, что означает, что у нас есть 3 экземпляра «A», работающих с одним экземпляром БД (и схема, как правило, предполагает, что 3 экземпляра «A» могут выполнять чтение и запись для одних и тех же данных).

Теперь я продемонстрирую вопрос с помощью некоторого псевдокода, у нас есть следующая функция обновления в «A»:

Product p = getProdFromDb(); // for example selecting 
// from Postgresql db

p.updateInnerData(); // synch method that updates 
// something inside the p model that takes significant 
// amount of time
p.updateInDb(); //  for example update back in postgresql

Проблема здесь в том, что другие экземпляры "A" могут изменять продукт p, пока мы обновляем его здесь (не в этой функции, а в связи с такими другими функциями, которые изменяют продукты в "A"). Одно из известных мне решений - это использование блокировки на БД (например, с помощью «Выбрать ... для обновления»), но это создает узкое место в производительности в этой функции. Я хотел бы видеть лучшие решения, которые решают эту проблему без этого узкого места, реальные примеры в Java (или JS) были бы очень полезны.

Изменить: предположить, что разделение не является вариантом

Ответы [ 2 ]

0 голосов
/ 04 сентября 2018

Существует два вида блокировок: пессимистическая (та, которую вы пытаетесь избежать) и оптимистическая блокировка.

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

Один из способов сделать это - иметь столбец version, который увеличивается каждый раз, когда вы изменяете сущность. Когда вы пытаетесь сохранить, вы ожидаете, что сущность с version = version + 1 не существует. Если он уже существует, это означает, что произошло параллельное обновление, и вы повторите попытку (загрузка + изменение + сохранение).

В псевдокоде алгоритм выглядит так:

function updateEntity(ID, load, mutate, create)

    do
    {
        entity, version = load(ID) or create entity
        entity = mutate entity
        updateRow(that matches the ID and version) and increment version
    }
    while (row has not changed and was not inserted)

Я дам вам также пример кода на PHP (надеюсь, это легко понять) для MongoDB:

class OptimisticMongoDocumentUpdater
{

    public function addOrUpdate(Collection $collection, $id, callable $hidrator, callable $factory = null, callable $updater, callable $serializer)
    {
        /**
         * We try to add/update the entity in a concurrent safe manner
         * using optimistic locking: we always try to update the existing version;
         * if another concurrent write has finished before us in the mean time
         * then retry the *whole* updating process
         */

        do {
            $document = $collection->findOne([
                '_id' => new ObjectID($id),
            ]);

            if ($document) {
                $version = $document['version'];
                $entity = \call_user_func($hidrator, $document);
            } else {
                if (!$factory) {
                    return;//do not create if factory does not exist
                }
                $entity = $factory();
                $version = 0;
            }

            $entity = $updater($entity);

            $serialized = $serializer($entity);

            unset($serialized['version']);

            try {
                $result = $collection->updateOne(
                    [
                        '_id'     => new ObjectID($id),
                        'version' => $version,
                    ],
                    [
                        '$set' => $serialized,
                        '$inc' => ['version' => 1],
                    ],
                    [
                        'upsert' => true,
                    ]
                );
            } catch (\MongoDB\Driver\Exception\WriteException $writeException) {
                $result = $writeException->getWriteResult();
            }

        } while (0 == $result->getMatchedCount() && 0 == $result->getUpsertedCount());//no side effect? then concurrent update -> retry
    }
}
0 голосов
/ 04 сентября 2018

В моем ответе я предполагаю, что вы хотите 100% надежности.

Если это так, вы можете разделить свои таблицы на множество страниц, где каждая страница будет содержать X строк. когда вы попытаетесь обновить таблицу, вы заблокируете только эту страницу, но тогда будет больше операций ввода-вывода.

Кроме того, в вашей базе данных вы можете настроить ее так, чтобы команда select читала даже незафиксированные строки, что повысит скорость - для сервера SQL это SELECT WITH (NOLOCK)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...