(原創) 使用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

1.解答

那這是為什么呢?
按一般的理解,MongoClient接口實現的時候肯定要考慮MongoDB連接異常的情況,只有確保連接成功建立,才能一步一步往下取database、取collection、基於collection進行增刪改查操作等等。
這里為什么不呢 ?我發現了一個大BUG?不應該啊,pymongo發布使用的時間比我用Python寫代碼都要長,這要是bug早該解決了。
如何使用pymongo連接MongoDB,網上確實有很多很多博客,不過絕大多數都很簡單,基本就我上面那幾行的正確使用而已,頂多再提醒一下安裝pymongo的時候不能安裝第三方bson,因為pymongo自帶bson,兩者不匹配,講如何進行密碼驗證的都很少,更別說replSet參數的使用。
終於還是在官網的API接口文檔MongoClient的解釋中找到了答案:

 
圖2

簡單翻譯一下:
從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

圖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', ...) 
    1. "mongodb+srv://"
      這種形式的URL只支持一個hostname,對應DNS server,進行SRV record查詢,它也支持replSet和authSource(TXT record),需要注意的是它默認使用TLS,即ssl=True。
      具體的說明還是看initial-dns-seedlist-discovery
      假設我們使用
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 
    1. 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
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM