高可用性和PyMongo#

PyMongo makes it easy to write highly available applications whether you use a single replica set or a large sharded cluster.

连接到副本集#

PyMongo与 replica sets 容易的。在这里,我们将启动一个新的副本集,并演示如何使用PyMongo处理初始化和正常连接。

参见

上的MongoDB文档 replication

启动副本集#

主要 replica set documentation 包含有关设置新副本集或迁移现有MongoDB设置的详细信息,请务必查看。在这里,我们将只做最小限度的工作,以便在本地建立一个三节点副本集。

警告

副本集应始终在生产中使用多个节点-仅建议在测试和开发时将所有集成员放在同一个物理节点上。

我们从三开始 mongod 进程,每个进程在不同的端口上,使用不同的dbpath,但都使用相同的副本集名称“foo”。

$ mkdir -p /data/db0 /data/db1 /data/db2
$ mongod --port 27017 --dbpath /data/db0 --replSet foo
$ mongod --port 27018 --dbpath /data/db1 --replSet foo
$ mongod --port 27019 --dbpath /data/db2 --replSet foo

初始化集合#

此时,我们的所有节点都已启动并运行,但集合尚未初始化。在初始化集合之前,没有任何节点将成为主节点,并且基本上是“脱机”的。

To initialize the set we need to connect directly to a single node and run the initiate command using the directConnection option:

>>> from pymongo import MongoClient
>>> c = MongoClient('localhost', 27017, directConnection=True)

备注

我们本来可以连接到任何其他节点,但是只有我们启动的节点才允许包含任何初始数据。

连接之后,我们运行initiate命令来启动:

>>> config = {'_id': 'foo', 'members': [
...     {'_id': 0, 'host': 'localhost:27017'},
...     {'_id': 1, 'host': 'localhost:27018'},
...     {'_id': 2, 'host': 'localhost:27019'}]}
>>> c.admin.command("replSetInitiate", config)
{'ok': 1.0, ...}

三个 mongod 我们先前启动的服务器现在将作为副本集进行协调并联机。

连接到副本集#

上述初始连接是未初始化副本集的特殊情况。正常情况下,我们希望以不同的方式连接。与副本集的连接可以使用 MongoClient() 构造函数,指定集的一个或多个成员,并可选地指定副本集名称。以下任一连接到我们刚刚创建的副本集:

>>> MongoClient('localhost')
MongoClient(host=['localhost:27017'], ...)
>>> MongoClient('localhost', replicaset='foo')
MongoClient(host=['localhost:27017'], replicaset='foo', ...)
>>> MongoClient('localhost:27018', replicaset='foo')
MongoClient(['localhost:27018'], replicaset='foo', ...)
>>> MongoClient('localhost', 27019, replicaset='foo')
MongoClient(['localhost:27019'], replicaset='foo', ...)
>>> MongoClient('mongodb://localhost:27017,localhost:27018/')
MongoClient(['localhost:27017', 'localhost:27018'], ...)
>>> MongoClient('mongodb://localhost:27017,localhost:27018/?replicaSet=foo')
MongoClient(['localhost:27017', 'localhost:27018'], replicaset='foo', ...)

传递到的地址 MongoClient() 被称为 种子 . 只要至少有一个种子处于联机状态,MongoClient就会发现副本集中的所有成员,并确定哪个是当前的主成员,哪些是辅助成员或仲裁器。每个种子必须是单个mongod的地址。多宿主和循环DNS地址是 not 支持。

这个 MongoClient 构造函数是非阻塞的:当客户端使用后台线程连接到副本集时,构造函数立即返回。请注意,如果您创建一个客户机并立即打印其 nodes 属性,则列表最初可能为空。如果您稍等片刻,MongoClient会发现整个副本集:

>>> from time import sleep
>>> c = MongoClient(replicaset='foo'); print(c.nodes); sleep(0.1); print(c.nodes)
frozenset([])
frozenset([('localhost', 27019), ('localhost', 27017), ('localhost', 27018)])

但是,您不必等待应用程序中的副本集发现。如果需要对MongoClient执行任何操作,例如 find()insert_one() ,则客户端在尝试该操作之前将等待发现合适的成员。

处理故障转移#

当发生故障转移时,PyMongo将自动尝试查找新的主节点并在该节点上执行后续操作。然而,这不可能完全透明地发生。在这里,我们将执行一个故障转移示例,以说明所有事情的行为。首先,我们将连接到副本集并执行两个基本操作:

>>> db = MongoClient("localhost", replicaSet='foo').test
>>> db.test.insert_one({"x": 1}).inserted_id
ObjectId('...')
>>> db.test.find_one()
{'x': 1, '_id': ObjectId('...')}

通过检查主机和端口,我们可以看到我们连接到 本地主机:27017 ,它是当前的主节点:

>>> db.client.address
('localhost', 27017)

现在让我们关闭该节点,看看再次运行查询时会发生什么:

>>> db.test.find_one()
Traceback (most recent call last):
pymongo.errors.AutoReconnect: ...

我们得到一个 AutoReconnect 例外情况。这意味着驱动程序无法连接到旧的主服务器(这是有意义的,因为我们关闭了服务器),但它将尝试在后续操作中自动重新连接。当出现此异常时,我们的应用程序代码需要决定是重试操作还是继续操作,接受操作可能已失败的事实。

在随后尝试运行查询时,我们可能会继续看到此异常。但是,最终副本集将进行故障切换并选择一个新的主服务器(这通常不超过几秒钟)。此时,驱动程序将连接到新的主服务器,操作将成功:

>>> db.test.find_one()
{'x': 1, '_id': ObjectId('...')}
>>> db.client.address
('localhost', 27018)

让以前的初选重新开始。它将作为辅助对象重新加入集合。现在我们可以进入下一个部分:将读取分配给辅助对象。

辅助读取#

默认情况下,MongoClient的实例向副本集的主要成员发送查询。要对查询使用辅助项,必须更改读取首选项:

>>> client = MongoClient(
...     'localhost:27017',
...     replicaSet='foo',
...     readPreference='secondaryPreferred')
>>> client.read_preference
SecondaryPreferred(tag_sets=None)

现在,所有查询都将发送到集合的次成员。如果没有辅助成员,则主成员将用作备用。如果您有不希望发送到主服务器的查询,可以使用 secondary 阅读偏好。

默认情况下 Database 从其MongoClient继承,并且 Collection 是从其数据库继承的。要使用不同的读取首选项,请使用 get_database() 方法,或 get_collection() 方法:

>>> from pymongo import ReadPreference
>>> client.read_preference
SecondaryPreferred(tag_sets=None)
>>> db = client.get_database('test', read_preference=ReadPreference.SECONDARY)
>>> db.read_preference
Secondary(tag_sets=None)
>>> coll = db.get_collection('test', read_preference=ReadPreference.PRIMARY)
>>> coll.read_preference
Primary()

也可以更改现有的 Collectionwith_options() 方法:

>>> coll2 = coll.with_options(read_preference=ReadPreference.NEAREST)
>>> coll.read_preference
Primary()
>>> coll2.read_preference
Nearest(tag_sets=None)

请注意,由于大多数数据库命令只能发送到副本集的主数据库,因此 command() 方法不符合数据库的 read_preference ,但可以向方法传递显式的读取首选项:

>>> db.command('dbstats', read_preference=ReadPreference.NEAREST)
{...}

读取配置使用三个选项: 读取首选项标记集局部阈值 .

读取首选项

Read preference is configured using one of the classes from read_preferences (Primary, PrimaryPreferred, Secondary, SecondaryPreferred, or Nearest). For convenience, we also provide ReadPreference with the following attributes:

  • PRIMARY :从主服务器读取。这是默认的读取首选项,并提供了最强的一致性。如果没有主可用,则升高 AutoReconnect .

  • PRIMARY_PREFERRED :从主服务器读取(如果可用),否则从辅助服务器读取。

  • SECONDARY :从辅助设备读取。如果没有匹配的辅助设备可用,则升高 AutoReconnect .

  • SECONDARY_PREFERRED :从辅助服务器读取(如果可用),否则从主服务器读取。

  • NEAREST :从任何可用成员读取。

标记集

Replica-set members can be tagged according to any criteria you choose. By default, PyMongo ignores tags when choosing a member to read from, but your read preference can be configured with a tag_sets parameter. tag_sets must be a list of dictionaries, each dict providing tag values that the replica set member must match. PyMongo tries each set of tags in turn until it finds a set of tags with at least one matching member. For example, to prefer reads from the New York data center, but fall back to the San Francisco data center, tag your replica set members according to their location and create a MongoClient like so:

>>> from pymongo.read_preferences import Secondary
>>> db = client.get_database(
...     'test', read_preference=Secondary([{'dc': 'ny'}, {'dc': 'sf'}]))
>>> db.read_preference
Secondary(tag_sets=[{'dc': 'ny'}, {'dc': 'sf'}])

MongoClient试图在纽约,然后是旧金山找到二级人才,然后加薪 AutoReconnect 如果没有。作为一个额外的后备,指定一个最终的空标记集, {{}} ,这意味着“从匹配模式的任何成员读取,忽略标记。”

read_preferences 更多信息。

局部阈值

如果多个成员匹配read preference和标记集,PyMongo将从最近的成员中读取,这些成员是根据ping时间选择的。默认情况下,只有ping时间在最近的15毫秒内的成员才会用于查询。您可以选择在具有较高延迟的成员之间分配读取,方法是设置 localThresholdMS 更大的数字:

>>> client = pymongo.MongoClient(
...     replicaSet='repl0',
...     readPreference='secondaryPreferred',
...     localThresholdMS=35)

在本例中,PyMongo在最接近的成员ping时间的35毫秒内将读取分发给匹配的成员。

备注

localThresholdMS 在与副本集对话时被忽略 通过 一个蒙哥斯。相当于 localThreshold 命令行选项。

健康监测#

当MongoClient初始化时,它会启动后台线程来监视副本集中的更改:

  • 运行状况:检测某个成员何时关闭或出现,或者某个不同的成员是否成为主成员

  • 配置:检测何时添加或删除成员,并检测成员标记中的更改

  • 延迟:跟踪每个成员的ping时间的移动平均值

副本集监视确保当副本集的状态更改时,查询会不断路由到适当的成员。

mongos负载平衡#

的实例 MongoClient 可以配置mongos服务器的地址列表:

>>> client = MongoClient('mongodb://host1,host2,host3')

列表中的每个成员都必须是单个mongos服务器。多宿主和循环DNS地址是 not 支持。客户端持续监视所有mongose的可用性,以及每个mongose的网络延迟。

在其蒙系内平均分配其操作 localThresholdMS (类似于 distributes reads to secondaries 在副本集中)。默认情况下,阈值为15毫秒。

延迟最低的服务器,以及延迟不超过 localThresholdMS 除了最低延迟的服务器,接收操作是平等的。例如,如果我们有三个Mongos:

  • 主机1:20毫秒

  • 主机2:35毫秒

  • 主机3:40毫秒

默认情况下 localThresholdMS 是15毫秒,所以PyMongo平均使用host1和host2。它使用host1,因为它对驱动程序的网络延迟最短。它使用host2是因为它的延迟在最慢延迟服务器的15毫秒之内。但是它为host3找借口:host3比最低延迟服务器晚了20毫秒。

如果我们设置 localThresholdMS 到30毫秒所有服务器都在阈值范围内:

>>> client = MongoClient('mongodb://host1,host2,host3/?localThresholdMS=30')

警告

not 通过负载平衡器将PyMongo连接到mongos实例池。一个套接字连接必须始终路由到同一个mongos实例以获得正确的游标支持。