最近一直被這個連接問題給困惱。。。怎么都沒法連接上阿里雲的雲服務器。對於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()