合理设计数据结构以实现高度一致性

Datastore 可将数据分布到多台机器,并在广泛的地理区域中使用无主 (Masterless) 同步复制,因而具备高可用性、可扩缩性和耐用性。但是,作为此设计的折衷,任何单个实体组的写入吞吐量被限制为大约每秒提交一次。跨多个实体组的查询或事务也存在限制。本页面更详细地描述了这些限制,并讨论了构建数据结构的最佳做法,以便在支持高度一致性的同时仍满足应用的写入吞吐量要求。

一致性级别

Datastore 查询可以按以下任何一种一致性级别交付结果:

  • 具备高度一致性的查询可以保证结果最新,但可能需要较长时间才能完成,或者在某些情况下可能不受支持。
  • 具备最终一致性的查询通常运行速度更快,但有时会返回过时的结果。

在最终一致性查询中,也会按最终一致性访问用于收集结果的索引。因此,此类查询有时可能返回不再符合查询条件的实体,也可能会忽略某些符合查询条件的实体。高度一致性查询在事务上是一致的,意味着结果基于单个一致的数据快照。

一致性保证

查询返回的结果具有不同级别的一致性保证,具体取决于查询的性质:

  • 祖先查询(即针对实体组执行的查询)在默认情况下具备高度一致性,但可以通过设置 Datastore 读取政策使其具备最终一致性(见下文)。
  • 全局查询(不针对实体组执行的查询)始终保持最终一致。

在许多应用中,在全面了解不相关数据时,可以使用最终一致性(即跨多个实体组进行全局查询,这有时可能返回稍过时的数据),而在查看或修改一组高度相关的数据时,可以使用高度一致性(祖先查询,或查询单个实体)。在此类应用中,将高度相关的数据放入实体组通常是一种很好的做法。实体组数量增多导致吞吐量提高,实体组数量减少则增加了可以在单个祖先查询中读取的实体的量。应用需要考虑这一点,以确定吞吐量与一致性之间的适当平衡。

Datastore 读取政策

为了提高性能,您可以设置查询的“读取政策”以使结果最终一致。(Datastore API 也允许您明确设置高度一致性政策,但此设置没有实际影响,因为无论政策如何,全局查询始终是最终一致的。)

可以通过查询对象的读取选项来实现最终一致的读取:

C#

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore C# API 参考文档

Query query = new Query("Task")
    {
        Filter = Filter.HasAncestor(_db.CreateKeyFactory("TaskList")
            .CreateKey(keyName))
    };
    var results = _db.RunQuery(query,
        ReadOptions.Types.ReadConsistency.Eventual);

Go

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Go API 参考文档

ancestor := datastore.NameKey("TaskList", "default", nil)
    query := datastore.NewQuery("Task").Ancestor(ancestor).EventualConsistency()

Java

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Java API 参考文档

Query<Entity> query = Query.newEntityQueryBuilder()
        .setKind("Task")
        .setFilter(PropertyFilter.hasAncestor(
            datastore.newKeyFactory().setKind("TaskList").newKey("default")))
        .build();
    datastore.run(query, ReadOption.eventualConsistency());

Node.js

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Node.js API 参考文档

const ancestorKey = datastore.key(['TaskList', 'default']);
    const query = datastore.createQuery('Task').hasAncestor(ancestorKey);

    query.run({consistency: 'eventual'});

PHP

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore PHP API 参考文档

$query = $datastore->query()
        ->kind('Task')
        ->hasAncestor($datastore->key('TaskList', 'default'));
    $result = $datastore->runQuery($query, ['readConsistency' => 'EVENTUAL']);

Python

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Python API 参考文档

# Read consistency cannot be specified in google-cloud-python.

Ruby

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Ruby API 参考文档

ancestor_key = datastore.key "TaskList", "default"

    query = datastore.query("Task")
                     .ancestor(ancestor_key)

    tasks = datastore.run query, consistency: :eventual

事务和一致性注意事项

Datastore 提交操作可以事务方式或非事务方式执行:事务方式意味着提交发生在某一事务的环境中,并且该事务的一组变更操作全部都应用或一个都不应用;非事务方式则意味着这组变更操作可能不会全部都应用,也不会一个都不应用。

单个事务可以包含任意数量的创建、更新或删除变更。为了保持数据的一致性,事务会确保其所包含的所有变更都作为一个整体应用于 Datastore,或者只要有任何变更失败,则一个变更都不应用。此外,在同一个事务中执行的所有高度一致读取(祖先查询或 lookup 操作)都依赖于单个一致的数据快照。高度一致性查询必须指定祖先过滤条件。参与事务的查询始终是高度一致的。事务最多可以涉及 25 个实体组。最终一致性读取没有这些限制,并且在许多情况下足以满足需要。使用最终一致性读取,您可以将数据分配在大量实体组之间,从而通过对不同实体组并行执行提交来获得更高的写入吞吐量。但是,您需要了解最终一致性读取的特征以确定其是否适合您的应用:

  • 这些读取的结果可能不会反映最新的事务。这种情况之所以会发生是因为这些读取并不保证它们运行时所在的副本是最新的。相反,它们在执行查询时会使用该副本上任何可用的数据。
  • 跨多个实体组的提交事务可能看上去已应用于这些实体中的一部分,并且未应用于其他实体。但请注意,事务始终不会看上去在单个实体内部分应用。
  • 查询结果可能包括根据过滤条件不应包括的实体,且可能排除应包括的实体。这可能是因为用于读取索引的快照版本与用于读取实体的快照版本不同。

设计数据结构以确保一致性

要了解如何设计数据结构以确保高度一致性,请对用于一个简单任务列表应用的两种不同方法进行比较。第一种方法是在其自身的新实体组中创建每个实体(即,每个实体都是根实体):

C#

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore C# API 参考文档

Entity task = new Entity()
    {
        Key = _db.CreateKeyFactory("Task").CreateKey("sampleTask"),
        ["category"] = "Personal",
        ["done"] = false,
        ["priority"] = 4,
        ["description"] = "Learn Cloud Datastore"
    };

Go

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Go API 参考文档

type Task struct {
    	Category        string
    	Done            bool
    	Priority        float64
    	Description     string `datastore:",noindex"`
    	PercentComplete float64
    	Created         time.Time
    }
    task := &Task{
    	Category:        "Personal",
    	Done:            false,
    	Priority:        4,
    	Description:     "Learn Cloud Datastore",
    	PercentComplete: 10.0,
    	Created:         time.Now(),
    }

Java

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Java API 参考文档

Key taskKey = datastore.newKeyFactory()
        .setKind("Task")
        .newKey("sampleTask");
    Entity task = Entity.newBuilder(taskKey)
        .set("category", "Personal")
        .set("done", false)
        .set("priority", 4)
        .set("description", "Learn Cloud Datastore")
        .build();

Node.js

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Node.js API 参考文档

const task = {
      category: 'Personal',
      done: false,
      priority: 4,
      description: 'Learn Cloud Datastore',
    };

PHP

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore PHP API 参考文档

$task = $datastore->entity('Task', [
        'category' => 'Personal',
        'done' => false,
        'priority' => 4,
        'description' => 'Learn Cloud Datastore'
    ]);

Python

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Python API 参考文档

task = datastore.Entity(client.key('Task'))
    task.update({
        'category': 'Personal',
        'done': False,
        'priority': 4,
        'description': 'Learn Cloud Datastore'
    })

Ruby

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Ruby API 参考文档

task = datastore.entity "Task" do |t|
      t["category"] = "Personal"
      t["done"] = false
      t["priority"] = 4
      t["description"] = "Learn Cloud Datastore"
    end

然后,该方法会对实体种类 Task 查询优先级大于或等于 4 且尚未完成的任务,并按优先级的降序顺序排列查询结果:

C#

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore C# API 参考文档

Query query = new Query("Task")
    {
        Filter = Filter.And(Filter.Equal("done", false),
            Filter.GreaterThanOrEqual("priority", 4)),
        Order = { { "priority", PropertyOrder.Types.Direction.Descending } }
    };

Go

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Go API 参考文档

query := datastore.NewQuery("Task").
    	Filter("Done =", false).
    	Filter("Priority >=", 4).
    	Order("-Priority")

Java

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Java API 参考文档

Query<Entity> query = Query.newEntityQueryBuilder()
        .setKind("Task")
        .setFilter(CompositeFilter.and(
            PropertyFilter.eq("done", false), PropertyFilter.ge("priority", 4)))
        .setOrderBy(OrderBy.desc("priority"))
        .build();

Node.js

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Node.js API 参考文档

const query = datastore
      .createQuery('Task')
      .filter('done', '=', false)
      .filter('priority', '>=', 4)
      .order('priority', {
        descending: true,
      });

PHP

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore PHP API 参考文档

$query = $datastore->query()
        ->kind('Task')
        ->filter('done', '=', false)
        ->filter('priority', '>=', 4)
        ->order('priority', Query::ORDER_DESCENDING);

Python

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Python API 参考文档

query = client.query(kind='Task')
    query.add_filter('done', '=', False)
    query.add_filter('priority', '>=', 4)
    query.order = ['-priority']

Ruby

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Ruby API 参考文档

query = datastore.query("Task")
                     .where("done", "=", false)
                     .where("priority", ">=", 4)
                     .order("priority", :desc)

但是,因为我们使用的是最终一致性查询(而非祖先查询),所以查询结果可能不包含新的实体。尽管如此,几乎所有的写入操作都可在提交不久之后用于最终一致性查询。对于许多应用来说,在当前用户自己更改的情况下,提供最终一致性查询结果的解决方案通常足以使此类延迟在完全可接受的程度之内。

为了实现高度一致性,一个更好的方法是创建具有祖先路径的实体。祖先路径标识创建的实体划分为的公共根实体。此示例使用名为 default 的种类 TaskList 的祖先路径:

C#

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore C# API 参考文档

Key taskListKey = _db.CreateKeyFactory("TaskList").CreateKey(TestUtil.RandomName());
    Key taskKey = new KeyFactory(taskListKey, "Task").CreateKey("sampleTask");
    Entity task = new Entity()
    {
        Key = taskKey,
        ["category"] = "Personal",
        ["done"] = false,
        ["priority"] = 4,
        ["description"] = "Learn Cloud Datastore"
    };

Go

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Go API 参考文档

parentKey := datastore.NameKey("TaskList", "default", nil)
    key := datastore.IncompleteKey("Task", parentKey)

    task := Task{
    	Category:    "Personal",
    	Done:        false,
    	Priority:    4,
    	Description: "Learn Cloud Datastore",
    }

    // A complete key is assigned to the entity when it is Put.
    var err error
    key, err = client.Put(ctx, key, &task)

Java

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Java API 参考文档

Key taskKey = datastore.newKeyFactory()
        .addAncestors(PathElement.of("TaskList", "default"))
        .setKind("Task")
        .newKey("sampleTask");
    Entity task = Entity.newBuilder(taskKey)
        .set("category", "Personal")
        .set("done", false)
        .set("priority", 4)
        .set("description", "Learn Cloud Datastore")
        .build();

Node.js

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Node.js API 参考文档

const taskKey = datastore.key([
      'TaskList',
      'default',
      'Task',
      'sampleTask',
    ]);

    const task = {
      key: taskKey,
      data: {
        category: 'Personal',
        done: false,
        priority: 4,
        description: 'Learn Cloud Datastore',
      },
    };

PHP

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore PHP API 参考文档

$parentKey = $datastore->key('TaskList', 'default');
    $key = $datastore->key('Task')->ancestorKey($parentKey);
    $task = $datastore->entity(
        $key,
        [
            'Category' => 'Personal',
            'Done' => false,
            'Priority' => 4,
            'Description' => 'Learn Cloud Datastore'
        ]
    );

Python

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Python API 参考文档

key_with_parent = client.key(
        'TaskList', 'default', 'Task', 'sample_task')

    task = datastore.Entity(key=key_with_parent)

    task.update({
        'category': 'Personal',
        'done': False,
        'priority': 4,
        'description': 'Learn Cloud Datastore'
    })

Ruby

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Ruby API 参考文档

task_key = datastore.key [["TaskList", "default"], ["Task", "sampleTask"]]

    task = datastore.entity task_key do |t|
      t["category"] = "Personal"
      t["done"] = false
      t["priority"] = 4
      t["description"] = "Learn Cloud Datastore"
    end

然后,您将能够在由该公共根实体标识的实体组内执行具备高度一致性的祖先查询:

C#

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore C# API 参考文档

Query query = new Query("Task")
    {
        Filter = Filter.HasAncestor(_db.CreateKeyFactory("TaskList")
            .CreateKey(keyName))
    };

Go

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Go API 参考文档

ancestor := datastore.NameKey("TaskList", "default", nil)
    query := datastore.NewQuery("Task").Ancestor(ancestor)

Java

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Java API 参考文档

Query<Entity> query = Query.newEntityQueryBuilder()
        .setKind("Task")
        .setFilter(PropertyFilter.hasAncestor(
            datastore.newKeyFactory().setKind("TaskList").newKey("default")))
        .build();

Node.js

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Node.js API 参考文档

const ancestorKey = datastore.key(['TaskList', 'default']);

    const query = datastore.createQuery('Task').hasAncestor(ancestorKey);

PHP

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore PHP API 参考文档

$ancestorKey = $datastore->key('TaskList', 'default');
    $query = $datastore->query()
        ->kind('Task')
        ->hasAncestor($ancestorKey);

Python

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Python API 参考文档

# Query filters are omitted in this example as any ancestor queries with a
    # non-key filter require a composite index.
    ancestor = client.key('TaskList', 'default')
    query = client.query(kind='Task', ancestor=ancestor)

Ruby

如需了解如何安装和使用 Cloud Datastore 客户端库,请参阅 Cloud Datastore 客户端库。如需了解详情,请参阅 Cloud Datastore Ruby API 参考文档

ancestor_key = datastore.key "TaskList", "default"

    query = datastore.query("Task")
                     .ancestor(ancestor_key)

此方法通过针对每个任务列表写入单个实体组来实现高度一致性,但也将对任务列表的更改限制为每秒不超过 1 次写入(对实体组支持的限制)。如果应用有可能遇到更频繁的写入使用情况,则可能需要考虑使用其他方式。例如,如果应用是允许用户将消息发布到公共消息板的留言板,那么您可以将最近的帖子放入具有过期时间的 memcache 中,并混合显示来自 memcache 和 Datastore 的最近帖子,或者可以在 cookie 中缓存它们、在 URL 中放入某种状态,或进行其他完全不同的操作。目标是找到一个缓存解决方案,在当前用户向应用发布消息的时间段内为该用户提供数据。请记住,如果在事务内执行 lookup、祖先查询(假设读取政策未设为最终一致)或任何操作,您会始终看到最近写入的数据。

如需了解如何使用事务的其他示例,请转到此处

事务的实体组限制

将数据整理成实体组可以限制可执行的事务:

  • 一个事务所访问的所有数据都必须包含在最多 25 个实体组中。
  • 如果您想在事务内使用查询,必须将数据整理成实体组,这样才可以指定与正确数据匹配的祖先过滤器。
  • 单个实体组的写入吞吐量限制约为每秒一个事务。存在此限制是因为 Datastore 会在大范围的地理区域内,对每个实体组执行无主同步复制,以提供高可靠性和容错性。

如需详细了解如何更新实体和索引,请参阅事务隔离一文。