MongoDB

注意:BAE的MongoDB只能被部署在BAE中的应用访问,无法通过公网或BAE的本地开发环境访问。

MongoDB是百度云为开发者提供的分布式非关系型数据库存储服务,后端采用业界十分流行的MongoDB,并在前端提供与标准MongoDB完全一致的使用方式,特点如下:

  • 同时支持公有服务(MongoDB 2.4.13)和私有服务(MongoDB 2.6.3)。
  • 完全兼容MongoDB协议,开发者可以像使用本地数据库一样使用MongoDB服务。
  • 支持百万级别的后端数据库集群,并且多机冗余备份,开发者不需要关注后端机器及数据库的稳定性、网络问题、机房灾难、单库压力等各种风险。
  • 自动负载均衡和主从分离,开发者无需关注后端集群架构,可以像使用单机数据库一样使用分布式数据库。
  • 提供天然的数据隔离。
  • 提供安全性检查,对于恶意攻击性访问可及时发现并拒绝。
  • 提供免费套餐以及多种付费套餐的形式,满足不同开发者的需求。
  • 支持各语言原生的SDK访问数据库。

添加MongoDB数据库服务

注意:

BAE目前已不支持购买私有MongoDB服务,仅支持免费MongoDB服务。

  1. 在“应用引擎BAE-部署列表”页面选取目标部署并单击进入后,单击左侧导航栏的“扩展服务”。
  2. 单击“添加扩展服务”,选择MongoDB。

  3. 选择合适的套餐并勾选“同意”后,单击“立即购买”进入确认订单页面确认购买。

    套餐标注“免费”的为公有数据库(新用户仅支持1个免费的公有数据库),标注“私有”的为私有数据库。私有服务的密码初始化时为用户的SK (Secret Access Key),创建成功后,密码会随着重置SK而变化。私有数据库与共有数据库的区别如下:

    • 相同点:

      • 支持各语言原生SDK访问数据库,用户只需更改数据库的连接信息即可使用,应用迁移的代价几乎为零。
      • 提供天然的数据隔离和数据库冗余备份。
    • 不同点:

      • 公有数据库支持数据库导入导出及RockMongo,私有数据库不支持。
      • 公有数据库支持主从数据库自动切换,私有数据库不支持。
      • 私有MongoDB支持长连接,维护MongoDB连接池,不受1小时不活跃断连接的限制。用户代码里面须有重连机制,防止后端服务发生热切换导致连接失败。公有数据库会受1小时不活跃断连接的限制。
      • 私有MongoDB单个数据库实例默认最大连接数为1000,且直接连接服务实例,相较于公有集群,更能满足应用高并发、低延迟的需求。
      • 私有MongoDB无服务使用限制,用户独占服务资源。

      注意:公有MongoDB连接建立后如果连续空闲1小时无任何活动,将会被服务器端断开连接。如果在并发连接数已达到配额上限的情况下再发起建立新连接请求,服务器端将会在已有连接中查找是否有空闲30秒以上的,如果有则断开最近最不活跃(Least Recently Used)的一个连接并接受新连接请求;否则拒绝新连接请求。请您注意在应用代码中处理长时间空闲MongoDB连接将会被断开的问题。

  4. 购买成功后返回“扩展服务列表”页面,MongoDB部分显示新添加的MongoDB数据库资源。

  5. 单击数据库名称进入“MongoDB数据库详情”页面,展示基本信息及运行状态的同时,可对数据库进行如下操作:

    • 数据导入

      预先将导入对象存入BOS后,单击右上角的“导入”按钮,然后选择目标文件进行导入。导入结束后提示最近一次导入的执行结果。

    • 数据导出

      单击右上角的“导出”按钮,选择目标文件格式后进行导出。目前支持zip、gzip、bzip2三种类型。

      注意: 导出前请先开通BOS服务创建BOS Bucket用于存放目标文件。

    • 删除服务

      单击右上角的“删除服务”按钮后对数据库进行删除,确定后系统会回收相应的服务资源。

代码示例

Node.js连接MongoDB

  1. 将依赖添加到package.json。

    {
        "dependencies": {
            "mongodb": "2.0.46"
        }
    }
    
  2. 添加代码连接MongoDB。

    /*-------server.js---------*/
    var Db = require('mongodb').Db;
    var Server = require('mongodb').Server;
    var http = require('http'); 
    /*数据库连接信息host,port,user,pwd*/
    var db_name = '<DB_Name>'; //数据库名称
    var db_host = 'mongo.duapp.com'; //数据库地址
    var db_port = '8908'; // 数据库端口
    var username = '<User_AK>'; //用户AK
    var password = '<User_SK>'; //用户SK
    
    var db = new Db(db_name, new Server(db_host, db_port, {}), {
        w: 1
    });
    
    //启动时建立连接
    db.open(function(err, db) {
        db.authenticate(username, password, function(err, result) {
            if (err) {
                db.close();
                res.end('Authenticate failed!');
                return;
            }
            console.log("open db");
        });
    });
    
    var port = 18080;
    
    //每次来请求时复用已有连接执行query,如果连接已被server端断开底层驱动会自动重连
    http.createServer(function(request, res) {
        function test(err, collection) {
            collection.insert({
                a: 1
            }, function(err, docs) {
                if (err) {
                    console.log(err);
                    res.end('insert error');
                    return;
                }
                collection.count(function(err, count) {
                    if (err) {
                        console.log(err);
                        res.end('count error');
                        return;
                    }
                    res.end('count: ' + count + '\n');
                });
            });
        }
        db.collection("test_insert", test);
    }).listen(port);
    

PHP连接MongoDB

<?php

doSomething(); // 这次操作会新建一个连接
echo "done";
sleep(5);
doSomething(); // 这次操作会复用旧的连接
echo "after a long long time"; // 假设过了很久的时间都没有MongoDB访问 服务器端已将该连接关闭
doSomething(); // 这次操作在尝试复用旧的连接发现已失效然后再新建一个连接


function doSomething() {
    $mongoDB = getMongoDB();
    $mongoCollection = $mongoDB->selectCollection('test');
    $array = array(
       'key1' => 'value1',
    );
    $mongoCollection->insert($array);
    $mongoCursor = $mongoCollection->find();
    while($mongoCursor->hasNext()) {
        print_r($mongoCursor->getNext());
    }
}

//如果没有可用的connection将会新建一个
//如果有可用的connection将会尝试直接复用旧的
//但如果旧的因为长期不活跃已被server端关闭的话,会再重新连接
//这样可以减少不断创建新连接的开销
function getMongoDB() {
    $host = 'mongo.duapp.com';
    $port = '8908';
    $dbname = "<DB_Name>"; // 数据库名称
    $user = "<User_AK>"; // 用户AK
    $pwd = "<User_SK>"; // 用户SK

    $retry = 3;
    do {
        try {
            $mongoClient = new MongoClient("mongodb://{$user}:{$pwd}@{$host}:{$port}/{$dbname}");
            return $mongoClient->selectDB($dbname);
        } catch (Exception $e) {
            $retry--;
        }
    } while($retry > 0);
}

PHP复制MongoDB数据库集合

<?php

$dbname = '<DB_Name>'; //数据库名称
$host   = 'mongo.duapp.com';
$port   = '8908';
$user   = '<User_AK>'; //用户AK
$pwd    = '<User_SK>'; //用户SK

try {
    $mongoClient = new MongoClient("mongodb://{$host}:{$port}");
    $mongoDB     = $mongoClient->selectDB($dbname);
    $mongoDB->authenticate($user, $pwd);

    $mongoCollection  = $mongoDB->selectCollection('test_collection');
    $targetCollection = $mongoDB->selectCollection('test_collection_copy');

    $mongoCursor = $mongoCollection->find();
    while ($mongoCursor->hasNext()) {
        $line = $mongoCursor->getNext();
        $ret  = $targetCollection->insert($line);
    }
}
catch (Exception $e) {
    die($e->getMessage());
}    

?>

Python连接MongoDB

#-*- coding:utf-8 -*-

import pymongo
from pymongo import MongoClient

def test_mongo():
    ### 连接MongoDB服务
    ### 从管理控制台获取host, port, db_name, api_key, secret_key
    con = MongoClient("mongo.duapp.com", 8908)  
    db_name = "<DB_Name>" # 数据库名称
    db = con[db_name]
    api_key = "<User_AK>" # 用户AK
    secret_key = "<User_SK>" # 用户SK
    db.authenticate(api_key, secret_key)

    ### 插入数据到集合test
    collection_name = 'test'
    db[collection_name].insert({"id":10, 'value':"test test"})

    ### 查询集合test
    cursor = db[collection_name].find()
    con.close()
    return "select collection test %s"%str(cursor[0])

def app(environ, start_response):
    status = '200 OK'
    headers = [('Content-type', 'text/html')]
    start_response(status, headers)
    try:
        return test_mongo()
    except Exception as e:
        return "exception"

from bae.core.wsgi import WSGIApplication
application = WSGIApplication(app)

Java连接MongoDB

/**
* 以mongo-java-driver 2.13.2为例
*/
import com.mongodb.*;

import java.io.IOException;

import java.util.Arrays;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MongoTestHttp extends HttpServlet {

    private static DB db;
    private static MongoCredential credential;
    private static MongoClient mongoClient;

    private static String host = "mongo.duapp.com";
    private static int port = 8908;
    private static String dbname = "<DB_Name>"; // 数据库名称
    private static String user = "<User_AK>"; // 用户AK
    private static String pwd = "<User_SK>"; // 用户SK

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        try {
            DB db = getMongoDB();
            DBCollection coll = db.getCollection("test");
            DBObject data = new BasicDBObject();
            data.put("key", "value");
            coll.insert(data);
            DBObject myDoc = coll.findOne();
            resp.getWriter().print(myDoc.toString());
        } catch (Exception e) {
            e.printStackTrace(resp.getWriter());
        }
    }

    public DB getMongoDB() throws Exception{
        int retry = 3;
        do {
            try {
                // 第一次调用会新建connection
                if (db == null) {
                    credential = MongoCredential.createCredential(user, dbname, pwd.toCharArray());
                    mongoClient = new MongoClient(new ServerAddress(host, port), Arrays.asList(credential));
                    db = mongoClient.getDB(dbname);
                    return db;
                }
                else {
                    // 如果ping成功,则connection仍然活跃可以直接重用
                    // 如果因为长时间空闲connetion已被服务器端关闭,此处将抛出异常,在重试环节重新建立连接
                    // 下一次ping将会成功
                    db.command(new BasicDBObject("ping","1"));
                    return db;
                }
            } catch(Exception e) {
                retry--;
            }
        } while (retry>0);

        throw new Exception("failed to connect mongodb");
    }
}

数据库访问说明

  • 请求:为了防止恶意攻击,MongoDB服务采用分钟配额来限制数据库的访问,超配额的数据库将被封禁5分钟。

    • 请求数:200000个/分钟
    • 流入流量:300MB/分钟
    • 流出流量:600MB/分钟
  • 连接数:每个数据库并发连接数将视系统繁忙情况动态调整,范围为10—50。
  • 容量:每个数据库有各自的容量限制,超过配额后,数据库将会被封禁。请提前作好监控并及时扩容。

注意: BAE的数据库扩展服务只能被部署在BAE中的应用访问,无法通过公网或BAE的本地开发环境访问。