【问题标题】:Keeping data from db syned with horizontal scale保持数据库中的数据与水平比例同步
【发布时间】:2020-02-13 20:49:00
【问题描述】:

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

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

【问题讨论】:

  • 请说明您想要改善的性能瓶颈是什么。你想选择更快,还是更新?
  • 我不希望他们都在不使用锁的情况下快速工作(如果可能的话)

标签: java architecture relational-database microservices horizontal-scaling


【解决方案1】:

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

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

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

【讨论】:

  • 您的回答很有道理,但我不明白代码示例中版本检查的位置。我也不确定这是否消除了瓶颈,它肯定是一个变化,但是如果你需要在你的服务的多个实例中并行处理 10,000 个这样的更新请求,你仍然会等待很多时间
  • @ZivGlazer 此代码尝试更新与某些_idversion 匹配的实体(加载对象时的version);如果它找不到具有该版本的实体,则意味着它已经并行更新。
  • @ZivGlazer 如果请求不在同一个实体上会更快,因为没有锁。
  • 据我了解,您在服务器代码中实现了锁,而不是使用传统的数据库锁。 db lock也可以在特定的行上工作(我认为在关系dbs中,不确定nosql)所以最好使用db locks
  • @ZivGlazer 没有锁,真的。自己看吧。只是一个基于版本的查询的简单更新
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

    如果p.updateInnerData(); 花费时间 X 并且您的 IO 速率很高,可能不会向微服务发送 Y 新请求,那么函数本身就构成了性能瓶颈。理想情况下,微服务数据库相关操作应该设计清晰的性能基准;这通常会导致我们选择能够实现您想要实现的高/预期 IOPS 的数据库本身。

    1. 使用 RDBMS 作为目标数据库,一种可能的选择可能是解耦与 p.updateInnerData(); 相关的昂贵的数据库操作,并通过可靠的消息传递平台使其异步,从而确保严格排序而不会影响速度;例如卡夫卡;我们甚至可以考虑通过将 p 对象/更改本身存储在 Db 表中的 BLOB/JSON 来备份消息,并立即将控制权返回给用户,然后异步触发消息。

    2. 由于 NoSQL 是目标数据库,我们希望根据我们的 READ/WRITE 需求进行选择,从而大大减少与写入和读取相关的延迟。

    【讨论】:

      猜你喜欢
      • 2010-09-14
      • 2010-10-29
      • 2011-07-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-03
      • 2017-09-01
      相关资源
      最近更新 更多