• 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()
    
    
  • 相关阅读:
    postfix遇到的问题
    SElinux以及防火墙的关闭
    centos查看系统信息
    WINDOWS访问SAMBA提示没有权限
    常用命令
    口才
    【李敖的管理经】
    随笔
    查询MX记录
    bash: ifconfig: command not found
  • 原文地址:https://www.cnblogs.com/kalicener/p/15837575.html
Copyright © 2020-2023  润新知