2.2. 复制器数据库

在 2.1.0 版更改: 介绍了调度复制器。默认情况下,复制状态不再写入文档。有新的复制作业状态和新的API端点 _scheduler/jobs_scheduler/docs .

在 3.2.0 版更改: 引入公平份额调度。多个 _replicator 数据库运行其作业的机会相等(可配置)。以前,计划复制作业时不考虑其原始数据库。

这个 _replicator 数据库的工作方式与CouchDB中的其他数据库类似,但添加到其中的文档将触发复制。创建 (PUTPOST )开始复制的文档。 DELETE 用于取消正在进行的复制的复制文档。

这些文档的内容与我们过去使用的JSON对象完全相同 POST_replicate (字段 sourcetargetcreate_targetcreate_target_paramscontinuousdoc_idsfilterquery_paramsuse_checkpointscheckpoint_interval )。

复制文档可以具有用户定义的 _id (方便以后查找特定的复制请求)。设计文件(和 _local 添加到replicator数据库的文档)将被忽略。

默认的replicator数据库是 _replicator . 可以创建其他replicator数据库。为了被系统识别,它们的数据库名称应该以 /_replicator .

2.2.1. 基础

假设您将以下文档发布到 _replicator

{
    "_id": "my_rep",
    "source": "http://myserver.com/foo",
    "target": {
        "url": "http://localhost:5984/bar",
        "auth": {
            "basic": {
                "username": "user",
                "password": "pass"
            }
        }
    },
    "create_target":  true,
    "continuous": true
}

在沙发日志中,您将看到以下两个条目:

[notice] 2017-04-05T17:16:19.646716Z node1@127.0.0.1 <0.29432.0> -------- Replication `"a81a78e822837e66df423d54279c15fe+continuous+create_target"` is using:
    4 worker processes
    a worker batch size of 500
    20 HTTP connections
    a connection timeout of 30000 milliseconds
    10 retries per request
    socket options are: [{keepalive,true},{nodelay,false}]
[notice] 2017-04-05T17:16:19.646759Z node1@127.0.0.1 <0.29432.0> -------- Document `my_rep` triggered replication `a81a78e822837e66df423d54279c15fe+continuous+create_target`

然后可以从中查询此文档的复制状态 http://adm:pass@localhost:5984/_scheduler/docs/_replicator/my_rep

{
    "database": "_replicator",
    "doc_id": "my_rep",
    "error_count": 0,
    "id": "a81a78e822837e66df423d54279c15fe+continuous+create_target",
    "info": {
        "revisions_checked": 113,
        "missing_revisions_found": 113,
        "docs_read": 113,
        "docs_written": 113,
        "changes_pending": 0,
        "doc_write_failures": 0,
        "checkpointed_source_seq": "113-g1AAAACTeJzLYWBgYMpgTmHgz8tPSTV0MDQy1zMAQsMckEQiQ1L9____szKYE01ygQLsZsYGqcamiZjKcRqRxwIkGRqA1H-oSbZgk1KMLCzTDE0wdWUBAF6HJIQ",
        "source_seq": "113-g1AAAACTeJzLYWBgYMpgTmHgz8tPSTV0MDQy1zMAQsMckEQiQ1L9____szKYE01ygQLsZsYGqcamiZjKcRqRxwIkGRqA1H-oSbZgk1KMLCzTDE0wdWUBAF6HJIQ",
        "through_seq": "113-g1AAAACTeJzLYWBgYMpgTmHgz8tPSTV0MDQy1zMAQsMckEQiQ1L9____szKYE01ygQLsZsYGqcamiZjKcRqRxwIkGRqA1H-oSbZgk1KMLCzTDE0wdWUBAF6HJIQ"
    },
    "last_updated": "2017-04-05T19:18:15Z",
    "node": "node1@127.0.0.1",
    "source_proxy": null,
    "target_proxy": null,
    "source": "http://myserver.com/foo/",
    "start_time": "2017-04-05T19:18:15Z",
    "state": "running",
    "target": "http://localhost:5984/bar/"
}

国家是 running . 这意味着replicator已计划运行此复制作业。复制文档内容保持不变。以前,在2.1版之前,它更新为 triggered 状态。

复制作业也将出现在中

http://adm:pass@localhost:5984/_scheduler/jobs

{
    "jobs": [
        {
            "database": "_replicator",
            "doc_id": "my_rep",
            "history": [
                {
                    "timestamp": "2017-04-05T19:18:15Z",
                    "type": "started"
                },
                {
                    "timestamp": "2017-04-05T19:18:15Z",
                    "type": "added"
                }
            ],
            "id": "a81a78e822837e66df423d54279c15fe+continuous+create_target",
            "info": {
                "changes_pending": 0,
                "checkpointed_source_seq": "113-g1AAAACTeJzLYWBgYMpgTmHgz8tPSTV0MDQy1zMAQsMckEQiQ1L9____szKYE01ygQLsZsYGqcamiZjKcRqRxwIkGRqA1H-oSbZgk1KMLCzTDE0wdWUBAF6HJIQ",
                "doc_write_failures": 0,
                "docs_read": 113,
                "docs_written": 113,
                "missing_revisions_found": 113,
                "revisions_checked": 113,
                "source_seq": "113-g1AAAACTeJzLYWBgYMpgTmHgz8tPSTV0MDQy1zMAQsMckEQiQ1L9____szKYE01ygQLsZsYGqcamiZjKcRqRxwIkGRqA1H-oSbZgk1KMLCzTDE0wdWUBAF6HJIQ",
                "through_seq": "113-g1AAAACTeJzLYWBgYMpgTmHgz8tPSTV0MDQy1zMAQsMckEQiQ1L9____szKYE01ygQLsZsYGqcamiZjKcRqRxwIkGRqA1H-oSbZgk1KMLCzTDE0wdWUBAF6HJIQ"
            },
            "node": "node1@127.0.0.1",
            "pid": "<0.1174.0>",
            "source": "http://myserver.com/foo/",
            "start_time": "2017-04-05T19:18:15Z",
            "target": "http://localhost:5984/bar/",
            "user": null
        }
    ],
    "offset": 0,
    "total_rows": 1
}

_scheduler/jobs 显示更多信息,例如状态更改的详细历史记录。如果持久性复制尚未启动、已失败或已完成,则只能在中找到有关其状态的信息 _scheduler/docs . 请记住,有些复制文档可能无效,无法成为复制作业。其他一些可能会被延迟,因为它们从缓慢的源数据库中获取数据。

如果出现错误,例如源数据库丢失,复制作业将崩溃,并在等待一段时间后重试。每次连续的碰撞都会导致更长的等待时间。

例如,发布此文档

{
    "_id": "my_rep_crashing",
    "source": "http://myserver.com/missing",
    "target": {
        "url": "http://localhost:5984/bar",
        "auth": {
            "basic": {
                "username": "user",
                "password": "pass"
            }
        }
    },
    "create_target":  true,
    "continuous": true
}

当源数据库丢失时,将导致周期性的启动和崩溃,时间间隔越来越大。这个 history 列表来自 _scheduler/jobs 对于此复制,将如下所示:

[
      {
          "reason": "db_not_found: could not open http://adm:*****@localhost:5984/missing/",
          "timestamp": "2017-04-05T20:55:10Z",
          "type": "crashed"
      },
      {
          "timestamp": "2017-04-05T20:55:10Z",
          "type": "started"
      },
      {
          "reason": "db_not_found: could not open http://adm:*****@localhost:5984/missing/",
          "timestamp": "2017-04-05T20:47:10Z",
          "type": "crashed"
      },
      {
          "timestamp": "2017-04-05T20:47:10Z",
          "type": "started"
      }
]

_scheduler/docs 显示较短的摘要:

{
      "database": "_replicator",
      "doc_id": "my_rep_crashing",
      "error_count": 6,
      "id": "cb78391640ed34e9578e638d9bb00e44+create_target",
      "info": {
           "error": "db_not_found: could not open http://myserver.com/missing/"
      },
      "last_updated": "2017-04-05T20:55:10Z",
      "node": "node1@127.0.0.1",
      "source_proxy": null,
      "target_proxy": null,
      "source": "http://myserver.com/missing/",
      "start_time": "2017-04-05T20:38:34Z",
      "state": "crashing",
      "target": "http://localhost:5984/bar/"
}

重复的碰撞被描述为 crashing 状态。 -ing 后缀表示这是一个临时状态。用户可以随时创建丢失的数据库,然后复制作业可以恢复正常。

2.2.2. 描述相同复制的文档

假设有2个文档添加到 _replicator 数据库顺序如下:

{
    "_id": "my_rep",
    "source": "http://myserver.com/foo",
    "target":  "http://user:pass@localhost:5984/bar",
    "create_target":  true,
    "continuous": true
}

{
    "_id": "my_rep_dup",
    "source": "http://myserver.com/foo",
    "target":  "http://user:pass@localhost:5984/bar",
    "create_target":  true,
    "continuous": true
}

两者描述了完全相同的复制(只有 _ids 不同)。在这种情况下,文件 my_rep 触发复制,而 my_rep_dup 会失败的。检查 ``_scheduler/docs` 准确解释了失败的原因:

{
    "database": "_replicator",
    "doc_id": "my_rep_dup",
    "error_count": 1,
    "id": null,
    "info": {
        "error": "Replication `a81a78e822837e66df423d54279c15fe+continuous+create_target` specified by document `my_rep_dup` already started, triggered by document `my_rep` from db `_replicator`"
    },
    "last_updated": "2017-04-05T21:41:51Z",
    "source": "http://myserver.com/foo/",
    "start_time": "2017-04-05T21:41:51Z",
    "state": "failed",
    "target": "http://user:****@localhost:5984/bar",
}

请注意,此复制的状态为 failed .不像 crashingfailed 状态是终端。只要两个文档都存在,复制器就不会重试运行 my_rep_dup 复制。另一个原因可能是文档格式错误。例如,如果工作进程计数被指定为字符串 ("worker_processes": "a few" )将发生失败,而不是整数。

2.2.3. 复制计划程序

一旦创建了复制作业,它们就由调度程序管理。而另一些则是定时启动复制的组件。这种行为使得有可能拥有比集群同时运行更多的作业。不断失败的复制作业将受到惩罚并被迫等待。等待时间随着每次连续故障呈指数增长。

在决定停止哪些作业和启动哪些作业时,调度器使用循环算法来确保公平性。运行时间最长的作业将停止,等待时间最长的作业将启动。

注解

非连续(正常)复制在开始运行后会受到不同的处理。看到了吗 正常复制与连续复制 有关详细信息,请参阅。

调度程序的行为可以通过 max_jobsintervalmax_churn 选项。见 Replicator configuration section 更多信息。

2.2.4. 复制状态

复制作业在其生命周期中经历各种状态。这是所有状态和它们之间的转换的图表:

Replication state diagram

复制状态图

蓝色和黄色形状表示复制作业状态。

梯形表示外部API,这就是用户与复制器交互的方式。将文档写入 _replicator 是创建复制的首选方法,但将 _replicate 也支持HTTP端点。

六边形是内部API边界。对于这个图来说,它们是可选的,只是作为附加信息显示,以帮助阐明复制器是如何工作的。有两个处理阶段:第一个阶段是解析复制文档并成为复制作业,第二个阶段是调度程序本身。调度程序运行复制作业,定期停止和启动一些复制作业。通过 _replicate 端点绕过第一个组件,直接转到调度程序。

2.2.4.1. 状态描述

在解释每个状态的细节之前,值得注意的是,图中每个状态的颜色和形状:

Blue VS yellow 将状态分别划分为“健康”和“不健康”。不健康状态表示出了问题,可能需要用户注意。

Rectangle VS oval 将“终端”状态与“非终端”状态分开。终端状态是那些不再过渡到其他状态的状态。非正式地说,终端状态下的作业不会被重试,也不会消耗内存或CPU资源。

  • Initializing :表示复制程序已注意到复制文档中的更改。工作应该在这种状态下迅速过渡。被困在这里一段时间可能意味着有内部错误。
  • Failed :无法处理复制文档并将其转换为计划程序的有效复制作业。此状态是终端状态,需要用户干预来解决问题。最终处于这种状态的一个典型原因是文档格式不正确。例如,为接受布尔值的参数指定整数。失败的另一个原因可能是指定了重复复制。重复复制是具有相同参数但具有不同文档ID的复制。
  • Error :无法将复制文档更新转换为复制作业。不像 Failed 状态,这是临时的,复制器会定期重试。在连续故障的情况下应用指数退避。此状态存在的主要原因是使用自定义用户函数处理筛选后的复制。需要筛选器函数内容才能计算复制ID。在检索到函数代码之前,无法创建复制作业。因为检索是通过网络进行的,所以必须处理临时故障。
  • Running :复制作业正在正常运行。这意味着,可能有一个变更提要打开,如果发现变更,它们将被处理并发布到目标。工作仍在考虑中 Running 即使它的工作人员当前没有将更改从源传输到目标,而只是在等待更改提要。连续复制很可能会以这种状态结束。
  • Pending :复制作业未运行,正在等待轮到它。当添加到计划程序的作业数超过此状态时,达到该状态 replicator.max_jobs . 在这种情况下,调度程序将周期性地停止和启动作业子集,以使每个作业都有公平的机会取得进展。
  • Crashing :复制作业已成功添加到复制计划程序。但是,在上次运行期间遇到错误。错误可能是网络故障、源数据库丢失、权限错误等。重复的连续崩溃会导致指数级后退。此状态被视为临时(非终端),将定期重试复制作业。最大退避间隔大约为一天左右。
  • Completed :这是非连续复制的终端成功状态。一旦处于这种状态,调度程序就会“忘记”复制,并且不会消耗更多的CPU或内存资源。连续复制作业永远不会达到此状态。

2.2.4.2. 正常复制与连续复制

正常(非连续)复制一旦启动将允许运行到完成。这种行为是为了保留将源数据库的快照复制到目标数据库的语义。例如,如果在复制启动后将新文档添加到源中,则目标数据库上不应显示这些更新。停止和限制正常复制将违反该约束。

警告

当连续复制和正常复制混合使用时,一旦计划正常复制运行,它们可能会暂时缺少连续复制作业。

但是,如果操作员减少最大复制数的值,则正常复制仍将停止并重新安排。这样,如果操作员认为复制会压倒节点,那么它就有能力恢复。任何停止的复制都将重新提交到要重新安排的队列中。

2.2.5. 兼容模式

以前版本的CouchDB replicator将状态更新写回复制文档。在用户代码以编程方式读取这些状态的情况下,将通过配置设置启用兼容性模式:

[replicator]
update_docs = true

在此模式下,replicator将继续向文档写入状态更新。

要有效地禁用调度行为(即定期停止和启动作业),请设置 max_jobs 配置设置为大数值。例如::

[replicator]
max_jobs = 9999999

Replicator configuration section 对于其他replicator配置选项。

2.2.6. 取消复制

简单地取消复制 DELETE 触发复制的文档。例如,要更新复制,请更改工作线程或源的数量,只需使用新数据更新文档。如果复制文档中有额外的特定于应用程序的数据,则复制程序将忽略该数据。

2.2.7. 服务器重新启动

当CouchDB重新启动时,它会检查 _replicator 数据库,并重新启动文档描述的复制(如果它们不在中) completedfailed 国家。如果是,则忽略它们。

2.2.8. 聚类

在群集中,复制作业在所有节点节点之间平均平衡,这样一个复制作业一次只能在一个节点上运行。

当一个新的复制程序在集群中重新启动时,会注意到当一个新的复制节点在集群中被重新添加或重新启动时,所有的复制程序都会重新启动。此机制还提供了在节点发生故障时进行复制故障转移的功能。从复制文档启动的复制作业(但不是从 _replicate HTTP端点)将自动迁移其中一个活动节点。

2.2.9. 其他Replicator数据库

想象一下复制器数据库 (_replicator )有这两个文档,它们表示从服务器A和B进行拉取复制:

{
    "_id": "rep_from_A",
    "source":  "http://aserver.com:5984/foo",
    "target": {
        "url": "http://localhost:5984/foo_a",
        "auth": {
            "basic": {
                "username": "user",
                "password": "pass"
            }
        }
    },
    "continuous":  true
}
{
    "_id": "rep_from_B",
    "source":  "http://bserver.com:5984/foo",
    "target": {
        "url": "http://localhost:5984/foo_b",
        "auth": {
            "basic": {
                "username": "user",
                "password": "pass"
            }
        }
    },
    "continuous":  true
}

现在不需要停止和重新启动CouchDB,就可以添加另一个replicator数据库。例如 another/_replicator

$ curl -X PUT http://user:pass@localhost:5984/another%2F_replicator/
{"ok":true}

注解

在URL中使用数据库名称中的/字符时,应转义。

然后将复制文档添加到新的replicator数据库:

{
    "_id": "rep_from_X",
    "source":  "http://xserver.com:5984/foo",
    "target":  "http://user:pass@localhost:5984/foo_x",
    "continuous":  true
}

从现在起,系统中有三个复制活动:两个来自A和B的复制,一个来自X的新复制。

然后删除其他replicator数据库:

$ curl -X DELETE http://user:pass@localhost:5984/another%2F_replicator/
{"ok":true}

此操作之后,将停止从服务器X拉取复制,并且 _replicator 数据库(从服务器A和B拉取)将继续。

2.2.10. 公平分配作业调度

当多个 _replicator 使用数据库,并且任何节点上的作业总数都大于 max_jobs ,将安排复制作业,以便每个 _replicator 默认情况下,数据库运行其作业的机会相等。

这是通过给每个人分配一定数量的“份额”来实现的 _replicator 数据库,然后自动调整正在运行的作业的比例,以匹配每个数据库的共享比例。默认情况下,每个 _replicator 为数据库分配了100个共享。可以更改每个人的股票分配 _replicator 数据库中的 [replicator.shares] 配置节。

用一组例子可能更容易描述公平分享行为。每个示例都假定缺省值为 max_jobs = 500 和两个Replicator数据库: _replicatoranother/_replicator

示例1:如果 _replicator 有1000个工作岗位, another/_replicator 具有10个作业,则调度程序将从以下位置运行约490个作业 _replicator 和10个工作岗位,来自 another/_replicator

示例2:如果 _replicator 有200个工作岗位, another/_replicator 也有200个作业,所有400个作业将可以运行,因为所有作业的总和小于 max_jobs 限制。

示例3:如果两个复制器数据库每个都有1000个作业,调度程序将平均从每个数据库运行大约250个作业。

示例4:如果两个复制器数据库各有1000个作业,但是 _replicator 分配了400个共享,则调度程序平均将从 _replicator 和100个工作岗位,来自 _another/replicator

示例中描述的比例是近似值,可能会有一些波动,也可能需要几十分钟到一小时的时间才能收敛。

2.2.11. 复制复制器数据库

假设在server C中有一个replicator数据库,其中包含以下两个拉式复制文档:

{
     "_id": "rep_from_A",
     "source":  "http://aserver.com:5984/foo",
     "target":  "http://user:pass@localhost:5984/foo_a",
     "continuous":  true
}
{
     "_id": "rep_from_B",
     "source":  "http://bserver.com:5984/foo",
     "target":  "http://user:pass@localhost:5984/foo_b",
     "continuous":  true
}

现在,您希望在服务器D中进行相同的拉取复制,也就是说,您希望服务器D从服务器A和B进行拉复制。您有两个选项:

  • 显式地将两个文档添加到服务器的D replicator数据库中
  • 将服务器的C replicator数据库复制到服务器的D replicator数据库中

两种选择都能达到完全相同的目标。

2.2.12. 代表团

复制文档可以有一个自定义 user_ctx 财产。此属性定义运行复制的用户上下文。对于触发复制的旧方法(发布到 /_replicate/ ),不需要此属性。这是因为关于经过身份验证的用户的信息在复制过程中是随时可用的,在这种情况下,这不是持久的。现在,对于replicator数据库,问题是只有在编写复制文档时才会显示有关哪个用户正在启动特定复制的信息。但是,复制文档和复制本身中的信息是持久的。这个实现细节意味着,对于非管理员用户,一个 user_ctx 必须在复制文档中定义包含用户名及其角色子集的属性。这是由replicator数据库的默认设计文档中的文档更新验证功能实现的。验证功能还确保非管理员用户无法设置用户上下文的值 name 属性设置为除自己的用户名之外的任何内容。同样的原则也适用于角色。

对于管理员 user_ctx 属性是可选的,如果缺少该属性,则默认为具有名称的用户上下文 null 以及一个空的角色列表,这意味着设计文档不会写入本地目标。如果需要将设计文档写入本地目标,则角色 _admin 必须出现在用户上下文的角色列表中。

另外,对于管理员 user_ctx 属性可用于代表其他用户触发复制。这是将传递给本地目标数据库文档验证函数的用户上下文。

注解

这个 user_ctx 属性仅对本地终结点有效。

委托复制文档示例:

{
    "_id": "my_rep",
    "source":  "http://bserver.com:5984/foo",
    "target":  "http://user:pass@localhost:5984/bar",
    "continuous":  true,
    "user_ctx": {
        "name": "joe",
        "roles": ["erlanger", "researcher"]
    }
}

如前所述 user_ctx 属性对于管理员是可选的,而对于常规(非管理员)用户则是必需的。当角色属性 user_ctx 则默认为空列表 [] .

2.2.13. 选择器对象

在复制文档中包含选择器对象使您能够使用查询表达式来确定是否应在复制中包含文档。

选择器指定文档中的字段,并提供一个表达式以使用字段内容或其他数据进行计算。如果表达式解析为 true ,文档已复制。

选择器对象必须:

  • 结构为有效的JSON。
  • 包含有效的查询表达式。

选择器的语法与 selectorsyntax 用于 _find

使用选择器比使用JavaScript过滤器函数要高效得多,如果只对文档属性进行过滤,则建议使用选择器。

2.2.14. 指定用户名和密码

有多种方式可以为复制端点指定用户名和密码:

  • 在一个 {{"auth": {{"basic": ...}}}} 对象:

    3.2.0 新版功能.

    {
        "target": {
            "url": "http://someurl.com/mydb",
            "auth": {
                "basic": {
                    "username": "$username",
                    "password": "$password"
                 }
            }
        },
        ...
    }
    

    这是首选格式,因为它允许包含如下字符 @: 以及用户名和密码字段中的其他内容。

  • 在端点URL的userinfo部分。这允许更紧凑的端点表示,但是它防止使用像这样的字符 @: 在用户名或密码中:

    {
        "target":  "http://user:pass@localhost:5984/bar"
        ...
    }
    

    不建议在URL的userinfo部分中指定凭据,因为 RFC3986 。CouchDB仍然支持这种指定凭据的方式,并且还没有何时删除支持的目标版本。

  • 在一个 "Authorization: Basic $b64encoded_username_and_password" 标题:

    {
        "target": {
            "url": "http://someurl.com/mydb",
                "headers": {
                    "Authorization": "Basic dXNlcjpwYXNz"
                }
            },
        ...
    }
    

    这种方法的缺点是需要经过额外的Base64编码步骤。此外,它可能会给人一种加密或隐藏凭据的印象,因此可能会鼓励无意中共享和泄露凭据。

当以多种形式提供凭据时,将按以下顺序选择凭据:

  • "auth": {{"basic": {{...}}}} 对象
  • URL用户信息
  • "Authorization: Basic ..." 标题。

首先, auth 对象,如果在那里定义了凭据,则使用这些凭据。如果不是,则检查URL userinfo。如果在那里找到凭据,则使用这些凭据,否则使用基本的auth标头。