Unable to connect to a member of the replica set matching the read preference Primary
今天嘗試使用MongoDB Replica Set提供的自動故障恢復功能一直無法成功,總是遇到上面這個錯誤。好一頓整終於找到的原因,由於這里還比較有迷惑性,特此記錄一下供其他人參考。
其實Google一下這個錯誤,在前幾條結果中就有答案,但是該死的GFW屏蔽了Google Group,導致我跳過了那條結果而一直沒有找到正確答案,我可以罵臟話嗎?
進入正題,我們知道使用Replica Set是要在連接字符串中加入所有結點的地址(建議這么做,但並不是強制。關於連接字符串參考這里)。以我為機器為例,我的Windows在虛擬機192.168.122.100上,MongoDB運行在物理機192.168.122.1中的三個實例上,分別是:
192.168.122.1:27017 192.168.122.1:27011 192.168.122.1:27012
因此我的連接字符串大概會是這個樣子(ReplicaSet為rs0):
mongodb://192.168.122.1,192.168.122.1:27011,192.168.122.1:27012/Cart?replicaSet=rs0
然后我到MongoDB的實例上建立了Replica Set:
yaoxing@YX-ARCH ~ $ mongo localhost MongoDB shell version: 2.4.8 connecting to: test >rs.initiate()
然后嘗試把其他兩個實例添加到Replica Set中:
rs.add("localhost:27011"); rs.add("localhost:27012");
發生了錯誤:
{ "errmsg" : "exception: can't use localhost in repl set member names except when using it for all members", "code" : 13393, "ok" : 0 }
最容易想到的就是把localhost更換成機器名了,於是
rs.add("YX-ARCH:27011")
rs.add("YX-ARCH:27012")
成功,一切看起來都很美好,但其實問題就已經在這里發生了。如果嘗試關閉Primary,就會有另外一個實例從Secondary變為Primary,這點沒有問題。但客戶端始終會拋出本文開頭那個異常,無法自動切換到新的Primary上。原因如下:
如果仔細看MongoDB的文檔,你會發現其實在連接字符串中寫進所有的結點並不是必要的,但如果只寫一個結點的話,必須要加上?replicaSet=[set name]參數(原因參考文檔)。為什么可以只寫一個結點?因為當Driver連接上一個結點之后會從該結點獲取其他結點的信息,這樣做是為了以后動態添加新結點的時候Driver可以從結點信息中自動識別,而不必每次都修改連接字符串。當然如果只寫一個結點的話,壞處就是當這個結點掛了的時候Driver就不知道去哪找其他結點的信息了。
由此我們可以發現,有幾台服務器並不是從你的連接字符串中發現的,而是當Driver連接上某一台服務器后,從中獲取的。所以從以上的例子中獲取了什么東西?
rs0:PRIMARY> rs.conf() { "_id" : "rs0", "version" : 7, "members" : [ { "_id" : 0, "host" : "YX-ARCH:27017", "priority" : 20 }, { "_id" : 1, "host" : "YX-ARCH:27011", "priority" : 30 }, { "_id" : 2, "host" : "YX-ARCH:27012", "priority" : 20 } ] }
一共三個HOST
YX-ARCH YX-ARCH:27011 YX-ARCH:27012
明眼人應該看出來了,YX-ARCH這個東西在我的虛擬機中不存在,因此C# Driver發現連接字符串最終無法連接到成為新Primary的YX-ARCH:27011,而唯一可以連接的192.168.122.1是Secondary身份,我又沒有指定readPreference,因此不可讀。悲劇就這么發生了。
當然知道了原因,解決方案也很簡單:
在replica set中和連接字符串中使用相同的HOST地址就可以完美解決問題
希望對遇到同樣問題的人有所幫助。