golang和python連接阿里雲MongoDB雲數據庫(解決ReplicaSetNoPrimary錯誤)


最近一直被這個連接問題給困惱。。。怎么都沒法連接上阿里雲的雲服務器。對於golang我用的是 github.com/mongodb/mongo-go-driver ,每一項都按照了文檔去設置但是死活連接不上,於是更換為github.com/globalsign/mgo ,這樣就可以正常連接。對於python,如果使用了pymongo4.0.1版本也是無法連接,但是更換為pymongo3.6就可以正常連接。

這兩個現象都非常奇怪,但都有相同的特點,無論是golang里的mgo庫還是pythonpymong3.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 name mongo<N>, the driver (run in Docker host) would not be able to resolve these names. You can confirm this by trying to ping mongo1 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 specify connect=direct in the connection URI.

也就是說driver會默認開啟服務發現,這就導致我們會從容器的外部來訪問這個集群的其他機器。

pymongomgo這些比較舊的服務器里,因為那時還沒有流行這種集群化管理,所以沒有服務發現的功能。

解決辦法

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 specify connect=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
}

ApplyURIconnect=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()


免責聲明!

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



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