最近一直被这个连接问题给困恼。。。怎么都没法连接上阿里云的云服务器。对于golang我用的是 github.com/mongodb/mongo-go-driver
,每一项都按照了文档去设置但是死活连接不上,于是更换为github.com/globalsign/mgo
,这样就可以正常连接。对于python,如果使用了pymongo4.0.1
版本也是无法连接,但是更换为pymongo3.6
就可以正常连接。
这两个现象都非常奇怪,但都有相同的特点,无论是golang
里的mgo
库还是python
的pymong3.6
都是挺老的版本。
golang报错日志:
2022/01/23 21:38:18 server selection error: server selection timeout, current topology: { Type: ReplicaSetNoPrimary, Servers: [{ Addr: 139.196.245.210:3717, Type: Unknown, Last error: connection() error occured during connection handshake: connection(139.196.245.210:3717[-127]) socket was unexpectedly closed: EOF }, { Addr: 139.196.245.214:3717, Type: Unknown, Last error: connection() error occured during connection handshake: connection(139.196.245.214:3717[-128]) socket was unexpectedly closed: EOF }, ] }
python报错日志
pymongo.errors.ServerSelectionTimeoutError: 139.196.245.214:3717: connection closed,139.196.245.210:3717: connection closed, Timeout: 30s, Topology Description: <TopologyDescription id: 61ed5945eba641d6e1b58800, topology_type: ReplicaSetNoPrimary, servers: [<ServerDescription ('139.196.245.210', 3717) server_type: Unknown, rtt: None, error=AutoReconnect('139.196.245.210:3717: connection closed')>, <ServerDescription ('139.196.245.214', 3717) server_type: Unknown, rtt: None, error=AutoReconnect('139.196.245.214:3717: connection closed')>]>
看到这个错误日志真的令人迷惑,为啥会出现这么多的server,我只是通过跳板机访问了一个服务。
查找资料后发现,这些云数据库都是容器化管理,也就是我的一台mongodb云数据库,其实有多个容器组成的集群,这些容器之间可以相互访问,但是外部无法访问集群的节点。
这里非常感谢,连接Replica Set出现问题给出了解释:
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.
也就是说driver会默认开启服务发现
,这就导致我们会从容器的外部来访问这个集群的其他机器。
在pymongo
和mgo
这些比较旧的服务器里,因为那时还没有流行这种集群化管理,所以没有服务发现的功能。
解决办法
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.
采用direct
的连接方式。
这里可以看golang
给出的docs
:
package main
import (
"context"
"log"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func main() {
// Create a direct connection to a host. The driver will send all requests
// to that host and will not automatically discover other hosts in the
// deployment.
clientOpts := options.Client().ApplyURI(
"mongodb://localhost:27017/?connect=direct")
client, err := mongo.Connect(context.TODO(), clientOpts)
if err != nil {
log.Fatal(err)
}
_ = client
}
ApplyURI
把 connect=direct
加入,这样就可以愉快连接了。
pymongo
client = MongoClient('mongodb://localhost',
port=3733,
username=username,
password=password,
authSource='admin',
directConnection =True,
authMechanism='SCRAM-SHA-1'
)
pymongo
中有个字段 directConnection
,这个字段设置为True
代表直接连接。
这里附上我连接的代码
golang
package main
import (
"context"
"log"
"os"
"time"
"github.com/elliotchance/sshtunnel"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"golang.org/x/crypto/ssh"
)
func main() {
tunnel := sshtunnel.NewSSHTunnel(
// 在这里设置你的跳板机地址.
"username@ipv4:port",
// 选择sshpassword的连接方式
ssh.Password("password"),
// 阿里云mongodb的地址.
"dds-uf61fd4**********44-pub.mongodb.rds.aliyuncs.com:3717",
// 设置本地绑定端口
"3733",
)
tunnel.Log = log.New(os.Stdout, "", log.Ldate|log.Lmicroseconds)
go tunnel.Start()
time.Sleep(100 * time.Millisecond) // 等待开启tunnel
MgoCli()
}
var mgoCli *mongo.Client
func initDb() {
var err error
credential := options.Credential{
AuthMechanism: "SCRAM-SHA-1", // 阿里云服务的
Username: "username", // mongodb用户名
Password: "password", //mongodb 密码
AuthSource: "admin", //默认admin不需要改
PasswordSet: true,
}
clientOpts := options.Client().ApplyURI("mongodb://localhost:3733").
SetAuth(credential)
//连接到MongoDB
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) // 设置5s超时
defer cancel()
client, err := mongo.Connect(ctx, clientOpts)
if err != nil {
log.Fatal(err)
}
//检查连接
err = client.Ping(context.TODO(), nil)
if err != nil {
log.Fatal(err)
}
}
func MgoCli() *mongo.Client {
if mgoCli == nil {
initDb()
}
return mgoCli
}
pymongo
from pymongo import MongoClient
from sshtunnel import SSHTunnelForwarder
from pprint import pprint
import urllib.parse
import time
tunnel = SSHTunnelForwarder(
("跳板机ip",22),
ssh_username=r"跳板机用户名",
ssh_password=r"跳板机密码",
remote_bind_address=(r"dds-uf61f**********b.mongodb.rds.aliyuncs.com", 3717),
local_bind_address=("127.0.0.1",3733))
tunnel.start()
print(tunnel.local_bind_port)
from pymongo import MongoClient
client = MongoClient('mongodb://localhost',
port=3733,
username='数据库用户名',
password='数据库密码',
authSource='admin',
directConnection =True, # 使用直接连接方式
authMechanism='SCRAM-SHA-256'
)
db = client['admin']
npm = db['npm_records']
for item in npm.find().limit(1):
pprint(item)
tunnel.close()