(原创) 使用pymongo 3.6.0连接MongoDB的正确姿势
0.疑惑
前两天使用pymongo连接MongoDB的时候发现了一个奇怪的现象:我本机MongoDB并没有打开,但是使用pymong.MongoClient()进行连接时,并没有异常,我的服务端也正常跑起来了,直到收到请求,进行数据库查询操作的时候,等了相当长的一段时间之后,服务端才由于MongoDB连接不上报异常。
  Note: 本机环境pymongo 3.6.0,MongoDB 3.4.6
不信?可以打开ipython,输入如下命令:
from pymongo import MongoClient
client = MongoClient('aaa', 1234)
db = client.database
task = db.task
怎么样?是不是一直ok
再执行下面这条命令呢?
task.count()
等待相当长的一段时候后报错了:
ServerSelectionTimeoutError: aaa:1234: [Errno 11001] getaddrinfo failed
如图1所示

1.解答
那这是为什么呢?
按一般的理解,MongoClient接口实现的时候肯定要考虑MongoDB连接异常的情况,只有确保连接成功建立,才能一步一步往下取database、取collection、基于collection进行增删改查操作等等。
这里为什么不呢 ?我发现了一个大BUG?不应该啊,pymongo发布使用的时间比我用Python写代码都要长,这要是bug早该解决了。
如何使用pymongo连接MongoDB,网上确实有很多很多博客,不过绝大多数都很简单,基本就我上面那几行的正确使用而已,顶多再提醒一下安装pymongo的时候不能安装第三方bson,因为pymongo自带bson,两者不匹配,讲如何进行密码验证的都很少,更别说replSet参数的使用。
终于还是在官网的API接口文档中MongoClient的解释中找到了答案:

简单翻译一下:
从pymongo3.0版本开始,MongoClient的构造函数就不会再阻塞等待MongoDB连接的建立,即使连接不上也不会上报ConnectionFailure,用户提交的资格证书(估计是用户名密码或者cert证书)是错误的也不会上报ConfigurationError。相反,构造函数会立即返回并在后台线程中加载处理连接数据的进程。如果想确认返回的client是否真实可用,可以如下操作:
# The ismaster command is cheap and does not require auth.
    client.admin.command('ismaster')
这说明至少3.0之前版本的设计和我的想法是一样的,那现在为什么换了高级玩法呢?还是不太明白
至于为什么执行命令时上报异常的时间比较长呢?

图3和图1中的时间差达到了近40s
因为两个参数:
connectTimeoutMS,连接mongo的超时机制, 默认20s
serverSelectionTimeoutMS,连接database的超时机制, 默认30s
虽然上面说过MongoClient的构造函数不再阻塞建立连接,但那个note上面还有一句话:
Note:  MongoClient creation will block waiting for answers from DNS when mongodb+srv:// URIs are used.
当使用"mongodb+srv://"形式的URI连接数据库服务器时,MongoClient将会阻塞等到DNS的域名解析结果,但同样不是数据库的连接。估计这种"mongodb+srv://"形式的URI是用来连接MongoDB去年提出的云服务吧,要不哪来的DNS解析需求呢。
2.更新汇总
简单说一下MongoClient中提到的一些更新要点:
- 版本3.6:新增mongodb+srv://形式的URI,新增retryWrites 关键字变量和URI选项
- 版本3.5:新增'username'和'password'两个选项。新增'authSource'、'authMechanism'、'authMechanismProperties' 三个选项的文档。舍弃'socketKeepAlive'关键字变量和URI选项。 socketKeepAlive默认值改为True。
- 版本3.0:"pymongo.mongo_client.MongoClient"现在是唯一的client类,应用于独立的Mongo服务器、多台Mongos、Mongo集群。它兼容了“MongoReplicaSetClient”的功能,可以连接Mongo集群、寻找集群成员等操作,后者已被弃用。
- MongoClient的构造函数就不会再阻塞等待MongoDB连接的建立,即使连接不上也不会上报ConnectionFailure,用户提交的资格证书(估计是用户名密码或者cert证书)是错误的也不会上报ConfigurationError。相反,构造函数会立即返回并在后台线程中加载处理连接数据的进程。
- 因此“alive”方法也弃用了,因为它不再能提供有效信息;如果服务器连接断开了,在执行下一次操作的时候异常就会被发现。
- 在Pymongo 2.x中,MongoClient可以接受单例数据库的地址列表做参数,并自动连接第一个可用的数据库。
MongoClient(['host1.com:27017', 'host2.com:27017'])
不再支持多服务器的地址列表,如果要给列表的话,这些服务器一定要配置在同一个集群中。
- 在mongo集群中行为不再讲究“高可用”,而是“负载均衡”。因为以前只是在连的时候优先连接最低负载的服务器,除非网络异常才会连别的,但实际上这个“最低”可能只是一时的。而在Pymongo 3.x中,改为统一监控集群网络实时负载了。
- 新增“connect” URI选项(True立即连接,false第一个操作时才连接)
connect参数我试过并没有作用,或许在3.6版本中一并失效了吧
- “start_request”、“in_request”、“end_request ”三个方法和“auto_start_request ”选项都被移除了。
- “copy_database ”方法被移除了
- MongoClient.disconnect()被移除了,它和close()一样的。
- MongoClient不再支持以实例属性方式读取下划线开头命名的数据库属性了,必须以字典形式读取。
YES: client['__my_database__'],  NO: client.__my_database__
3.总结
- 1)两种基本的连接方法,一种是使用keyword argument(关键字变量),另一种是MongoDB URI format(URI参数)
from pymongo import MongoClient
client = MongoClient()
# keyword argument
client = MongoClient('localhost', 27017)
# MongoDB URI
client = MongoClient('mongodb://localhost:27017/')
- 2)用户名密码验证
 note: MongoDB 3.0(对应pymongo2.8)之后默认使用“SCRAM-SHA-1”加解密;之前使用的是“MONGODB-CR”,可以使用authMechanism指定;同时可以使用authSource指定应用加解密的database,默认是admin。
# since MongoDB 3.0, SCRAM-SHA-1
from pymongo import MongoClient
# keyword argument
client = MongoClient('example.com',
                      username='user',
                      password='password',
                      authSource='the_database',
                      authMechanism='SCRAM-SHA-1')
# MongoDB URI
uri = "mongodb://user:password@example.com/the_database?authMechanism=SCRAM-SHA-1"
 client = MongoClient(uri)
- 3)replSet
 假设本地有如下的集群配置
config = {'_id': 'foo', 'members': [
     {'_id': 0, 'host': 'localhost:27017'},
     {'_id': 1, 'host': 'localhost:27018'},
     {'_id': 2, 'host': 'localhost:27019'}]}
可以通过replSet参数指定集群名称(_id),主库、从库等都可以读取到,这里就不细说了
>>> 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/?replicaSet=foo')
MongoClient(['localhost:27017', 'localhost:27018'], replicaset='foo', ...)
- "mongodb+srv://"
 这种形式的URL只支持一个hostname,对应DNS server,进行SRV record查询,它也支持replSet和authSource(TXT record),需要注意的是它默认使用TLS,即ssl=True。
 具体的说明还是看initial-dns-seedlist-discovery
 假设我们使用
 
- "mongodb+srv://"
mongodb+srv://server.mongodb.com/
而DNS server(_mongodb._tcp.server.mongodb.com)上有如下SRV record
Record                            TTL   Class    Priority Weight Port  Target
_mongodb._tcp.server.mongodb.com. 86400 IN SRV   0        5      27317 mongodb1.mongodb.com.
_mongodb._tcp.server.mongodb.com. 86400 IN SRV   0        5      27017 mongodb2.mongodb.com.
且server.mongdb.com存在如下Txt records
Record              TTL   Class    Text
server.mongodb.com. 86400 IN TXT   "replicaSet=replProduction&authSource=authDB"
那么对应解析结果就是:
mongodb://mongodb1.mongodb.com:27317,mongodb2.mongodb.com:27107/?ssl=true&replicaSet=replProduction&authSource=authDB
- SSL
 一堆的参数,还没有用过,自行看文档吧。
 
- SSL
4.代码示例
此处给出一份使用pymongo3.6连接MongoDB的代码示例,分别是OPTION和URI两种方式
主要考虑集群配置和密码校验两个方面,假设配置文件如下
MONGODB = {
    'host': '127.0.0.1',
    'port': '27017',
    'user': '',
    'pwd': '',
    'db': 'test',
    'replicaSet': {
        'name': 'abc',
        "members": [
            {
                "host": "localhost",
                "port": "27017"
            },
            {
                "host": "localhost",
                "port": "27027"
            },
            {
                "host": "localhost",
                "port": "27037"
            }
        ]
    }
}
约定如下:
replicaSet的name为空则不使用集群配置
user和pwd为空则不需要进行密码校验
db不给出则默认为“admin”
则OPTION方式:
import urllib.parse
import pymongo
from config import MONGODB
if MONGODB['replicaSet']['name']:
    host_opt = []
    for m in MONGODB['replicaSet']['members']:
        host_opt.append('%s:%s' % (m['host'], m['port']))
    replicaSet = MONGODB['replicaSet']['name']
else:
    host_opt = '%s:%s' % (MONGODB['host'], MONGODB['port'])
    replicaSet = None
option = {
    'host': host_opt,
    'authSource': MONGODB['db'] or 'admin',    # 指定db,默认为'admin'
    'replicaSet': replicaSet,
}
if MONGODB['user'] and MONGODB['pwd']:
    # py2中为urllib.quote_plus
    option['username'] = urllib.parse.quote_plus(MONGODB['user'])
    option['password'] = urllib.parse.quote_plus(MONGODB['pwd'])
    option['authMechanism'] = 'SCRAM-SHA-1'
client = pymongo.MongoClient(**option)
URI方式
import urllib.parse
import pymongo
from config import MONGODB
# mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]
params = []
host_info = ''
# 处理replicaSet设置
if MONGODB['replicaSet']['name']:
    host_opt = []
    for m in MONGODB['replicaSet']['members']:
        host_opt.append('%s:%s' % (m['host'], m['port']))
    host_info = (',').join(host_opt)
    replicaSet_str = 'replicaSet=%s' % MONGODB['replicaSet']['name']
    params.append(replicaSet_str)
else:
    host_info = '%s:%s' % (MONGODB['host'], MONGODB['port'])
# 处理密码校验
if MONGODB['user'] and MONGODB['pwd']:
    # py2中为urllib.quote_plus
    username = urllib.parse.quote_plus(MONGODB['user'])
    password = urllib.parse.quote_plus(MONGODB['pwd'])
    auth_str = '%s:%s@' % (username, password)
    params.append('authMechanism=SCRAM-SHA-1')
else:
    auth_str = ''
if params:
    param_str = '?' + '&'.join(params)
else:
    param_str = ''
uri = 'mongodb://%s%s/%s%s' % (auth_str, host_info, MONGODB['db'], param_str)
client = pymongo.MongoClient(uri)
假设db中有collection名为TEST_COL,可以如下验证client的有效性:
    database = client[MONGODB['db']]
    print(database.TEST_COL.count())
    # client.run.command({'count': 'TEST_COL'})    # 需要权限
    # client.admin.command('ismaster')             # 不支持副本集环境
4.配置副本集读写分离
from pymongo import ReadPreference
db = conn.get_database(MONGODB['db'], read_preference=ReadPreference.SECONDARY_PREFERRED)
副本集ReadPreference有5个选项:
- PRIMARY:默认选项,从primary节点读取数据
- PRIMARY_PREFERRED:优先从primary节点读取,如果没有primary节点,则从集群中可用的secondary节点读取
- SECONDARY:从secondary节点读取数据
- SECONDARY_PREFERRED:优先从secondary节点读取,如果没有可用的secondary节点,则从primary节点读取
- NEAREST:从集群中可用的节点读取数据
作者:mona_alwyn
链接:https://www.jianshu.com/p/d9918b0a3ebc
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
(原创) 使用pymongo 3.6.0连接MongoDB的正确姿势的更多相关文章
- SpringBoot2.0整合fastjson的正确姿势
		SpringBoot2.0如何集成fastjson?在网上查了一堆资料,但是各文章的说法不一,有些还是错的,可能只是简单测试一下就认为ok了,最后有没生效都不知道.恰逢公司项目需要将J ... 
- 使用PyMongo访问需要认证的MongoDB
		Windows 10家庭中文版,Python 3.6.4,PyMongo 3.7.0,MongoDB 3.6.3,Scrapy 1.5.0, 前言 在Python中,使用PyMongo访问Mongod ... 
- pymongo连接MongoDB
		导语 pymongo 是目前用的相对普遍一个python用来连接MongoDB的库,是工作中各种基本需求都能满足具体api可以参考 pymongo APIpymongo github 安装 Mongo ... 
- 使用mongo-java-driver-3.0.2连接MongoDB数据库
		这里使用的mongodb的java驱动版本是:3.0.2,文件名mongo-java-driver-3.0.2.jar 博客本地下载下载网址(也可以下载其它版本):http://central.ma ... 
- Python 连接MongoDB并比较两个字符串相似度的简单示例
		本文介绍一个示例:使用 pymongo 连接 MongoDB,查询MongoDB中的 字符串 记录,并比较字符串之间的相似度. 一,Python连接MongoDB 大致步骤:创建MongoClient ... 
- 基于 pyMongo 和 wxPython 实现的 MongoDB Schema Analyser
		MongoDB 作为文档型 NoSql 数据库,它的集合表结构往往不像关系型数据库那么固定和统一,同一个集合下的文档(document)的字段变化和差异可能很大,特别是在数据模型缺乏良好规划和规范的数 ... 
- centos7 远程连接mongodb时,27017端口连接不上的解决办法
		一.问题描述:centos 7 上安装mongogdb,然后通过另外一台电脑用pymongo连接mongodb时,报错:连接拒绝 解决过程: 1.修改mongo.conf文件 命令:sudo vi ... 
- 爬虫入门【8】Python连接MongoDB的用法简介
		MongoDB的连接和数据存取 MongoDB是一种跨平台,面向文档的NoSQL数据库,提供高性能,高可用性并且易于扩展. 包含数据库,集合,文档等几个重要概念. 我们在这里不介绍MongoDB的特点 ... 
- 远程连接mongodb时,27017端口连接不上的解决办法
		一.背景描述: 我在linux RED7上安装了mongodb,并没有修改mongodb的配置文件.然后通过另外一台电脑用pymongo连接mongodb时,报错:timeout. ping IP ... 
随机推荐
- sas 选择一段日期,和一定周期,生成日期序列和周期序列
			工作需要,得选择一段日期,和一定周期,生成日期序列和周期序列.暂时用七天为一个周期 data d; format date date9.; do date='04mar2018'd to'05may2 ... 
- DevExpress ASP.NET v18.2新功能详解(四)
			行业领先的.NET界面控件2018年第二次重大更新——DevExpress v18.2日前正式发布,本站将以连载的形式为大家介绍新版本新功能.本文将介绍了DevExpress ASP.NET Cont ... 
- Highcharts 柱图 每个柱子外围的白色边框
			Highcharts 柱图中每条柱外会有默认白色的边框, 去边框代码如下: plotOptions: { bar: { borderColor: "",//去边框 } } 
- transform带来的坑
			1.transform会使子元素fixed定位和absolute定位失效. 2.父元素设置了border-radius和overflow:hidden, 但是子元素有transform属性,父元素设置 ... 
- git工具学习
			最近实习的时候,遇到git工具,发现好强大之前没用过,特来学习下,然后自己注册了一个github账号,结合git命令练习一下,git的安装就不说了. 学习资料来源:廖雪峰Git教程 git简介: gi ... 
- leetcode第27题:移除指定元素
			给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成 ... 
- ubuntu下pyspark的安装
			1.安装jkd1.8(这里不再描述) 2.直接在终端输入pip install pyspark(官网提供的最简单的一种安装方式) 过程如下: Collecting pyspark Downloadin ... 
- Delphi 10.3.1来了
			10.3.1发布了,这个版本可以独自安装,是对Delphi 10.3 Rio,C ++ Builder 10.3 Rio和RAD Studio 10.3 Rio的更新.如果安装了2018年11月发布的 ... 
- python day09作业答案
			2. def lst(input): lst2=[] count=0 for i in range(0,len(input)): if i %2!=0: lst2.append(input[i]) r ... 
- SpringBoot2静态资料访问
			在SpringBoot2内要继承WebMvcConfigurationSupport并重写addResourceHandlers方法才能访问到静态资料. @Configuration public c ... 
