保持数据库的数据与水平比例同步

问题描述 投票:4回答:2

让我们假设我们有一个微服务“A”。我们现在正在水平扩展它,这意味着我们有3个“A”实例在同一个数据库实例上工作(而schemea,通常假设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”中的产品的其他功能)。我知道的一个解决方案是在数据库上使用锁(例如使用“Select ... for Update”),但它会在此函数中产生性能瓶颈。我希望看到更好的解决方案,解决这个问题,没有这个瓶颈,Java(或JS)中的真实例子将非常有帮助。

编辑:假设分区不是一个选项

java architecture relational-database microservices horizontal-scaling
2个回答
3
投票

有两种锁定:悲观(你试图避免的那种)和乐观锁定。

在乐观锁定中,您不会保留任何锁定,而是尝试保存文档;如果文档已经同时修改(意味着它自我们加载后就被修改了),那么你重试整个过程(加载+变异+保存)。

一种方法是使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)

我将为您提供一个用于MongoDB的PHP代码示例(希望它易于理解):

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
    }
}

2
投票

在我的回答中,我假设你想要100%的可靠性。

如果是这种情况,您可以将表格划分为多个页面,其中每个页面将包含X行数。当您尝试更新表时,您将只锁定该页面,但随后会有更多的I / O.

此外,在您的数据库上,您可以对其进行配置,以便select命令甚至可以读取未提及的行,这将提高速度 - 对于SQL服务器,它是SELECT WITH (NOLOCK)

© www.soinside.com 2019 - 2024. All rights reserved.