什么是复制?

将数据从一个数据库复制到另一个数据库可以在同一 IBM® Cloudant® for IBM Cloud® 帐户内执行,也可以跨帐户和跨数据中心执行。

IBM Cloudant 甚至可以通过 使用 PouchDB。 复制可以是单向的,也可以是双向的, 可以是“单次”操作,也可以是连续操作, 并且可以通过参数进行精细调整。

IBM Cloudant 的复制协议与一系列其他数据库和库相兼容,因此非常适合物联网 (IoT) 和移动应用程序。

IBM Cloudant 是一个具有 HTTP API 的分布式 JSON 数据存储。 IBM Cloudant 可以在多种云上或在服务器机架中作为服务运行。 文档存储在数据库中,随着 IBM Cloudant 跨多个节点对数据分片,文档可增大到任意大小。 复制是将数据从源数据库复制到目标数据库的过程。 源数据库和目标数据库不需要位于同一 IBM Cloudant 帐户上,甚至不需要位于同一数据中心。

此图显示数据库 a 和 b 的图片。 数据库 b 有一个文档。 复制完成后,数据库 a 中的文档会显示在数据库 b 中。
图解复制过程

源数据库中每个文档的最新版本传输到目标数据库后,复制即完成。 传输包含新文档、对现有文档的更新以及删除项。 复制后仅保留文档的最新版本; 将省略旧版本。

复制不会改变源数据库,只是会向其写入检查点数据,以允许部分复制从最后一个已知位置恢复。 目标数据库中预先存在的所有数据都会保留。

如何开始使用仪表板进行复制

IBM Cloudant“仪表板”提供了一个用户界面,可方便地触发复制。 在 IBM Cloudant 仪表板上单击 Replication,然后单击 Start Replication。 完成以下复制表单:

此截屏显示填写了所有正确字段的复制表单。 有一个源部分是本地数据库,还有一个目标部分是新数据库和认证。 在“选项”部分,您可以选择“一次性复制”或“重复复制”,并添加一个复制文档。
复制表单

出于安全考虑,IBM Cloudant 团队建议您在复制任务中使用 IAM API 密钥或 IBM Cloudant 旧版身份验证 API 密钥, 而不是账户级凭据。 有关更多信息,请参阅 管理访问权 或旧的 认证授权 文档。

使用该表单定义源数据库和目标数据库,然后单击Start Replication

点击 Replication 即可查看每个复制任务的状态。 随着每个作业的进展,其状态将从 Running更改为Completed。 下图显示了 Completed 的状态。

在表格的“状态”字段下,您会看到“已完成”。
已完成状态

如何跨不同 IBM Cloudant 帐户进行复制

复制的源和目标是 IBM Cloudant 数据库的 URL,如以下示例所示。

请参阅定义用于复制的源 URL 和目标 URL 的以下示例:

{
  "source": {
    "url": "https://myfirstaccount.cloudant.com/a",
    "auth": {
      "basic": {
        "username": "$USERNAME",
        "password": "$PASSWORD"
      }
    }
  },
  "target": {
    "url": "https://mysecondaccount.cloudant.com/b",
    "auth": {
      "basic": {
        "username": "$USERNAME",
        "password": "$PASSWORD"
      }
    }
  }
}

源和目标不必位于同一帐户上。 源和目标数据库名称也无需匹配。 您必须有权访问源和目标,并且必须有权写入目标。

复制是在源还是在目标上运行?

复制可以在源端启动,也可以在目标端启动。 此选项意味着您可以决定是帐户 A 将数据推送到帐户 B,还是帐户 B 从帐户 A 拉取数据。 在某些情况下,可能无法通过上述任一配置来运行复制,例如,一个帐户位于防火墙后时。 复制是通过 HTTPS 进行的,因此无需打开任何非标准端口。 哪个设备启动复制将由您决定。

复制如何影响更改列表?

可以使用 _changes 端点来获取对文档所做更改的列表。 但是,IBM Cloudant 数据库的分布式性质意味着 _changes 订阅源提供的响应不可能是在特定日期和时间之后所发生更改的简单列表。

CAP 定理 讨论明确指出, IBM Cloudant 采用“最终一致性”模型。 此模型表示当您同时询问文档的数据库的两个不同副本时,可能会获得不同的结果。 当其中一个数据库副本仍在等待完成复制时,可能会发生此情况。

最终,数据库副本会完成其复制,以便对文档的所有更改都反映在每个副本中。

这种“最终一致性”模型有两个特征,会影响变更列表:

  1. 影响文档的更改几乎肯定会在不同时间在数据库的不同副本中执行。
  2. 这些更改对文档发生影响的顺序在数据库的不同副本之间可能不同,具体取决于执行复制的时间和位置。

第一个特征的一个结果是,请求更改列表时,请求特定时间点之后的更改列表没有意义。 原因是更改列表可能由不同的数据库副本提供,从而产生不同时间的文档更新。 但是,请求特定更改(通过使用序列标识来指定)之后的更改列表 有意义的。

第一个特征带来的另一个后果是, 为了就变更列表达成一致,可能需要“回顾”之前的变更。 换言之,要获取更改列表,您将从数据库副本达成一致的最新更改开始。 数据库副本之间的协议点在 IBM Cloudant 通过使用 检查点 机制,使数据库副本之间的复制能够同步。

最后,当您查看更改列表时,这些更改可能会在后续请求中以不同的顺序显示。 顺序取决于文档在不同数据库副本之间的更改方式。 换言之,初始更改列表可能按 ABC 的顺序报告更改。 但后续更改列表可能按 CAB 的顺序报告更改。 所有更改都会列出,但顺序不同。 造成这种差异的原因是,在复制期间数据库两个不同副本收到更改的顺序可能不同。

对于变更列表而言,“最终一致性”是什么意思?

请求更改列表时,获得的响应可能会有所不同,具体取决于是哪个数据库副本提供该列表。

since 选项获取特定更新序列标识后的更改列表。 列表始终包含更新后的更改,但也可能包含更新前的更改。 原因是对列表请求作出响应的数据库副本必须确保自己列出与所有副本一致的更改。 为了实现这种一致性,数据库副本可能必须使更改列表从所有副本达成一致的点开始。 此点通过使用检查点进行确定。

因此, 使用 _changes 数据源的应用程序必须 具有“幂等性”。 幂等性意味着应用程序必须能够安全地多次接收相同的数据, 并且在重复请求时,这些数据可能以不同的顺序出现。

检查点

在内部,复制过程会将其状态写入存储在源数据库和目标数据库中的“检查点”文档中。 检查点允许从其停止的位置恢复复制任务,而不必从头开始。 可以通过提供 请求复制时的 "use_checkpoints": false 选项。 如果复制要从最后一个已知位置高效地恢复,那么使此功能保持开启非常有用。

许可权

要将文档插入到 _replicator 数据库中,需要管理访问权。 在 source 和 target 参数中提供的登录凭证不需要完全管理许可权。 如果凭证执行以下任务就足够了:

  • 在目标端写入文档。
  • 在两端写入检查点文档。

IBM Cloudant 具有特殊的 _replicator 用户许可权。 此许可权允许创建检查点文档,但不允许在数据库中创建普通文档。 通常, 创建符合以下条件的 API 密钥

  • 具有对源端的 _reader_replicator 访问权。
  • 具有对目标端的 _reader_writer 访问权。

可以在 IBM Cloudant“仪表板”中逐个数据库创建和配置 API 密钥。

可在 IBM Cloudant Dashboard 中按数据库创建和配置 API 密钥。
IBM Cloudant 具有权限的用户和 API 密钥

还可以通过调用 IBM Cloudant API 以编程方式 创建它们。

出于安全考虑,IBM Cloudant 团队建议您在复制任务中使用 IAM API 密钥或 IBM Cloudant 旧版身份验证 API 密钥, 而不是账户级凭据。 有关更多信息,请参阅 管理访问权 或旧的 认证授权 文档。

双向复制

可以在一个过程中双向复制数据,此过程称为双向复制或同步。 您可通过设置两个单独的复制过程来启用此同步:一个过程将数据从 A 复制到 B,另一个过程将数据从 B 复制到 A。 这两个复制过程独立运行,数据在两个方向无缝移动。

此图显示数据库 a 和 b。 数据库 a 有四个文档,一个文档被划掉。 数据库 b 有一个文档。 将数据库 a 复制到数据库 b 后,数据库 b 有五个文档,一个文档被划掉。 将数据库 b 复制到数据库 a 之后,数据库 a 中也有了五份文档,其中一份被划掉了。
双向复制

关于持续复制的讨论

到目前为止,讨论仅涉及的是单次复制,它在所有源数据都写入目标数据库之后即完成。 通过持续复制,数据可持续流动。 对源数据库的所有后续更改都将实时传输到目标数据库。

在“IBM Cloudant”仪表板中定义复制任务时,勾选“Make this replication continuous”复选框, 或在 API 中设置 continuousIBM Cloudant 标志来触发。

通过设置 continuous 标志,可使双向复制在一个方向或两个方向持续进行。

请参阅下面使用 HTTP 启动连续复制的示例:

POST /_replicator HTTP/1.1
Content-Type: application/json
Host: $SERVICE_URL
Authorization: ...

请参阅以下示例以启动连续复制:

curl -X POST \
    -H "Content-type: application/json" \
    "$SERVICE_URL/_replicator" \
    -d '{ "_id": "repldoc-example",
          "continuous": true,
          "create_target": true,
          "source": { "url": "'"$SOURCE_SERVICE_URL/source"'" },
          "target": {
            "auth": { "iam": { "api_key": "'"$API_KEY"'" } },
            "url": "'"$TARGET_SERVICE_URL/target"'"
          }
        }'
import com.ibm.cloud.cloudant.v1.Cloudant;
import com.ibm.cloud.cloudant.v1.model.DocumentResult;
import com.ibm.cloud.cloudant.v1.model.PutReplicationDocumentOptions;
import com.ibm.cloud.cloudant.v1.model.ReplicationDatabase;
import com.ibm.cloud.cloudant.v1.model.ReplicationDatabaseAuth;
import com.ibm.cloud.cloudant.v1.model.ReplicationDatabaseAuthIam;
import com.ibm.cloud.cloudant.v1.model.ReplicationDocument;
Cloudant service = Cloudant.newInstance();
ReplicationDatabase sourceDb = new ReplicationDatabase.Builder()
    .url("<your-source-service-url>/source")
    .build();
ReplicationDatabaseAuthIam targetAuthIam =
    new ReplicationDatabaseAuthIam.Builder()
        .apiKey("<your-iam-api-key>")
        .build();
ReplicationDatabaseAuth targetAuth = new ReplicationDatabaseAuth.Builder()
    .iam(targetAuthIam)
    .build();
ReplicationDatabase targetDb = new ReplicationDatabase.Builder()
    .auth(targetAuth)
    .url("<your-target-service-url>/target")
    .build();
ReplicationDocument replDocument = new ReplicationDocument();
replDocument.setSource(sourceDb);
replDocument.setTarget(targetDb);
replDocument.setContinuous(true);
PutReplicationDocumentOptions replicationDocumentOptions =
    new PutReplicationDocumentOptions.Builder()
        .docId("repldoc-example")
        .replicationDocument(replDocument)
        .build();
DocumentResult response =
    service.putReplicationDocument(replicationDocumentOptions).execute()
        .getResult();
System.out.println(response);
const { CloudantV1 } = require('@ibm-cloud/cloudant');
const service = CloudantV1.newInstance({});
const sourceDb: CloudantV1.ReplicationDatabase = {
  url: '<your-source-service-url>/source'
};
const targetDb: CloudantV1.ReplicationDatabase = {
  auth: {
    iam: {
      'api_key': '<your-iam-api-key>'
    }
  },
  url: '<your-target-service-url>/target'
};
const replDocument: CloudantV1.ReplicationDocument = {
  id: 'repldoc-example',
  continuous: true,
  create_target: true,
  source: sourceDb,
  target: targetDb
}
service.putReplicationDocument({
  docId: 'repldoc-example',
  replicationDocument: replDocument
}).then(response => {
  console.log(response.result);
});
from ibmcloudant.cloudant_v1 import CloudantV1, ReplicationDocument, ReplicationDatabase, ReplicationDatabaseAuthIam, ReplicationDatabaseAuth
service = CloudantV1.new_instance()
source_db = ReplicationDatabase(
  url='<your-source-service-url>/source'
)
target_auth_iam = ReplicationDatabaseAuthIam(
  api_key='<your-iam-api-key>'
)
target_auth = ReplicationDatabaseAuth(
  iam=target_auth_iam
)
target_db = ReplicationDatabase(
  auth=target_auth,
  url='<your-target-service-url>/target'
)
replication_document = ReplicationDocument(
  id='repldoc-example',
  continuous=True,
  create_target=True,
  source=source_db,
  target=target_db
)
response = service.put_replication_document(
  doc_id='repldoc-example',
  replication_document=replication_document
).get_result()
print(response)
source, err := service.NewReplicationDatabase(
  "<your-source-service-url>/source",
)
if err != nil {
  panic(err)
}
target, err := service.NewReplicationDatabase(
  "<your-target-service-url>/target",
)
if err != nil {
  panic(err)
}
auth, err := service.NewReplicationDatabaseAuthIam(
  "<your-iam-api-key>",
)
if err != nil {
  panic(err)
}
target.Auth = &cloudantv1.ReplicationDatabaseAuth{Iam: auth}
replicationDoc, err := service.NewReplicationDocument(
  source,
  target,
)
if err != nil {
  panic(err)
}
replicationDoc.Continuous = core.BoolPtr(true)
replicationDoc.CreateTarget = core.BoolPtr(true)
putReplicationDocumentOptions := service.NewPutReplicationDocumentOptions(
  "repldoc-example",
  replicationDoc,
)
documentResult, response, err := service.PutReplicationDocument(putReplicationDocumentOptions)
if err != nil {
  panic(err)
}
b, _ := json.MarshalIndent(documentResult, "", "  ")
fmt.Println(string(b))

先前的 Go 示例需要以下导入块:

import (
   "encoding/json"
   "fmt"
   "github.com/IBM/cloudant-go-sdk/cloudantv1"
)

所有 Go 示例都需要初始化 service 对象。 有关更多信息,请参阅 API 文档的 认证部分 以获取示例。

请参阅以下定义连续复制的 JSON 文档示例:

{
    "_id": "weekly_continuous_backup",
    "source": {
      "url": "https://$SOURCE_SERVICE_DOMAIN/source",
      "auth": {
        "basic": {
          "username": "$USERNAME",
          "password": "$PASSWORD"
        }
      }
    },
    "target": {
      "url": "https://$TARGET_SERVICE_DOMAIN/target",
      "auth": {
        "basic": {
          "username": "$USERNAME",
          "password": "$PASSWORD"
        }
      }
    },
    "continuous": true
}

其他复制用例

IBM Cloudant 的复制协议与用于各种现实世界应用的其他数据库和库相兼容。

Apache CouchDB

Apache CouchDB 这是一个开源数据库, 可以与 IBM Cloudant 进行通信, 且只需极少的配置。 包含以下应用程序:

  • 备份——将 IBM Cloudant 中的数据复制到您自己的 CouchDB 数据库中 并每晚对您的数据进行快照备份,以供归档之用。 将数据发送至备份服务(例如 Amazon Glacier )进行安全存储。
  • 优先本地数据收集——先将数据写入本地 Apache CouchDB, 然后将其复制到 IBM Cloudant 进行长期存储, 聚合, 和分析。

PouchDB

PouchDB 这是一个开源的、 基于浏览器的数据库,支持在浏览器与 IBM Cloudant 之间双向复制数据。 通过在客户端的 Web 浏览器中存储数据,Web 应用程序可以在没有因特网连接的情况下运行。 PouchDB 在有互联网连接时,可将任何已更改的数据与 IBM Cloudant 进行双向同步。 从客户端设置复制需要几行 JavaScript 代码。

请参阅以下使用 PouchDB 来启用复制的示例 JavaScript:

var db = new PouchDB("myfirstdatabase");
var URL = "https://$USERNAME:$PASSWORD@$SERVICE_DOMAIN/my_database");
db.sync(URL, { live: true });

过滤的复制

在复制过程中,当您将一个数据库复制到另一个数据库时,能够除去某些数据是很有用的,如以下示例中所示:

  • 除去对已删除文档的所有跟踪,使目标数据库小于源数据库。
  • 将数据分成更小的部分,例如将英国的数据存储在一个数据库中,将美国的数据存储在另一个数据库中。

复制过滤函数

IBM Cloudant 的过滤复制支持定义 JavaScript 函数,该函数使用返回值来确定是否过滤数据库中的每个文档。 过滤函数存储在设计文档中。

请参阅以下示例过滤器函数以复制未删除的文档:

function(doc, req) {
    if (doc._deleted) {
        return false;
    }
    return true;
}

复制作业启动时,会将过滤函数的名称指定为其存储所在的设计文档与该过滤函数名称的组合。 还可以指定 query_params 值。 此值是一个对象,其中包含在其第二个 (query) 自变量的 req 字段中传递给过滤函数的属性。

请参阅下面使用 HTTP 启动过滤复制的示例:

POST /_replicator HTTP/1.1
Content-Type: application/json
Host: $SERVICE_URL
Authorization: ...

请参阅以下使用命令行来启动已过滤的复制的示例:

curl -X POST \
    -H "Content-type: application/json" \
    "$SERVICE_URL/_replicator" \
    -d @filtered-replication.json
import com.ibm.cloud.cloudant.v1.Cloudant;
import com.ibm.cloud.cloudant.v1.model.DocumentResult;
import com.ibm.cloud.cloudant.v1.model.PutReplicationDocumentOptions;
import com.ibm.cloud.cloudant.v1.model.ReplicationDatabase;
import com.ibm.cloud.cloudant.v1.model.ReplicationDatabaseAuth;
import com.ibm.cloud.cloudant.v1.model.ReplicationDatabaseAuthIam;
import com.ibm.cloud.cloudant.v1.model.ReplicationDocument;
Cloudant service = Cloudant.newInstance();
ReplicationDatabase sourceDb = new ReplicationDatabase.Builder()
    .url("<your-source-service-url>/source")
    .build();
ReplicationDatabaseAuthIam targetAuthIam =
    new ReplicationDatabaseAuthIam.Builder()
        .apiKey("<your-iam-api-key>")
        .build();
ReplicationDatabaseAuth targetAuth = new ReplicationDatabaseAuth.Builder()
    .iam(targetAuthIam)
    .build();
ReplicationDatabase targetDb = new ReplicationDatabase.Builder()
    .auth(targetAuth)
    .url("<your-target-service-url>/target"))
    .build();
ReplicationDocument replDocument = new ReplicationDocument();
replDocument.setSource(sourceDb);
replDocument.setTarget(targetDb);
replDocument.setFilter("mydesigndoc/myfilter");
Map queryParams = new HashMap<>();
queryParams.put("foo", "bar");
queryParams.put("baz", 5);
replDocument.setQueryParams(queryParams);
PutReplicationDocumentOptions replicationDocumentOptions =
    new PutReplicationDocumentOptions.Builder()
        .docId("repldoc-example")
        .replicationDocument(replDocument)
        .build();
DocumentResult response =
    service.putReplicationDocument(replicationDocumentOptions).execute()
        .getResult();
System.out.println(response);
const { CloudantV1 } = require('@ibm-cloud/cloudant');
const service = CloudantV1.newInstance({});
const sourceDb: CloudantV1.ReplicationDatabase = {
  url: '<your-source-service-url>/source'
};
const targetDb: CloudantV1.ReplicationDatabase = {
  auth: {
    iam: {
      'api_key': '<your-iam-api-key>'
    }
  },
  url: '<your-target-service-url>/target'
};
const replDocument: CloudantV1.ReplicationDocument = {
  id: 'repldoc-example',
  filter: 'mydesigndoc/myfilter',
  query_params: {'foo': 'bar', 'baz': 5},
  source: sourceDb,
  target: targetDb
}
service.putReplicationDocument({
  docId: 'repldoc-example',
  replicationDocument: replDocument
}).then(response => {
  console.log(response.result);
});
from ibmcloudant.cloudant_v1 import CloudantV1, ReplicationDocument, ReplicationDatabase, ReplicationDatabaseAuthIam, ReplicationDatabaseAuth
service = CloudantV1.new_instance()
source_db = ReplicationDatabase(
  url='<your-source-service-url>/source'
)
target_auth_iam = ReplicationDatabaseAuthIam(
  api_key='<your-iam-api-key>'
)
target_auth = ReplicationDatabaseAuth(
  iam=target_auth_iam
)
target_db = ReplicationDatabase(
  auth=target_auth,
  url='<your-target-service-url>/target'
)
replication_document = ReplicationDocument(
  id='repldoc-example',
  filter='mydesigndoc/myfilter',
  query_params={'foo': 'bar', 'baz': 5},
  source=source_db,
  target=target_db
)
response = service.put_replication_document(
  doc_id='repldoc-example',
  replication_document=replication_document
).get_result()
print(response)
source, err := service.NewReplicationDatabase(
  "<your-source-service-url>/source",
)
if err != nil {
  panic(err)
}
target, err := service.NewReplicationDatabase(
  "<your-target-service-url>/target",
)
if err != nil {
  panic(err)
}
auth, err := service.NewReplicationDatabaseAuthIam(
  "<your-iam-api-key>",
)
if err != nil {
  panic(err)
}
target.Auth = &cloudantv1.ReplicationDatabaseAuth{Iam: auth}
replicationDoc, err := service.NewReplicationDocument(
  source,
  target,
)
if err != nil {
  panic(err)
}
replicationDoc.Filter := "mydesigndoc/myfilter"
replicationDoc.QueryParams := map[string]interface{}{"foo": "bar", "baz": 5}
putReplicationDocumentOptions := service.NewPutReplicationDocumentOptions(
  "repldoc-example",
  replicationDoc,
)
documentResult, response, err := service.PutReplicationDocument(putReplicationDocumentOptions)
if err != nil {
  panic(err)
}
b, _ := json.MarshalIndent(documentResult, "", "  ")
fmt.Println(string(b))

先前的 Go 示例需要以下导入块:

import (
   "encoding/json"
   "fmt"
   "github.com/IBM/cloudant-go-sdk/cloudantv1"
   "github.com/IBM/go-sdk-core/core"
)

所有 Go 示例都需要初始化 service 对象。 有关更多信息,请参阅 API 文档的 认证部分 以获取示例。

请参阅以下定义了过滤复制功能的 JSON 文档示例:

{
    "_id": "weekly_backup",
    "source": {
      "url": "https://$SOURCE_SERVICE_DOMAIN/source",
      "auth": {
        "basic": {
          "username": "$USERNAME",
          "password": "$PASSWORD"
        }
      }
    },
    "target": {
      "url": "https://$TARGET_SERVICE_DOMAIN/target",
      "auth": {
        "basic": {
          "username": "$USERNAME",
          "password": "$PASSWORD"
        }
      }
    },
    "filter": "mydesigndoc/myfilter",
    "query_params": {
        "foo": "bar",
        "baz": 5
    }
}

更改订阅源

IBM Cloudant 通过单个 HTTP 订阅源从 _changes 端点发布影响数据库的添加、编辑和删除操作。 应用程序可以使用此订阅源来触发事件。 您可以使用 HTTP 或 curl(如示例中所示)来访问订阅源。 使用 feed=continuous 选项意味着流会提供获取数据库中每个文档最新版本所需的每个更改。

有关更多信息,请参阅 使用 IBM Cloudant 更改订阅源常见问题解答

请参阅下面使用 HTTP 查询更改源的示例:

GET /$DATABASE/_changes?feed=continuous HTTP/1.1
Host: $SERVICE_URL
Authorization: ...

请参阅以下使用命令行来查询更改订阅源的示例:

curl "$SERVICE_URL/$DATABASE/_changes?feed=continuous"

描述更改时,每个更改一行。 每个更改由以下部分组成:

  1. 包含序号 (seq) 的字符串。
  2. 包含已更改文档的标识的字符串。
  3. 更改数组。

要查看文档主体本身,请将 &include_docs=true 附加到 curl 命令。

使用以下示例(缩略版)中显示的格式来描述每个更改。

请参阅以下示例 _changes 订阅源:

{
    "seq":"11-g1A...c1Q",
    "id":"6f8ab9fa52c117eb76240daa1a55827f",
    "changes":[
        {
          "rev":"1-619d7981d7027274a4b88810d318a7b1"
        }
    ]
}

要从已知位置连接 changes 订阅源,请传递 since 自变量以及要从其开始的序号。

请参阅以下示例(节选),该示例使用 HTTP 提供 since 选项,以便从已知位置加入 _changes 源:

GET /$DATABASE/_changes?feed=continuous&include_docs=true&since=11-g1A...c1Q HTTP/1.1
HOST: $SERVICE_URL
Authorization: ...

请参阅以下示例(节选),该示例通过命令行提供 since 选项,以从已知位置加入 _changes 源:

curl "$SERVICE_URL/$DATABASE/_changes?feed=continuous&include_docs=true&since=11-g1A...c1Q"

要在此刻重新连接 changes 订阅源,请设置 since=now

请参阅以下示例,该示例使用 HTTP 将 since=now 作为参数,以在当前时刻订阅 _changes 的动态:

GET /$DATABASE/_changes?feed=continuous&include_docs=true&since=now HTTP/1.1
Host: $SERVICE_URL
Authorization: ...

请参阅以下示例,该示例通过命令行提供 since=now,以在当前时刻加入 _changes 的订阅源:

curl "$SERVICE_URL/$DATABASE/_changes?feed=continuous&include_docs=true&since=now"

通过编程方式访问 _changes 数据十分简单。 例如,请参阅 IBM Cloudant API 文档 中的 SDK 示例,以遵循带有几行代码的更改。

以下列表包含一些示例用例:

  • 将项添加到消息队列以触发应用程序中的操作,例如发送客户电子邮件。
  • 更新内存中的数据库以记录活动的实时计数。
  • 将数据写入文本文件以将数据推送到 SQL 数据库。

changes 订阅源可以通过类似于在复制期间过滤的方法,使用过滤函数进行过滤。

请参阅下面使用 HTTP 过滤更改源的示例:

GET /$DATABASE/_changes?feed=continuous&include_docs=true&since=now&filter=mydesigndoc/myfilter HTTP/1.1
Host: $SERVICE_URL
Authorization: ...

请参阅以下使用命令行来过滤更改订阅源的示例:

curl "$SERVICE_URL/$DATABASE/_changes?feed=continuous&include_docs=true&since=now&filter=mydesigndoc/myfilter"

_changes 订阅源中文档的顺序并不总是相同。 换言之,更改可能不会按严格的时间顺序显示。 原因是数据是从多个 IBM Cloudant 节点返回的,并且最终一致性规则适用。

复制易犯错误

要成功复制,文档大小和所有附件大小的总和必须小于目标集群的最大请求大小。 例如,如果最大 HTTP 请求大小为 11 MB,那么以下场景适用:

基于最大 HTTP 请求大小 11 MB 的各种方案
文档大小 附件大小 总大小 是否复制?
1 MB 5 个 2 MB 的附件 11 MB
1 MB 1 个 10 MB 的附件 11 MB
1 MB 100 个 1 MB 的附件 101 MB

使用复制时,有一些事项需注意。

用户许可权不正确

为了使从数据库“a”复制到数据库“b”的过程以最佳方式执行,提供的凭证必须具有:

  • 对数据库“a”的 _reader_replicator 许可权。
  • 对数据库“b”的 _writer 许可权。

API密钥可在 IBM Cloudant 控制台中生成,也可通过 API 生成。 可以为每个密钥设置与特定 IBM Cloudant 数据库相关的独立权限。 IBM Cloudant 必须能够在复制的“读取”端写入其检查点文档,否则不会保存任何状态,并且无法从其停止的位置恢复复制。 如果未保存状态,那么在恢复大型数据集的复制时,可能会导致性能问题。 原因是,在没有检查点的情况下,复制过程每次恢复时都会从头重新开始。

复制文档发生冲突

未正确设置用户许可权的另一个结果是 _replicator 文档会出现冲突。 _replicator 文档会记录复制过程的当前状态。 在极端情况下,此文档可能会因包含许多未解决的冲突而变得很大。 此类大型文档会占用大量可用空间,导致额外的服务器负载。

可以通过向 _replicator 端点发送 GET 请求来检查 /_replicator 数据库的大小:

curl "$SERVICE_URL/_replicator"
import com.ibm.cloud.cloudant.v1.Cloudant;
import com.ibm.cloud.cloudant.v1.model.DatabaseInformation;
import com.ibm.cloud.cloudant.v1.model.GetDatabaseInformationOptions;
Cloudant service = Cloudant.newInstance();
GetDatabaseInformationOptions databaseInfoOptions =
    new GetDatabaseInformationOptions.Builder()
        .db("_replicator")
        .build();
DatabaseInformation response =
    service.getDatabaseInformation(databaseInfoOptions).execute()
        .getResult();
System.out.println(response);
const { CloudantV1 } = require('@ibm-cloud/cloudant');
const service = CloudantV1.newInstance({});
service.getDatabaseInformation({db: '_replicator'}).then(response => {
  console.log(response.result);
});
from ibmcloudant.cloudant_v1 import CloudantV1
service = CloudantV1.new_instance()
response = service.get_database_information(db='_replicator').get_result()
print(response)
getDatabaseInformationOptions := service.NewGetDatabaseInformationOptions(
  "_replicator",
)
databaseInformation, response, err := service.GetDatabaseInformation(getDatabaseInformationOptions)
if err != nil {
  panic(err)
}
b, _ := json.MarshalIndent(databaseInformation, "", "  ")
fmt.Println(string(b))

先前的 Go 示例需要以下导入块:

import (
   "encoding/json"
   "fmt"
   "github.com/IBM/cloudant-go-sdk/cloudantv1"
)

所有 Go 示例都需要初始化 service 对象。 有关更多信息,请参阅 API 文档的 认证部分 以获取示例。

从复制文档获取冲突

在返回的 JSON 中,查找 disk_size 值。 如果该值指示大小超过 1 GB,请转至 IBM Cloud 支持门户网站 以获取进一步建议。

可以检查单个 _replicator 文档是否存在冲突,如以下示例所示:

curl "$SERVICE_URL/_replicator/$DOCID?conflicts=true"
import com.ibm.cloud.cloudant.v1.Cloudant;
import com.ibm.cloud.cloudant.v1.model.GetReplicationDocumentOptions;
import com.ibm.cloud.cloudant.v1.model.ReplicationDocument;
Cloudant service = Cloudant.newInstance();
GetReplicationDocumentOptions replicationDocOptions =
    new GetReplicationDocumentOptions.Builder()
        .conflicts(true)
        .docId("$DOCID")
        .build();
ReplicationDocument response =
    service.getReplicationDocument(replicationDocOptions).execute()
        .getResult();
System.out.println(response);
const { CloudantV1 } = require('@ibm-cloud/cloudant');
const service = CloudantV1.newInstance({});
service.getReplicationDocument({
  conflicts: true,
  docId: '$DOCID'
}).then(response => {
  console.log(response.result);
});
from ibmcloudant.cloudant_v1 import CloudantV1
service = CloudantV1.new_instance()
response = service.get_replication_document(
  conflicts=True,
  doc_id='$DOCID'
).get_result()
print(response)
getReplicationDocumentOptions := service.NewGetReplicationDocumentOptions(
  "$DOCID",
)
replicationDocument, response, err := service.GetReplicationDocument(getReplicationDocumentOptions)
if err != nil {
  panic(err)
}
replicationDocument.Conflicts = core.BoolPtr(true)
b, _ := json.MarshalIndent(replicationDocument, "", "  ")
fmt.Println(string(b))

先前的 Go 示例需要以下导入块:

import (
   "encoding/json"
   "fmt"
   "github.com/IBM/cloudant-go-sdk/cloudantv1"
   "github.com/IBM/go-sdk-core/core"
)

所有 Go 示例都需要初始化 service 对象。 有关更多信息,请参阅 API 文档的 认证部分 以获取示例。

取消所有复制

如果要取消所有复制并从全新的干净 _replicator 数据库开始,请删除并重新创建 replicator 数据库。

请参阅以下 HTTP,删除并重新创建 _replicator 数据库:

DELETE /_replicator HTTP/1.1
HOST: $SERVICE_URL
Authorization: ...
PUT /_replicator HTTP/1.1
HOST: $SERVICE_URL
Authorization: ...

删除复制器数据库

请参阅以下示例以除去 _replicator 数据库:

curl -X DELETE "$SERVICE_URL/_replicator"
import com.ibm.cloud.cloudant.v1.Cloudant;
import com.ibm.cloud.cloudant.v1.model.DeleteDatabaseOptions;
import com.ibm.cloud.cloudant.v1.model.Ok;
Cloudant service = Cloudant.newInstance();
DeleteDatabaseOptions deleteDatabaseOptions = new DeleteDatabaseOptions.Builder()
        .db("_replicator")
        .build();
Ok response = service.deleteDatabase(deleteDatabaseOptions).execute()
        .getResult();
System.out.println(response);
const { CloudantV1 } = require('@ibm-cloud/cloudant');
const service = CloudantV1.newInstance({});
service.deleteDatabase({db: '_replicator'}).then(response => {
  console.log(response.result);
});
from ibmcloudant.cloudant_v1 import CloudantV1
service = CloudantV1.new_instance()
response = service.delete_database(db='_replicator').get_result()
print(response)
deleteDatabaseOptions := service.NewDeleteDatabaseOptions(
  "_replicator",
)
ok, response, err := service.DeleteDatabase(deleteDatabaseOptions)
if err != nil {
  panic(err)
}
b, _ := json.MarshalIndent(ok, "", "  ")
fmt.Println(string(b))

先前的 Go 示例需要以下导入块:

import (
   "encoding/json"
   "fmt"
   "github.com/IBM/cloudant-go-sdk/cloudantv1"
)

所有 Go 示例都需要初始化 service 对象。 有关更多信息,请参阅 API 文档的 认证部分 以获取示例。

重新创建复制器数据库

请参阅以下示例以重新创建 _replicator 数据库:

curl -X PUT "$SERVICE_URL/_replicator"
import com.ibm.cloud.cloudant.v1.Cloudant;
import com.ibm.cloud.cloudant.v1.model.Ok;
import com.ibm.cloud.cloudant.v1.model.PutDatabaseOptions;
Cloudant service = Cloudant.newInstance();
PutDatabaseOptions databaseOptions = new PutDatabaseOptions.Builder()
    .db("_replicator")
    .build();
Ok response =
    service.putDatabase(databaseOptions).execute()
        .getResult();
System.out.println(response);
const { CloudantV1 } = require('@ibm-cloud/cloudant');
const service = CloudantV1.newInstance({});
service.putDatabase({
  db: '_replicator'
}).then(response => {
  console.log(response.result);
});
from ibmcloudant.cloudant_v1 import CloudantV1
service = CloudantV1.new_instance()
response = service.put_database(db='_replicator').get_result()
print(response)
putDatabaseOptions := service.NewPutDatabaseOptions(
  "_replicator",
)
ok, response, err := service.PutDatabase(putDatabaseOptions)
if err != nil {
  panic(err)
}
b, _ := json.MarshalIndent(ok, "", "  ")
fmt.Println(string(b))

先前的 Go 示例需要以下导入块:

import (
   "encoding/json"
   "fmt"
   "github.com/IBM/cloudant-go-sdk/cloudantv1"
)

所有 Go 示例都需要初始化 service 对象。 有关更多信息,请参阅 API 文档的 认证部分 以获取示例。

多个同时复制

很容易会忘记您先前在两个数据库之间设置过复制,因而错误地创建多余的复制过程。 每个复制作业都相互独立,因此 IBM Cloudant 不会阻止您创建多余的复制过程。 但是,每个复制任务都会耗尽系统资源。

您可以在 IBM Cloudant 仪表盘中查看“正在进行的复制” ,以确保没有不需要的复制任务正在进行中。 删除所有不再需要的 _replicator 文档。

调整复制速度

By default, IBM Cloudant 复制以适当的速率进行,以便将数据从源端传输到目标端, 同时不会对性能造成负面影响。 在其他任务的复制速率和集群性能之间进行选择是一种权衡。 您的用例可能需要更快的复制,但代价是其他 IBM Cloudant 服务的复制会变慢。 或者,您可能需要集群性能优先,而将复制作为后台进程处理。

高级复制 API 选项 可用。 这些选项支持在复制期间使用的计算能力量增加或减少,如以下示例中所示:

  • 如果文档包含附件,那么可能要考虑减小 batch_size 而增大 worker_processes,以便在较小的批次中容纳更大的文档。
  • 如果有许多微型文档,那么可以考虑增大 worker_processhttp_connections 值。
  • 如果要在运行复制时尽可能减少影响,将 worker_processeshttp_connections 设置为 1 可能比较适合。
  • 有关更多信息,请参阅 按复制使用读和写操作

有关用例的最佳配置的进一步帮助,请转至 IBM Cloud 支持门户网站

可以通过启用 "use_bulk_get": true" 复制选项来提高复制性能。 在这种情况下,复制器将从源批量访存文档,而不是单独访存文档。

{
  "_id": "rep_doc_id",
  "source": "https://account1.cloudant.com/db1",
  "target": "https://account2.cloudant.com/db2",
  "use_bulk_get": true
}

增加的复制速率可能会消耗源和目标端点帐户上的可用读或写速率容量。

除去具有复制的冲突文档修订版

通过复制除去有冲突的文档修订版的一种方法是启用 "winning_revs_only": true 选项。 此选项仅复制 获胜 文档修订版。 这是缺省情况下由 GET $SERVICE_URL/$DATABASE/$DOCID 请求返回的修订版。 此选项是高级选项,因为它会废弃有冲突的文档修订版。 请谨慎使用此选项。