环境
-
用Docker来搭建的Replica Set,有三个Container在运行。
Container分别为:mongo0, mongo1, mongo3
对外暴露的端口分别为:30001, 30002, 30003
Replica Set名称:my-mongo-set
调用代码
我用的是官方的mongo-go-driver
package main
import (
"context"
"fmt"
"log"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func main() {
clientOptions := options.Client().ApplyURI("mongodb://mongo1,mongo2,mongo3)
// 上面的代码也可以写成
// options.Client().ApplyURI("mongodb://mongo1")
// 因为,当Mongo Client 不是以Single Node模式连接服务时,会查询Replica Set 中所有的节点,并且会在连接的主节点出问题时自动切换到新推举的主节点上
clientOptions.SetReplicaSet("my-mongo-set")
client, err := mongo.Connect(context.TODO(), clientOptions)
if err != nil {
log.Fatal(err)
}
// Check the connection
err = client.Ping(context.TODO(), nil)
if err != nil {
log.Fatal(err)
}
fmt.Println("Connected to MongoDB!")
err = client.Disconnect(context.TODO())
if err != nil {
log.Fatal(err)
}
fmt.Println("Connection to MongoDB closed.")
}
调用后出现错误提示:
2019/06/28 09:28:02 server selection error: server selection timeout
current topology: Type: ReplicaSetNoPrimary
Servers:
Addr: mongo1:27017, Type: Unknown, State: Connected, Avergage RTT: 0, Last error: dial tcp: lookup mongo1: no such host
Addr: mongo2:27017, Type: Unknown, State: Connected, Avergage RTT: 0, Last error: dial tcp: lookup mongo2: no such host
Addr: mongo0:27017, Type: Unknown, State: Connected, Avergage RTT: 0, Last error: dial tcp: lookup mongo0: no such host
exit status 1
调查
找了半天原因也不知道为什么会这样。用mongoclient
和Robo 3T
连接都没问题。不过终于还是让我找到了原因,主要是因为用Docker来做的Replica Set导致的。
原因
先大概解释一下:主要原因是Docker的网络导致的这个问题。每一个mongo的container启动的时候都有个name,在我的环境中就是mongo1~mongo3。对于mongo-go-driver的client来说,当它要连接一个Replica Set 的时候,需要连通这些mongodb server。但是mongo1~mongo3的hostname对于一个Docker network之外的程序是无法解析的,所以就会导致上面显示的错误。其中一个解决方案就是把go程序也放进Docker中,启动一个和Replica Set在同一个网络的container就好了。所以在用Docker开发时,但凡涉及网络访问的功能,要有这个网络分隔的概念,否则很多问题都会莫名其妙。
把原回答贴一下:
This is due to unresolved
hostname
from Docker host. In Docker, the instancesmongo1
,mongo2
, andmongo3
are reachable by those names. However, these names are not reachable from the Docker host. This is evident by this line:
Addr: mongo2:27017, Type: Unknown, State: Connected, Average RTT: 0, Last error: dial tcp: lookup mongo2: no such host
MongoDB driver will attempt
server discovery
from given a replica set member(s); it will find all of other nodes within the replica set (via rs.conf). The problem here is the replica set is set with namemongo<N>
, the driver (run in Docker host) would not be able to resolve these names. You can confirm this by trying to pingmongo1
from Docker host.You can either try running the application from another Docker instance sharing the same Docker network as the replica set. Or, modify the Docker networking as such to allow resolvable hostnames.
UPDATE:
Regarding your comment on why using mongo shell, or PyMongo works.
This is due to the difference in connection mode. When specifying a single node, i.e.
mongodb://node1:27017
in shell or PyMongo, server discovery are not being made. Instead it will attempt to connect to that single node (not as part as a replica set). The catch is that you need to connect to the primary node of the replica set to write (you have to know which one). If you would like to connect as a replica set, you have to define the replica set name.In contrast to the
mongo-go-driver
, by default it would perform server discovery and attempt to connect as a replica set. If you would like to connect as a single node, then you need to specifyconnect=direct
in the connection URI.