2.4. CouchDB 复制协议

版本:

3

《CouchDB 复制协议》是一种通过使用公共 CouchDB REST API 在 HTTP/1.1 上同步两个对等体之间 JSON 文档的协议,它基于 Apache CouchDB MVCC 数据模型。

2.4.1. 前言

2.4.1.1. 语言

本文件中“必须”、“不得”、“必需”、“应”、“不应”、“建议”、“不建议”、“推荐”、“可”和“可选”等关键词的解释,请参见 RFC 2119

2.4.1.2. 目标

本规范的主要目标是描述《CouchDB 复制协议》的内部机制。

次要目标是提供有关该协议的足够详细的信息,以便于在任何语言和平台上轻松构建可以与 CouchDB 同步数据的工具。

2.4.1.3. 定义

JSON

JSON 是一种用于序列化结构化数据的文本格式。它在 ECMA-262RFC 4627 中有描述。

URI

URI 由 RFC 3986 定义。它可以是 RFC 1738 中定义的 URL。

ID

标识符(可以是 UUID),如 RFC 4122 中所述。

修订版

一个 MVCC 令牌值,遵循以下模式:N-sig,其中 N 始终为正整数,而 sig 为文档签名(自定义)。不要将其与版本控制系统中的修订版混淆!

叶修订版

一系列更改中的最后一个文档修订版。由于并发更新,文档可能具有多个叶修订版(也称为冲突修订版)。

文档

一个文档是一个 JSON 对象,它具有分别在 _id_rev 字段中定义的 ID 和修订版。文档的 ID 在其存储的数据库中必须是唯一的。

数据库

具有唯一 URI 的文档集合。

更改馈送

指定数据库的文档更改事件(创建、更新、删除)流。

序列 ID

更改馈送提供的 ID。它必须是递增的,但可能不总是整数。

复制文档的数据库。

目标

复制文档到的数据库。

复制

源和目标端点之间的一方向同步过程。

检查点

用于复制恢复的中间记录序列 ID。

复制器

启动和运行复制的服务或应用程序。

过滤器函数

任何编程语言的特殊函数,用于在复制过程中过滤文档(请参见 过滤器函数

过滤器函数名称

过滤器函数的 ID,可以用作符号引用(也称为回调函数)来将相关的过滤器函数应用于复制。

过滤复制

使用过滤器函数从源复制文档到目标。

完全复制

从源复制所有文档到目标。

推送复制

源为本地端点,目标为远程端点的复制过程。

拉取复制

源为远程端点,目标为本地端点的复制过程。

连续复制

“永不停止”的复制:在处理完更改馈送中的所有事件后,复制器不会关闭连接,而是等待来自源的新更改事件。连接通过定期心跳保持活动状态。

复制日志

一个特殊的文档,它保存源和目标之间的复制历史记录(记录的检查点以及其他一些统计信息)。

复制 ID

一个唯一的值,明确标识复制日志。

2.4.2. 复制协议算法

《CouchDB 复制协议》并非神奇,而是对使用公共 CouchDB HTTP REST API 来实现文档从源到目标的复制的约定。

参考实现是用 Erlang 编写的,由 Apache CouchDB 中的 couch_replicator 模块提供。

建议遵循此算法规范,使用相同的 HTTP 端点,并使用相同的参数运行请求,以提供完全兼容的实现。自定义复制器实现可以使用不同的 HTTP API 端点和请求参数,具体取决于其本地情况,它们可以仅实现复制协议的一部分以仅运行推送或拉取复制。但是,虽然此类解决方案也可以运行复制过程,但它们会失去与 CouchDB 复制器的兼容性。

2.4.2.1. 验证对等体

+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
' Verify Peers:                                                             '
'                                                                           '
'                404 Not Found   +--------------------------------+         '
'       +----------------------- |     Check Source Existence     |         '
'       |                        +--------------------------------+         '
'       |                        |          HEAD /source          |         '
'       |                        +--------------------------------+         '
'       |                          |                                        '
'       |                          | 200 OK                                 '
'       |                          v                                        '
'       |                        +--------------------------------+         '
'       |                        |     Check Target Existence     | ----+   '
'       |                        +--------------------------------+     |   '
'       |                        |         HEAD /target           |     |   '
'       |                        +--------------------------------+     |   '
'       |                          |                                    |   '
'       |                          | 404 Not Found                      |   '
'       v                          v                                    |   '
'   +-------+    No              +--------------------------------+     |   '
'   | Abort | <----------------- |         Create Target?         |     |   '
'   +-------+                    +--------------------------------+     |   '
'       ^                          |                                    |   '
'       |                          | Yes                                |   '
'       |                          v                                    |   '
'       |        Failure         +--------------------------------+     |   '
'       +----------------------- |          Create Target         |     |   '
'                                +--------------------------------+     |   '
'                                |           PUT /target          |     |   '
'                                +--------------------------------+     |   '
'                                  |                                    |   '
'                                  | 201 Created                 200 OK |   '
'                                  |                                    |   '
+ - - - - - - - - - - - - - - - -  | - - - - - - - - - - - - - - - - -  | - +
                                   |                                    |
+ - - - - - - - - - - - - - - - -  | - - - - - - - - - - - - - - - - -  | - +
' Get Peers Information:           |                                    |   '
'                                  +------------------------------------+   '
'                                  |                                        '
'                                  v                                        '
'                                +--------------------------------+         '
'                                |     Get Source Information     |         '
'                                +--------------------------------+         '
'                                                                           '
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +

复制器必须使用 HEAD /{db} 请求来确保源和目标都存在。

2.4.2.1.1. 检查源是否存在

请求:

HEAD /source HTTP/1.1
Host: localhost:5984
User-Agent: CouchDB

响应:

HTTP/1.1 200 OK
Cache-Control: must-revalidate
Content-Type: application/json
Date: Sat, 05 Oct 2013 08:50:39 GMT
Server: CouchDB (Erlang/OTP)

2.4.2.1.2. 检查目标是否存在

请求:

HEAD /target HTTP/1.1
Host: localhost:5984
User-Agent: CouchDB

响应:

HTTP/1.1 200 OK
Cache-Control: must-revalidate
Content-Type: application/json
Date: Sat, 05 Oct 2013 08:51:11 GMT
Server: CouchDB (Erlang/OTP)

2.4.2.1.3. 创建目标?

如果目标不存在,复制器可以使用 PUT /{db} 请求来创建目标

请求:

PUT /target HTTP/1.1
Accept: application/json
Host: localhost:5984
User-Agent: CouchDB

响应:

HTTP/1.1 201 Created
Content-Length: 12
Content-Type: application/json
Date: Sat, 05 Oct 2013 08:58:41 GMT
Server: CouchDB (Erlang/OTP)

{
    "ok": true
}

但是,由于权限不足(由提供的凭据授予),复制器的 PUT 请求可能无法成功,因此会收到 401 未经授权403 禁止 错误。应预期此类错误并妥善处理

HTTP/1.1 500 Internal Server Error
Cache-Control: must-revalidate
Content-Length: 108
Content-Type: application/json
Date: Fri, 09 May 2014 13:50:32 GMT
Server: CouchDB (Erlang OTP)

{
    "error": "unauthorized",
    "reason": "unauthorized to access or create database http://localhost:5984/target"
}

2.4.2.1.4. 中止

如果源或目标不存在,应使用 HTTP 错误响应中止复制

HTTP/1.1 500 Internal Server Error
Cache-Control: must-revalidate
Content-Length: 56
Content-Type: application/json
Date: Sat, 05 Oct 2013 08:55:29 GMT
Server: CouchDB (Erlang OTP)

{
    "error": "db_not_found",
    "reason": "could not open source"
}

2.4.2.2. 获取对等体信息

+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+
' Verify Peers:                                                    '
'                         +------------------------+               '
'                         | Check Target Existence |               '
'                         +------------------------+               '
'                                     |                            '
'                                     | 200 OK                     '
'                                     |                            '
+ - - - - - - - - - - - - - - - - - - | - - - - - - - - - - - - - -+
                                      |
+ - - - - - - - - - - - - - - - - - - | - - - - - - - - - - - - - -+
' Get Peers Information:              |                            '
'                                     v                            '
'                         +------------------------+               '
'                         | Get Source Information |               '
'                         +------------------------+               '
'                         |      GET /source       |               '
'                         +------------------------+               '
'                                     |                            '
'                                     | 200 OK                     '
'                                     v                            '
'                         +------------------------+               '
'                         | Get Target Information |               '
'                         +------------------------+               '
'                         |      GET /target       |               '
'                         +------------------------+               '
'                                     |                            '
'                                     | 200 OK                     '
'                                     |                            '
+ - - - - - - - - - - - - - - - - - - | - - - - - - - - - - - - - -+
                                      |
+ - - - - - - - - - - - - - - - - - - | - - - - - - - - - - - - - -+
' Find Common Ancestry:               |                            '
'                                     |                            '
'                                     v                            '
'                         +-------------------------+              '
'                         | Generate Replication ID |              '
'                         +-------------------------+              '
'                                                                  '
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+

复制器使用 GET /{db} 请求从源和目标检索基本信息。GET 响应必须包含具有以下必填字段的 JSON 对象

  • instance_start_time字符串):始终为 "0"。(出于遗留原因返回。)

  • update_seq数字 / 字符串):当前数据库序列 ID。

其他任何字段都是可选的。Replicator 需要的信息是 update_seq 字段:此值将用于定义一个临时(因为数据库数据可能会发生变化)的上限,用于更改提要监听和统计计算,以显示正确的复制进度。

2.4.2.2.1. 获取源信息

请求:

GET /source HTTP/1.1
Accept: application/json
Host: localhost:5984
User-Agent: CouchDB

响应:

HTTP/1.1 200 OK
Cache-Control: must-revalidate
Content-Length: 256
Content-Type: application/json
Date: Tue, 08 Oct 2013 07:53:08 GMT
Server: CouchDB (Erlang OTP)

{
    "committed_update_seq": 61772,
    "compact_running": false,
    "db_name": "source",
    "disk_format_version": 6,
    "doc_count": 41961,
    "doc_del_count": 3807,
    "instance_start_time": "0",
    "purge_seq": 0,
    "sizes": {
      "active": 70781613961,
      "disk": 79132913799,
      "external": 72345632950
    },
    "update_seq": 61772
}

2.4.2.2.2. 获取目标信息

请求:

GET /target/ HTTP/1.1
Accept: application/json
Host: localhost:5984
User-Agent: CouchDB

响应:

HTTP/1.1 200 OK
Content-Length: 363
Content-Type: application/json
Date: Tue, 08 Oct 2013 12:37:01 GMT
Server: CouchDB (Erlang/OTP)

{
    "compact_running": false,
    "db_name": "target",
    "disk_format_version": 5,
    "doc_count": 1832,
    "doc_del_count": 1,
    "instance_start_time": "0",
    "purge_seq": 0,
    "sizes": {
      "active": 50829452,
      "disk": 77001455,
      "external": 60326450
    },
    "update_seq": "1841-g1AAAADveJzLYWBgYMlgTmGQT0lKzi9KdUhJMtbLSs1LLUst0k"
}

2.4.2.3. 查找共同祖先

+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
' Get Peers Information:                                                    '
'                                                                           '
'                             +-------------------------------------------+ '
'                             |           Get Target Information          | '
'                             +-------------------------------------------+ '
'                               |                                           '
+ - - - - - - - - - - - - - - - | - - - - - - - - - - - - - - - - - - - - - +
                                |
+ - - - - - - - - - - - - - - - | - - - - - - - - - - - - - - - - - - - - - +
' Find Common Ancestry:         v                                           '
'                             +-------------------------------------------+ '
'                             |          Generate Replication ID          | '
'                             +-------------------------------------------+ '
'                               |                                           '
'                               |                                           '
'                               v                                           '
'                             +-------------------------------------------+ '
'                             |      Get Replication Log from Source      | '
'                             +-------------------------------------------+ '
'                             |     GET /source/_local/replication-id     | '
'                             +-------------------------------------------+ '
'                               |                                           '
'                               | 200 OK                                    '
'                               | 404 Not Found                             '
'                               v                                           '
'                             +-------------------------------------------+ '
'                             |      Get Replication Log from Target      | '
'                             +-------------------------------------------+ '
'                             |     GET /target/_local/replication-id     | '
'                             +-------------------------------------------+ '
'                               |                                           '
'                               | 200 OK                                    '
'                               | 404 Not Found                             '
'                               v                                           '
'                             +-------------------------------------------+ '
'                             |          Compare Replication Logs         | '
'                             +-------------------------------------------+ '
'                               |                                           '
'                               | Use latest common sequence as start point '
'                               |                                           '
+ - - - - - - - - - - - - - - - | - - - - - - - - - - - - - - - - - - - - - +
                                |
                                |
+ - - - - - - - - - - - - - - - | - - - - - - - - - - - - - - - - - - - - - +
' Locate Changed Documents:     |                                           '
'                               |                                           '
'                               v                                           '
'                             +-------------------------------------------+ '
'                             |        Listen Source Changes Feed         | '
'                             +-------------------------------------------+ '
'                                                                           '
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +

2.4.2.3.1. 生成复制 ID

在开始复制之前,Replicator 必须生成一个复制 ID。此值用于跟踪复制历史记录,恢复和继续之前中断的复制过程。

复制 ID 生成算法是特定于实现的。无论使用哪种算法,它都必须唯一地标识复制过程。例如,CouchDB 的 Replicator 在生成复制 ID 时使用以下因素

  • 持久性对等 UUID 值。对于 CouchDB,使用本地 Server UUID

  • 源和目标 URI,以及源或目标是本地还是远程数据库

  • 如果需要创建目标

  • 如果复制是连续的

  • 任何自定义标头

  • 过滤器函数 代码(如果使用)

  • 更改提要查询参数(如果有)

注意

有关复制 ID 生成实现示例,请参阅 couch_replicator_ids.erl

2.4.2.3.2. 从源和目标检索复制日志

生成复制 ID 后,Replicator 应该使用 GET /{db}/_local/{docid} 从源和目标检索复制日志。

请求:

GET /source/_local/b3e44b920ee2951cb2e123b63044427a HTTP/1.1
Accept: application/json
Host: localhost:5984
User-Agent: CouchDB

响应:

HTTP/1.1 200 OK
Cache-Control: must-revalidate
Content-Length: 1019
Content-Type: application/json
Date: Thu, 10 Oct 2013 06:18:56 GMT
ETag: "0-8"
Server: CouchDB (Erlang OTP)

{
    "_id": "_local/b3e44b920ee2951cb2e123b63044427a",
    "_rev": "0-8",
    "history": [
        {
            "doc_write_failures": 0,
            "docs_read": 2,
            "docs_written": 2,
            "end_last_seq": 5,
            "end_time": "Thu, 10 Oct 2013 05:56:38 GMT",
            "missing_checked": 2,
            "missing_found": 2,
            "recorded_seq": 5,
            "session_id": "d5a34cbbdafa70e0db5cb57d02a6b955",
            "start_last_seq": 3,
            "start_time": "Thu, 10 Oct 2013 05:56:38 GMT"
        },
        {
            "doc_write_failures": 0,
            "docs_read": 1,
            "docs_written": 1,
            "end_last_seq": 3,
            "end_time": "Thu, 10 Oct 2013 05:56:12 GMT",
            "missing_checked": 1,
            "missing_found": 1,
            "recorded_seq": 3,
            "session_id": "11a79cdae1719c362e9857cd1ddff09d",
            "start_last_seq": 2,
            "start_time": "Thu, 10 Oct 2013 05:56:12 GMT"
        },
        {
            "doc_write_failures": 0,
            "docs_read": 2,
            "docs_written": 2,
            "end_last_seq": 2,
            "end_time": "Thu, 10 Oct 2013 05:56:04 GMT",
            "missing_checked": 2,
            "missing_found": 2,
            "recorded_seq": 2,
            "session_id": "77cdf93cde05f15fcb710f320c37c155",
            "start_last_seq": 0,
            "start_time": "Thu, 10 Oct 2013 05:56:04 GMT"
        }
    ],
    "replication_id_version": 3,
    "session_id": "d5a34cbbdafa70e0db5cb57d02a6b955",
    "source_last_seq": 5
}

复制日志应该包含以下字段

  • history数组 of 对象):复制历史记录。必需

    • doc_write_failures数字):写入失败次数

    • docs_read数字):读取文档的次数

    • docs_written数字):写入文档的次数

    • end_last_seq数字):最后处理的更新序列 ID

    • end_time字符串):以 RFC 5322 格式表示的复制完成时间戳

    • missing_checked数字):在源上检查的修订次数

    • missing_found数字):在目标上找到的缺失修订次数

    • recorded_seq数字):记录的中间检查点。必需

    • session_id字符串):唯一的会话 ID。通常,使用随机 UUID 值。必需

    • start_last_seq数字):开始更新序列 ID

    • start_time字符串):以 RFC 5322 格式表示的复制开始时间戳

  • replication_id_version数字):复制协议版本。定义复制 ID 计算算法、HTTP API 调用和其他例程。必需

  • session_id字符串):上次会话的唯一 ID。最新 history 对象的 session_id 字段的快捷方式。必需

  • source_last_seq数字):最后处理的检查点。最新 history 对象的 recorded_seq 字段的快捷方式。必需

此请求可能会出现 404 Not Found 响应

请求:

GET /source/_local/b6cef528f67aa1a8a014dd1144b10e09 HTTP/1.1
Accept: application/json
Host: localhost:5984
User-Agent: CouchDB

响应:

HTTP/1.1 404 Object Not Found
Cache-Control: must-revalidate
Content-Length: 41
Content-Type: application/json
Date: Tue, 08 Oct 2013 13:31:10 GMT
Server: CouchDB (Erlang OTP)

{
    "error": "not_found",
    "reason": "missing"
}

这没关系。这意味着没有关于当前复制的信息,因此它以前可能没有运行,因此 Replicator 必须运行完全复制。

2.4.2.3.3. 比较复制日志

如果从源和目标成功检索到复制日志,则 Replicator 必须通过遵循以下算法来确定它们的共同祖先

  • 比较按时间顺序排列的最后会话的 session_id 值 - 如果它们匹配,则源和目标具有共同的复制历史记录,并且似乎有效。使用 source_last_seq 值作为启动检查点

  • 如果出现不匹配,请遍历 history 集合以搜索源和目标的最新(按时间顺序排列)共同 session_id。使用 recorded_seq 字段的值作为启动检查点

如果源和目标没有共同的祖先,则 Replicator 必须运行完全复制。

2.4.2.4. 定位已更改的文档

+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
' Find Common Ancestry:                                                     '
'                                                                           '
'             +------------------------------+                              '
'             |   Compare Replication Logs   |                              '
'             +------------------------------+                              '
'                                          |                                '
'                                          |                                '
+ - - - - - - - - - - - - - - - - - - - -  |  - - - - - - - - - - - - - - - +
                                           |
+ - - - - - - - - - - - - - - - - - - - -  |  - - - - - - - - - - - - - - - +
' Locate Changed Documents:                |                                '
'                                          |                                '
'                                          |                                '
'                                          v                                '
'            +-------------------------------+                              '
'   +------> |     Listen to Changes Feed    | -----+                       '
'   |        +-------------------------------+      |                       '
'   |        |     GET  /source/_changes     |      |                       '
'   |        |     POST /source/_changes     |      |                       '
'   |        +-------------------------------+      |                       '
'   |                                      |        |                       '
'   |                                      |        |                       '
'   |                There are new changes |        | No more changes       '
'   |                                      |        |                       '
'   |                                      v        v                       '
'   |        +-------------------------------+    +-----------------------+ '
'   |        |     Read Batch of Changes     |    | Replication Completed | '
'   |        +-------------------------------+    +-----------------------+ '
'   |                                      |                                '
'   | No                                   |                                '
'   |                                      v                                '
'   |        +-------------------------------+                              '
'   |        |  Compare Documents Revisions  |                              '
'   |        +-------------------------------+                              '
'   |        |    POST /target/_revs_diff    |                              '
'   |        +-------------------------------+                              '
'   |                                      |                                '
'   |                               200 OK |                                '
'   |                                      v                                '
'   |        +-------------------------------+                              '
'   +------- |     Any Differences Found?    |                              '
'            +-------------------------------+                              '
'                                          |                                '
'                                      Yes |                                '
'                                          |                                '
+ - - - - - - - - - - - - - - - - - - - -  |  - - - - - - - - - - - - - - - +
                                           |
+ - - - - - - - - - - - - - - - - - - - -  |  - - - - - - - - - - - - - - - +
' Replicate Changes:                       |                                '
'                                          v                                '
'            +-------------------------------+                              '
'            |  Fetch Next Changed Document  |                              '
'            +-------------------------------+                              '
'                                                                           '
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +

2.4.2.4.1. 监听更改提要

定义启动检查点后,Replicator 应该通过使用 更改提要 读取源的 GET /{db}/_changes 请求。此请求必须使用以下查询参数发出

  • feed 参数定义更改提要响应样式:对于连续复制,应该使用 continuous 值,否则使用 normal

  • style=all_docs 查询参数告诉源它必须在输出中包含每个文档事件的所有修订叶子。

  • 对于连续复制,heartbeat 参数定义以毫秒为单位的心跳周期。默认情况下,推荐的值是 10000(10 秒)。

  • 如果在比较复制日志时找到了启动检查点,则必须使用此值传递 since 查询参数。在完全复制的情况下,它可以是 0(数字零)或省略。

此外,可以指定 filter 查询参数以在源端启用 过滤器函数。还可以提供其他自定义参数。

2.4.2.4.2. 读取更改批次

一次性读取整个提要可能不是最佳的资源使用方式。建议以小块的形式处理提要。但是,没有关于块大小的具体建议,因为它在很大程度上取决于可用资源:大块需要更多内存,而它们减少了 I/O 操作,反之亦然。

请注意,更改提要输出格式对于使用 feed=normal 和使用 feed=continuous 查询参数的请求是不同的。

普通提要

请求:

GET /source/_changes?feed=normal&style=all_docs&heartbeat=10000 HTTP/1.1
Accept: application/json
Host: localhost:5984
User-Agent: CouchDB

响应:

HTTP/1.1 200 OK
Cache-Control: must-revalidate
Content-Type: application/json
Date: Fri, 09 May 2014 16:20:41 GMT
Server: CouchDB (Erlang OTP)
Transfer-Encoding: chunked

{"results":[
{"seq":14,"id":"f957f41e","changes":[{"rev":"3-46a3"}],"deleted":true}
{"seq":29,"id":"ddf339dd","changes":[{"rev":"10-304b"}]}
{"seq":37,"id":"d3cc62f5","changes":[{"rev":"2-eec2"}],"deleted":true}
{"seq":39,"id":"f13bd08b","changes":[{"rev":"1-b35d"}]}
{"seq":41,"id":"e0a99867","changes":[{"rev":"2-c1c6"}]}
{"seq":42,"id":"a75bdfc5","changes":[{"rev":"1-967a"}]}
{"seq":43,"id":"a5f467a0","changes":[{"rev":"1-5575"}]}
{"seq":45,"id":"470c3004","changes":[{"rev":"11-c292"}]}
{"seq":46,"id":"b1cb8508","changes":[{"rev":"10-ABC"}]}
{"seq":47,"id":"49ec0489","changes":[{"rev":"157-b01f"},{"rev":"123-6f7c"}]}
{"seq":49,"id":"dad10379","changes":[{"rev":"1-9346"},{"rev":"6-5b8a"}]}
{"seq":50,"id":"73464877","changes":[{"rev":"1-9f08"}]}
{"seq":51,"id":"7ae19302","changes":[{"rev":"1-57bf"}]}
{"seq":63,"id":"6a7a6c86","changes":[{"rev":"5-acf6"}],"deleted":true}
{"seq":64,"id":"dfb9850a","changes":[{"rev":"1-102f"}]}
{"seq":65,"id":"c532afa7","changes":[{"rev":"1-6491"}]}
{"seq":66,"id":"af8a9508","changes":[{"rev":"1-3db2"}]}
{"seq":67,"id":"caa3dded","changes":[{"rev":"1-6491"}]}
{"seq":68,"id":"79f3b4e9","changes":[{"rev":"1-102f"}]}
{"seq":69,"id":"1d89d16f","changes":[{"rev":"1-3db2"}]}
{"seq":71,"id":"abae7348","changes":[{"rev":"2-7051"}]}
{"seq":77,"id":"6c25534f","changes":[{"rev":"9-CDE"},{"rev":"3-00e7"},{"rev":"1-ABC"}]}
{"seq":78,"id":"SpaghettiWithMeatballs","changes":[{"rev":"22-5f95"}]}
],
"last_seq":78}

连续提要

请求:

GET /source/_changes?feed=continuous&style=all_docs&heartbeat=10000 HTTP/1.1
Accept: application/json
Host: localhost:5984
User-Agent: CouchDB

响应:

HTTP/1.1 200 OK
Cache-Control: must-revalidate
Content-Type: application/json
Date: Fri, 09 May 2014 16:22:22 GMT
Server: CouchDB (Erlang OTP)
Transfer-Encoding: chunked

{"seq":14,"id":"f957f41e","changes":[{"rev":"3-46a3"}],"deleted":true}
{"seq":29,"id":"ddf339dd","changes":[{"rev":"10-304b"}]}
{"seq":37,"id":"d3cc62f5","changes":[{"rev":"2-eec2"}],"deleted":true}
{"seq":39,"id":"f13bd08b","changes":[{"rev":"1-b35d"}]}
{"seq":41,"id":"e0a99867","changes":[{"rev":"2-c1c6"}]}
{"seq":42,"id":"a75bdfc5","changes":[{"rev":"1-967a"}]}
{"seq":43,"id":"a5f467a0","changes":[{"rev":"1-5575"}]}
{"seq":45,"id":"470c3004","changes":[{"rev":"11-c292"}]}
{"seq":46,"id":"b1cb8508","changes":[{"rev":"10-ABC"}]}
{"seq":47,"id":"49ec0489","changes":[{"rev":"157-b01f"},{"rev":"123-6f7c"}]}
{"seq":49,"id":"dad10379","changes":[{"rev":"1-9346"},{"rev":"6-5b8a"}]}
{"seq":50,"id":"73464877","changes":[{"rev":"1-9f08"}]}
{"seq":51,"id":"7ae19302","changes":[{"rev":"1-57bf"}]}
{"seq":63,"id":"6a7a6c86","changes":[{"rev":"5-acf6"}],"deleted":true}
{"seq":64,"id":"dfb9850a","changes":[{"rev":"1-102f"}]}
{"seq":65,"id":"c532afa7","changes":[{"rev":"1-6491"}]}
{"seq":66,"id":"af8a9508","changes":[{"rev":"1-3db2"}]}
{"seq":67,"id":"caa3dded","changes":[{"rev":"1-6491"}]}
{"seq":68,"id":"79f3b4e9","changes":[{"rev":"1-102f"}]}
{"seq":69,"id":"1d89d16f","changes":[{"rev":"1-3db2"}]}
{"seq":71,"id":"abae7348","changes":[{"rev":"2-7051"}]}
{"seq":75,"id":"SpaghettiWithMeatballs","changes":[{"rev":"21-5949"}]}
{"seq":77,"id":"6c255","changes":[{"rev":"9-CDE"},{"rev":"3-00e7"},{"rev":"1-ABC"}]}
{"seq":78,"id":"SpaghettiWithMeatballs","changes":[{"rev":"22-5f95"}]}

对于两种更改提要格式,都保留每行记录的样式,以简化迭代获取和解码 JSON 对象,从而减少内存占用。

2.4.2.4.3. 计算修订差异

从更改提要读取更改批次后,Replicator 为文档 ID 和相关的叶子修订形成一个 JSON 映射对象,并将结果通过 POST /{db}/_revs_diff 请求发送到目标。

请求:

POST /target/_revs_diff HTTP/1.1
Accept: application/json
Content-Length: 287
Content-Type: application/json
Host: localhost:5984
User-Agent: CouchDB

{
    "baz": [
        "2-7051cbe5c8faecd085a3fa619e6e6337"
    ],
    "foo": [
        "3-6a540f3d701ac518d3b9733d673c5484"
    ],
    "bar": [
        "1-d4e501ab47de6b2000fc8a02f84a0c77",
        "1-967a00dff5e02add41819138abb3284d"
    ]
}

响应:

HTTP/1.1 200 OK
Cache-Control: must-revalidate
Content-Length: 88
Content-Type: application/json
Date: Fri, 25 Oct 2013 14:44:41 GMT
Server: CouchDB (Erlang/OTP)

{
    "baz": {
        "missing": [
            "2-7051cbe5c8faecd085a3fa619e6e6337"
        ]
    },
    "bar": {
        "missing": [
            "1-d4e501ab47de6b2000fc8a02f84a0c77"
        ]
    }
}

在响应中,Replicator 收到一个文档 ID - 修订映射,但仅针对目标中不存在且需要从源传输的修订。

如果请求中的所有修订都与文档的当前状态匹配,则响应将包含一个空 JSON 对象。

请求

POST /target/_revs_diff HTTP/1.1
Accept: application/json
Content-Length: 160
Content-Type: application/json
Host: localhost:5984
User-Agent: CouchDB

{
    "foo": [
        "3-6a540f3d701ac518d3b9733d673c5484"
    ],
    "bar": [
        "1-967a00dff5e02add41819138abb3284d"
    ]
}

响应:

HTTP/1.1 200 OK
Cache-Control: must-revalidate
Content-Length: 2
Content-Type: application/json
Date: Fri, 25 Oct 2013 14:45:00 GMT
Server: CouchDB (Erlang/OTP)

{}

2.4.2.4.4. 复制完成

当没有更多更改需要处理,也没有更多文档需要复制时,Replicator 完成复制过程。如果复制不是连续的,则 Replicator 可以向客户端返回有关该过程的统计信息的响应。

HTTP/1.1 200 OK
Cache-Control: must-revalidate
Content-Length: 414
Content-Type: application/json
Date: Fri, 09 May 2014 15:14:19 GMT
Server: CouchDB (Erlang OTP)

{
    "history": [
        {
            "doc_write_failures": 2,
            "docs_read": 2,
            "docs_written": 0,
            "end_last_seq": 2939,
            "end_time": "Fri, 09 May 2014 15:14:19 GMT",
            "missing_checked": 1835,
            "missing_found": 2,
            "recorded_seq": 2939,
            "session_id": "05918159f64842f1fe73e9e2157b2112",
            "start_last_seq": 0,
            "start_time": "Fri, 09 May 2014 15:14:18 GMT"
        }
    ],
    "ok": true,
    "replication_id_version": 3,
    "session_id": "05918159f64842f1fe73e9e2157b2112",
    "source_last_seq": 2939
}

2.4.2.5. 复制更改

+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
' Locate Changed Documents:                                                       '
'                                                                                 '
'               +-------------------------------------+                           '
'               |      Any Differences Found?         |                           '
'               +-------------------------------------+                           '
'                                                   |                             '
'                                                   |                             '
'                                                   |                             '
+ - - - - - - - - - - - - - - - - - - - - - - - - - | - - - - - - - - - - - - - - +
                                                    |
+ - - - - - - - - - - - - - - - - - - - - - - - - - | - - - - - - - - - - - - - - +
' Replicate Changes:                                |                             '
'                                                   v                             '
'               +-------------------------------------+                           '
'   +---------> |     Fetch Next Changed Document     | <---------------------+   '
'   |           +-------------------------------------+                       |   '
'   |           |          GET /source/docid          |                       |   '
'   |           +-------------------------------------+                       |   '
'   |             |                                                           |   '
'   |             |                                                           |   '
'   |             |                                          201 Created      |   '
'   |             | 200 OK                                   401 Unauthorized |   '
'   |             |                                          403 Forbidden    |   '
'   |             |                                                           |   '
'   |             v                                                           |   '
'   |           +-------------------------------------+                       |   '
'   |   +------ |  Document Has Changed Attachments?  |                       |   '
'   |   |       +-------------------------------------+                       |   '
'   |   |         |                                                           |   '
'   |   |         |                                                           |   '
'   |   |         | Yes                                                       |   '
'   |   |         |                                                           |   '
'   |   |         v                                                           |   '
'   |   |       +------------------------+   Yes    +---------------------------+ '
'   |   | No    |  Are They Big Enough?  | -------> | Update Document on Target | '
'   |   |       +------------------------+          +---------------------------+ '
'   |   |         |                                 |     PUT /target/docid     | '
'   |   |         |                                 +---------------------------+ '
'   |   |         |                                                               '
'   |   |         | No                                                            '
'   |   |         |                                                               '
'   |   |         v                                                               '
'   |   |       +-------------------------------------+                           '
'   |   +-----> |     Put Document Into the Stack     |                           '
'   |           +-------------------------------------+                           '
'   |             |                                                               '
'   |             |                                                               '
'   |             v                                                               '
'   |     No    +-------------------------------------+                           '
'   +---------- |           Stack is Full?            |                           '
'   |           +-------------------------------------+                           '
'   |             |                                                               '
'   |             | Yes                                                           '
'   |             |                                                               '
'   |             v                                                               '
'   |           +-------------------------------------+                           '
'   |           | Upload Stack of Documents to Target |                           '
'   |           +-------------------------------------+                           '
'   |           |       POST /target/_bulk_docs       |                           '
'   |           +-------------------------------------+                           '
'   |             |                                                               '
'   |             | 201 Created                                                   '
'   |             v                                                               '
'   |           +-------------------------------------+                           '
'   |           |          Ensure in Commit           |                           '
'   |           +-------------------------------------+                           '
'   |           |  POST /target/_ensure_full_commit   |                           '
'   |           +-------------------------------------+                           '
'   |             |                                                               '
'   |             | 201 Created                                                   '
'   |             v                                                               '
'   |           +-------------------------------------+                           '
'   |           |    Record Replication Checkpoint    |                           '
'   |           +-------------------------------------+                           '
'   |           |  PUT /source/_local/replication-id  |                           '
'   |           |  PUT /target/_local/replication-id  |                           '
'   |           +-------------------------------------+                           '
'   |             |                                                               '
'   |             | 201 Created                                                   '
'   |             v                                                               '
'   |     No    +-------------------------------------+                           '
'   +---------- | All Documents from Batch Processed? |                           '
'               +-------------------------------------+                           '
'                                                   |                             '
'                                               Yes |                             '
'                                                   |                             '
+ - - - - - - - - - - - - - - - - - - - - - - - - - | - - - - - - - - - - - - - - +
                                                    |
+ - - - - - - - - - - - - - - - - - - - - - - - - - | - - - - - - - - - - - - - - +
' Locate Changed Documents:                         |                             '
'                                                   v                             '
'               +-------------------------------------+                           '
'               |       Listen to Changes Feed        |                           '
'               +-------------------------------------+                           '
'                                                                                 '
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +

2.4.2.5.1. 获取已更改的文档

在此步骤中,Replicator 必须从源中获取目标中缺少的所有文档叶子修订。如果复制将使用先前计算的修订差异,则此操作将有效,因为它们定义了缺少的文档及其修订。

为了获取文档,Replicator 将使用以下查询参数发出 GET /{db}/{docid} 请求

  • revs=true:指示源将所有已知修订的列表包含在文档的 _revisions 字段中。此信息需要在源和目标之间同步文档的祖先历史记录

  • 查询参数 open_revs 包含一个 JSON 数组,其中包含需要获取的叶修订版本列表。如果指定的修订版本存在,则必须返回该修订版本的文档。否则,源必须返回一个包含单个字段 missing 的对象,该字段的值为丢失的修订版本。如果文档包含附件,源必须仅返回自指定修订版本值以来已更改(添加或更新)的附件信息。如果附件被删除,则文档中不应包含它的存根信息。

  • latest=true:确保源将返回最新的文档修订版本,无论在 open_revs 查询参数中指定了哪个修订版本。此参数解决了竞争条件问题,即请求的文档可能在该步骤与处理更改提要上的相关事件之间发生更改。

在响应中,源应返回 multipart/mixed 或改为返回 application/json,除非 Accept 标头指定了不同的 MIME 类型。 multipart/mixed 内容类型允许将响应数据作为流处理,因为可能存在多个文档(每个叶修订版本一个)以及多个附件。这些附件主要是二进制的,JSON 无法处理此类数据,除非作为 base64 编码的字符串,这对于传输和处理操作非常无效。

对于 multipart/mixed 响应,复制器逐个处理多个文档叶修订版本及其附件,作为原始数据,没有任何额外的编码。还有一种协议,可以使数据处理更有效:文档始终在其附件之前,因此复制器无需处理所有数据来映射相关的文档-附件,并且可以将其作为流处理,从而减少内存占用。

请求:

GET /source/SpaghettiWithMeatballs?revs=true&open_revs=[%225-00ecbbc%22,%221-917fa23%22,%223-6bcedf1%22]&latest=true HTTP/1.1
Accept: multipart/mixed
Host: localhost:5984
User-Agent: CouchDB

响应:

HTTP/1.1 200 OK
Content-Type: multipart/mixed; boundary="7b1596fc4940bc1be725ad67f11ec1c4"
Date: Thu, 07 Nov 2013 15:10:16 GMT
Server: CouchDB (Erlang OTP)
Transfer-Encoding: chunked

--7b1596fc4940bc1be725ad67f11ec1c4
Content-Type: application/json

{
    "_id": "SpaghettiWithMeatballs",
    "_rev": "1-917fa23",
    "_revisions": {
        "ids": [
            "917fa23"
        ],
        "start": 1
    },
    "description": "An Italian-American delicious dish",
    "ingredients": [
        "spaghetti",
        "tomato sauce",
        "meatballs"
    ],
    "name": "Spaghetti with meatballs"
}
--7b1596fc4940bc1be725ad67f11ec1c4
Content-Type: multipart/related; boundary="a81a77b0ca68389dda3243a43ca946f2"

--a81a77b0ca68389dda3243a43ca946f2
Content-Type: application/json

{
    "_attachments": {
      "recipe.txt": {
          "content_type": "text/plain",
          "digest": "md5-R5CrCb6fX10Y46AqtNn0oQ==",
          "follows": true,
          "length": 87,
          "revpos": 7
      }
    },
    "_id": "SpaghettiWithMeatballs",
    "_rev": "7-474f12e",
    "_revisions": {
        "ids": [
            "474f12e",
            "5949cfc",
            "00ecbbc",
            "fc997b6",
            "3552c87",
            "404838b",
            "5defd9d",
            "dc1e4be"
        ],
        "start": 7
    },
    "description": "An Italian-American delicious dish",
    "ingredients": [
        "spaghetti",
        "tomato sauce",
        "meatballs",
        "love"
    ],
    "name": "Spaghetti with meatballs"
}
--a81a77b0ca68389dda3243a43ca946f2
Content-Disposition: attachment; filename="recipe.txt"
Content-Type: text/plain
Content-Length: 87

1. Cook spaghetti
2. Cook meetballs
3. Mix them
4. Add tomato sauce
5. ...
6. PROFIT!

--a81a77b0ca68389dda3243a43ca946f2--
--7b1596fc4940bc1be725ad67f11ec1c4
Content-Type: application/json; error="true"

{"missing":"3-6bcedf1"}
--7b1596fc4940bc1be725ad67f11ec1c4--

收到响应后,复制器将所有接收到的数据放入本地堆栈中,以便进一步批量上传以有效地利用网络带宽。本地堆栈大小可以根据处理的 JSON 数据的文档数量或字节数来限制。当堆栈已满时,复制器会以批量模式将所有处理过的文档上传到目标。虽然强烈建议使用批量操作,但在某些情况下,复制器可能会逐个将文档上传到目标。

注意

替代复制器实现可以使用替代方法从源检索文档。例如,PouchDB 不使用 Multipart API,只获取带有内联附件的最新文档修订版本作为单个 JSON 对象。虽然这仍然是有效的 CouchDB HTTP API 使用方式,但此类解决方案可能需要针对非 CouchDB 对等方使用不同的 API 实现。

2.4.2.5.2. 上传已更改文档的批次

为了将多个文档一次性上传,复制器会向目标发送一个 POST /{db}/_bulk_docs 请求,其有效负载包含一个 JSON 对象,该对象包含以下必填字段。

  • docs对象数组):要更新到目标的文档对象列表。这些文档必须包含 _revisions 字段,该字段保存完整的修订版本历史记录列表,以使目标能够创建正确保留祖先关系的叶修订版本。

  • new_edits布尔值):特殊标志,指示目标按原样存储具有指定修订版本(字段 _rev)值的文档,而不会生成新的修订版本。始终为 false

该请求还可能包含 X-Couch-Full-Commit,用于控制 CouchDB <3.0 在启用延迟提交时的行为。其他对等方可能会忽略此标头,或使用它来控制类似的本地功能。

请求:

POST /target/_bulk_docs HTTP/1.1
Accept: application/json
Content-Length: 826
Content-Type:application/json
Host: localhost:5984
User-Agent: CouchDB
X-Couch-Full-Commit: false

{
    "docs": [
        {
            "_id": "SpaghettiWithMeatballs",
            "_rev": "1-917fa2381192822767f010b95b45325b",
            "_revisions": {
                "ids": [
                    "917fa2381192822767f010b95b45325b"
                ],
                "start": 1
            },
            "description": "An Italian-American delicious dish",
            "ingredients": [
                "spaghetti",
                "tomato sauce",
                "meatballs"
            ],
            "name": "Spaghetti with meatballs"
        },
        {
            "_id": "LambStew",
            "_rev": "1-34c318924a8f327223eed702ddfdc66d",
            "_revisions": {
                "ids": [
                    "34c318924a8f327223eed702ddfdc66d"
                ],
                "start": 1
            },
            "servings": 6,
            "subtitle": "Delicious with scone topping",
            "title": "Lamb Stew"
        },
        {
            "_id": "FishStew",
            "_rev": "1-9c65296036141e575d32ba9c034dd3ee",
            "_revisions": {
                "ids": [
                    "9c65296036141e575d32ba9c034dd3ee"
                ],
                "start": 1
            },
            "servings": 4,
            "subtitle": "Delicious with fresh bread",
            "title": "Fish Stew"
        }
    ],
    "new_edits": false
}

在响应中,目标必须返回一个 JSON 数组,其中包含文档更新状态列表。如果文档已成功存储,则列表项必须包含字段 ok,其值为 true。否则,它必须包含 errorreason 字段,其中包含错误类型和对人类友好的原因描述。

文档更新失败不是致命错误,因为目标可能会出于自身原因拒绝更新。建议使用错误类型 forbidden 来拒绝更新,但也可以使用其他错误类型(例如无效字段名称等)。复制器不应重试上传被拒绝的文档,除非有充分的理由这样做(例如,为此存在特殊的错误类型)。

请注意,虽然响应中可能有一个文档的更新失败,但目标仍然可以返回 201 Created 响应。如果所有上传的文档的所有更新都失败,情况也是如此。

响应:

HTTP/1.1 201 Created
Cache-Control: must-revalidate
Content-Length: 246
Content-Type: application/json
Date: Sun, 10 Nov 2013 19:02:26 GMT
Server: CouchDB (Erlang/OTP)

[
    {
        "ok": true,
        "id": "SpaghettiWithMeatballs",
        "rev":" 1-917fa2381192822767f010b95b45325b"
    },
    {
        "ok": true,
        "id": "FishStew",
        "rev": "1-9c65296036141e575d32ba9c034dd3ee"
    },
    {
        "error": "forbidden",
        "id": "LambStew",
        "reason": "sorry",
        "rev": "1-34c318924a8f327223eed702ddfdc66d"
    }
]

2.4.2.5.3. 上传带有附件的文档

当复制器不会使用已更改文档的批量上传时,存在一个特殊的优化情况。当文档包含大量附件文件或文件太大而无法有效地使用 Base64 编码时,将应用此情况。

对于这种情况,复制器会发出一个 /{db}/{docid}?new_edits=false 请求,其内容类型为 multipart/related。此类请求允许人们轻松地逐个流式传输文档及其所有附件,而无需任何序列化开销。

请求:

PUT /target/SpaghettiWithMeatballs?new_edits=false HTTP/1.1
Accept: application/json
Content-Length: 1030
Content-Type: multipart/related; boundary="864d690aeb91f25d469dec6851fb57f2"
Host: localhost:5984
User-Agent: CouchDB

--2fa48cba80d0cdba7829931fe8acce9d
Content-Type: application/json

{
    "_attachments": {
        "recipe.txt": {
            "content_type": "text/plain",
            "digest": "md5-R5CrCb6fX10Y46AqtNn0oQ==",
            "follows": true,
            "length": 87,
            "revpos": 7
        }
    },
    "_id": "SpaghettiWithMeatballs",
    "_rev": "7-474f12eb068c717243487a9505f6123b",
    "_revisions": {
        "ids": [
            "474f12eb068c717243487a9505f6123b",
            "5949cfcd437e3ee22d2d98a26d1a83bf",
            "00ecbbc54e2a171156ec345b77dfdf59",
            "fc997b62794a6268f2636a4a176efcd6",
            "3552c87351aadc1e4bea2461a1e8113a",
            "404838bc2862ce76c6ebed046f9eb542",
            "5defd9d813628cea6e98196eb0ee8594"
        ],
        "start": 7
    },
    "description": "An Italian-American delicious dish",
    "ingredients": [
        "spaghetti",
        "tomato sauce",
        "meatballs",
        "love"
    ],
    "name": "Spaghetti with meatballs"
}
--2fa48cba80d0cdba7829931fe8acce9d
Content-Disposition: attachment; filename="recipe.txt"
Content-Type: text/plain
Content-Length: 87

1. Cook spaghetti
2. Cook meetballs
3. Mix them
4. Add tomato sauce
5. ...
6. PROFIT!

--2fa48cba80d0cdba7829931fe8acce9d--

响应:

HTTP/1.1 201 Created
Cache-Control: must-revalidate
Content-Length: 105
Content-Type: application/json
Date: Fri, 08 Nov 2013 16:35:27 GMT
Server: CouchDB (Erlang/OTP)

{
    "ok": true,
    "id": "SpaghettiWithMeatballs",
    "rev": "7-474f12eb068c717243487a9505f6123b"
}

与通过 POST /{db}/_bulk_docs 端点进行批量更新不同,响应可能带有不同的状态代码。例如,在文档被拒绝的情况下,目标应返回 403 Forbidden

响应:

HTTP/1.1 403 Forbidden
Cache-Control: must-revalidate
Content-Length: 39
Content-Type: application/json
Date: Fri, 08 Nov 2013 16:35:27 GMT
Server: CouchDB (Erlang/OTP)

{
    "error": "forbidden",
    "reason": "sorry"
}

如果出现 401 Unauthorized403 Forbidden409 Conflict412 Precondition Failed,复制器不应重试请求,因为重复请求无法解决用户凭据或上传数据的问题。

2.4.2.5.4. 确保提交

一旦成功将一批更改上传到目标,复制器就会发出一个 POST /{db}/_ensure_full_commit 请求,以确保每个传输的位都已写入磁盘或其他持久存储位置。目标必须返回 201 Created 响应,其中包含一个 JSON 对象,该对象包含以下必填字段。

  • instance_start_time字符串):数据库打开时的时间戳,以自纪元以来的微秒表示。

  • ok布尔值):操作状态。始终为 true

    请求:

    POST /target/_ensure_full_commit HTTP/1.1
    Accept: application/json
    Content-Type: application/json
    Host: localhost:5984
    

    响应:

    HTTP/1.1 201 Created
    Cache-Control: must-revalidate
    Content-Length: 53
    Content-Type: application/json
    Date: Web, 06 Nov 2013 18:20:43 GMT
    Server: CouchDB (Erlang/OTP)
    
    {
        "instance_start_time": "0",
        "ok": true
    }
    

2.4.2.5.5. 记录复制检查点

由于成功上传并提交了更改批次,因此复制器会在源和目标上更新复制日志,记录当前的复制状态。此操作是必需的,以便在复制失败的情况下,复制可以从上次成功点恢复,而不是从一开始恢复。

复制器更新源上的复制日志。

请求:

PUT /source/_local/afa899a9e59589c3d4ce5668e3218aef HTTP/1.1
Accept: application/json
Content-Length: 591
Content-Type: application/json
Host: localhost:5984
User-Agent: CouchDB

{
    "_id": "_local/afa899a9e59589c3d4ce5668e3218aef",
    "_rev": "0-1",
    "_revisions": {
        "ids": [
            "31f36e40158e717fbe9842e227b389df"
        ],
        "start": 1
    },
    "history": [
        {
            "doc_write_failures": 0,
            "docs_read": 6,
            "docs_written": 6,
            "end_last_seq": 26,
            "end_time": "Thu, 07 Nov 2013 09:42:17 GMT",
            "missing_checked": 6,
            "missing_found": 6,
            "recorded_seq": 26,
            "session_id": "04bf15bf1d9fa8ac1abc67d0c3e04f07",
            "start_last_seq": 0,
            "start_time": "Thu, 07 Nov 2013 09:41:43 GMT"
        }
    ],
    "replication_id_version": 3,
    "session_id": "04bf15bf1d9fa8ac1abc67d0c3e04f07",
    "source_last_seq": 26
}

响应:

HTTP/1.1 201 Created
Cache-Control: must-revalidate
Content-Length: 75
Content-Type: application/json
Date: Thu, 07 Nov 2013 09:42:17 GMT
Server: CouchDB (Erlang/OTP)

{
    "id": "_local/afa899a9e59589c3d4ce5668e3218aef",
    "ok": true,
    "rev": "0-2"
}

…以及目标上的复制日志。

请求:

PUT /target/_local/afa899a9e59589c3d4ce5668e3218aef HTTP/1.1
Accept: application/json
Content-Length: 591
Content-Type: application/json
Host: localhost:5984
User-Agent: CouchDB

{
    "_id": "_local/afa899a9e59589c3d4ce5668e3218aef",
    "_rev": "1-31f36e40158e717fbe9842e227b389df",
    "_revisions": {
        "ids": [
            "31f36e40158e717fbe9842e227b389df"
        ],
        "start": 1
    },
    "history": [
        {
            "doc_write_failures": 0,
            "docs_read": 6,
            "docs_written": 6,
            "end_last_seq": 26,
            "end_time": "Thu, 07 Nov 2013 09:42:17 GMT",
            "missing_checked": 6,
            "missing_found": 6,
            "recorded_seq": 26,
            "session_id": "04bf15bf1d9fa8ac1abc67d0c3e04f07",
            "start_last_seq": 0,
            "start_time": "Thu, 07 Nov 2013 09:41:43 GMT"
        }
    ],
    "replication_id_version": 3,
    "session_id": "04bf15bf1d9fa8ac1abc67d0c3e04f07",
    "source_last_seq": 26
}

响应:

HTTP/1.1 201 Created
Cache-Control: must-revalidate
Content-Length: 106
Content-Type: application/json
Date: Thu, 07 Nov 2013 09:42:17 GMT
Server: CouchDB (Erlang/OTP)

{
    "id": "_local/afa899a9e59589c3d4ce5668e3218aef",
    "ok": true,
    "rev": "2-9b5d1e36bed6ae08611466e30af1259a"
}

2.4.2.6. 继续读取更改

一旦成功处理并传输了一批更改到目标,复制器就可以继续监听更改提要以获取新的更改。如果没有新的更改要处理,则复制被认为已完成。

对于连续复制,复制器必须继续等待源的新的更改。

2.4.3. 协议健壮性

由于CouchDB 复制协议在 HTTP 之上运行,而 HTTP 基于 TCP/IP,因此复制器应该预期在不稳定的环境中工作,该环境可能会出现延迟、丢失和其他意外情况。复制器不应将每个 HTTP 请求失败视为致命错误。它应该足够智能,能够检测超时、重复失败的请求、准备处理不完整或格式错误的数据等等。数据必须流动 - 这是规则。

2.4.4. 错误响应

如果出现问题,对等方必须返回一个 JSON 对象,该对象包含以下必需字段。

  • error字符串):用于程序和开发人员的错误类型。

  • reason字符串):用于人类的错误描述。

2.4.4.1. 错误请求

如果请求包含格式错误的数据(例如无效的 JSON),则对等方必须返回 HTTP 400 Bad Request 以及错误类型 bad_request

{
    "error": "bad_request",
    "reason": "invalid json"
}

2.4.4.2. 未经授权

如果对等体要求在请求中包含凭据,并且请求中不包含可接受的凭据,则对等体必须使用 HTTP 401 未授权 响应,并将 unauthorized 作为错误类型。

{
    "error": "unauthorized",
    "reason": "Name or password is incorrect"
}

2.4.4.3. 禁止

如果对等体收到有效的用户凭据,但请求者没有足够的权限执行操作,则对等体必须使用 HTTP 403 禁止 响应,并将 forbidden 作为错误类型。

{
    "error": "forbidden",
    "reason": "You may only update your own user document."
}

2.4.4.4. 资源未找到

如果在对等体上找不到请求的资源、数据库或文档,则对等体必须使用 HTTP 404 未找到 响应,并将 not_found 作为错误类型。

{
    "error": "not_found",
    "reason": "database \"target\" does not exists"
}

2.4.4.5. 方法不允许

如果使用了不支持的方法,则对等体必须使用 HTTP 405 方法不允许 响应,并将 method_not_allowed 作为错误类型。

{
    "error": "method_not_allowed",
    "reason": "Only GET, PUT, DELETE allowed"
}

2.4.4.6. 资源冲突

当多个客户端同时更新同一资源时,就会发生资源冲突错误。在这种情况下,对等体必须使用 HTTP 409 冲突 响应,并将 conflict 作为错误类型。

{
    "error": "conflict",
    "reason": "document update conflict"
}

2.4.4.7. 前提条件失败

如果尝试创建已经存在的数据库(错误类型 db_exists)或缺少某些附件信息(错误类型 missing_stub),则可能会发送 HTTP 412 前提条件失败 响应。没有明确的错误类型限制,但建议使用之前提到的错误类型。

{
    "error": "db_exists",
    "reason": "database \"target\" exists"
}

2.4.4.8. 服务器错误

如果错误是致命的,并且复制器无法执行任何操作来继续复制,则会引发此错误。在这种情况下,复制器必须返回 HTTP 500 内部服务器错误 响应,并附带错误描述(对错误类型没有限制)。

{
    "error": "worker_died",
    "reason": "kaboom!"
}

2.4.5. 优化

有一些建议的方法可以优化复制过程。

  • 将 HTTP 请求的数量保持在合理的最低限度。

  • 尝试使用连接池,并在可能的情况下进行并行/多个请求。

  • 不要在每次请求后关闭套接字:尊重 keep-alive 选项。

  • 使用持续会话(cookie 等)来减少身份验证开销。

  • 尝试对所有文档操作使用批量请求。

  • 找出更改提要处理的最佳批次大小。

  • 保留复制日志,并在可能的情况下从上次检查点恢复复制。

  • 优化过滤器函数:让它们尽可能快地运行。

  • 为意外情况做好准备:网络环境非常不稳定。

2.4.6. API 参考

2.4.6.1. 常用方法

2.4.6.2. 针对目标

2.4.6.3. 针对源

2.4.7. 参考