MongoDB PHP Driver的连接处理解析

1.3版本的PHP MongoDB
driver重写了连接处理库,和以前版本相比,在持久连接和连接池方面,都有了重大的变化。

 

1.2版本的连接管理

1.2版本的驱动引入了连接池,在执行任何查询时,都会从连接池中请求一个连接,完成之后再归还给连接池。这里的完成是指持有该连接的变量离开了它的作用域,下面是一个示例。

最简单的版本:

<?php
$m = new MongoClient();    // ← 从连接池请求连接
$c = $m->demo->test;
$c->insert( array( 'test' => 'yes' ) );
?>

← $m离开作用域,连接归还给连接池

在函数中:

<?php
function doQuery()
{
        $m = new MongoClient();    // ← 从连接池请求连接
        $c = $m->demo->test;
        $c->insert( array( 'test' => 'yes' ) );
} // ← $m离开作用域,连接归还给连接池
?>

在某些情况下,系统可能会产生大量的连接,比如在ORMs/ODMs的某个复杂结构中引用连接对象,如下例子:

<?php
for ( $i = 0; $i < 5; $i++ )
{
        $conns[] = new MongoClient();
}// ← 现在有5个连接
?>

要访问数据库,那么对访问权限的设置是必须的!

1.3版本的连接管理

在1.3版本中,连接管理做了很大改动。每个worker进程(线程、PHP-FPM或Apache
worker)中,驱动把连接管理和Mongo*对象分离,降低驱动的复杂度。下面以单个节点的MongoDB实例来说明驱动如何处理连接。

当一个worker进程启动,MongoDB驱动会为之初始化连接管理器管理连接,并且默认没有连接。

在第一个请求调用new
MongoClient();时,驱动创建一个新连接,并且以一个哈希值标识这个连接。这个哈希值包括以下参数:主机名、端口,进程ID和可选的replica
set名,如果是密码验证的连接,则还包括数据库名、用户名和密码的哈希值(对于密码验证的连接,我们后面再详细讨论)。调用MongoClient::getConnections()方法,可以查看连接对应的哈希值:

<?php
$m = new MongoClient( 'mongodb://whisky:27017/' );
var_dump( $m->getConnections()[0]['hash'] );
?>

输出:

string(22) “whisky:27017;-;X;22835″

输出中的”-”表示该连接不属于某个replica
set,”X”是没有用户名、数据库和密码时的占位符,22835是当前进程的进程ID。

然后该连接会在连接管理器中注册: 图片 1

在需要连接的任何时候,包括插入、删除、更新、查找或执行命令,驱动都会向管理器请求一个合适的连接来执行。请求连接时会用到new
MongoClient()的参数和当前进程的ID。每个worker进程/线程,连接管理器都会有一个连接列表,而每个PHP
worker同一时刻,只会运行一个请求,因此和每个MongoDB之间只需要一个连接,不断重用,直到PHP
worker终止或显式调用MongoClient::close()关闭连接。

1、启用权限控制(-auth),当启用MongoDB数据库服务时,对参数的设置可以决定是否启用权限控制
   不开启: mongod -dbpath=D:/MongoDB/Data
-logpath=D:/MongoDB/Log/test.log
    开启: mongod -dbpath=D:/MongoDB/Data
-logpath=D:/MongoDB/Log/test.log -auth

Replica sets

在存在复制集的环境中,情形有点不一样。new
MongoClient()的连接字符串中,需要指定多个hosts,并标示当前正在实用复制集:

$m = new
MongoClient(“mongodb://whisky:13000,whisky:13001/?replicaSet=seta”);

其中的replicaSet参数不能省略,否则驱动会认为你是准备连接三个不同的mongos进程。

在实例化时,驱动会检查复制集的拓扑结构。下面例子的输出,显示在调用new
MongoClient()之后,复制集中所有可见的数据节点都会在管理器中注册一个连接:

<?php
$m = new MongoClient( 'mongodb://whisky:13001/?replicaSet=seta' );
foreach ( $m->getConnections() as $c )
{
    echo $c['hash'], "n";
}
?>

输出:

whisky:13001;seta;X;32315 whisky:13000;seta;X;32315

虽然连接字符串中没有whisky:13000节点,但是管理器中已经注册了两个连接:

图片 2

管理器不仅包含连接的哈希值和TCP/IP
socket,还保存哪个节点是主节点,以及每个节点的“距离”。下面的脚本显示了这些额外的信息;

<?php
$m = new MongoClient( 'mongodb://whisky:13001/?replicaSet=seta' );
foreach ( $m->getConnections() as $c )
{
    echo $c['hash'], ":n",
        " - {$c['connection']['connection_type_desc']}, ",
        "{$c['connection']['ping_ms']} msn";
}
?>

输出:

whisky:13001;seta;X;5776: – SECONDARY, 1 ms whisky:13000;seta;X;5776:
– PRIMARY, 0 ms

驱动把操作分为两种类型:写操作,包括插入、更新、删除和命令;读操作,包括find和findOne。默认情况下,如果没有设置读偏好参数,管理器会一直返回主节点的连接。读偏好参数可以通过setSlaveOkay()设置,也可以在连接字符串中设置:

$m = new MongoClient("mongodb://whisky:13000,whisky:13001/?replicaSet=seta&readPreference=secondaryPreferred");

加上这些参数后,连接字符串变得特别长,因此PHP驱动允许将选项放在数组中,作为第二个参数传入:

$options = array(
        'replicaSet' => 'seta',
        'readPreference' => 'secondaryPreferred',
);
$m = new MongoClient("mongodb://whisky:13000,whisky:13001/", $options);

对于每个操作,驱动向管理器请求获取一个合适的连接。对于写操作,会一直返回主节点的连接;对于读操作,如果辅助节点可用且“距离”不远的话,则会返回该辅助节点的连接。

    由上面的两条开启服务的命令来看,是由启动命令mongod的参数
-auth决定,如果不开启,那么对数据库都可以访问。如果开启,那么就需要输入用户名和密码(-auth(‘用户’,’密码’))来进行授权,如果正确的则返回1,否则返回0

验证的连接

如果MongoDB启用验证功能,那么连接的哈希值会包含验证相关的哈希值。这样不同脚本,使用不同的用户名、密码连接同一个MongoDB上的不同的数据库时,能够相互区分,而不会误用连接。下面示例使用admin用户名连接admin数据库,然后观察hash值的变化:

<?php
$m = new MongoClient( 'mongodb://admin:admin@whisky:27017/admin' );
var_dump( $m->getConnections()[0]['hash'] );
?>

输出:

string(64)
“whisky:27017;-;admin/admin/bda5cc70cd5c23f7ffa1fda978ecb class=”wp_keywordlink”>D30;8697″

以前示例中的”X”部分已经替换为一个包含数据库名admin、用户名admin和哈希值bda5cc70cd5c23f7ffa1fda978ecbd30,该哈希值是根据用户名、数据库名和密码哈希值计算得来。

为了验证能够正确工作,需要在连接字符串中包含数据库名,否则会默认为admin。

在建立连接后要使用数据库,需要先选择该数据库,如:

$collection = $m->demoDb->collection; $collection->findOne();

如果选择的数据库是连接字符串中指定的数据库,或者连接字符串中的数据库是admin,那么一切都会正常运行。否则,驱动会创建一个新的连接,从而防止验证被绕过,如下所示:

<?php
$m = new MongoClient( 'mongodb://user:user@whisky:27017/test' );

$db = $m->test2;
$collection = $db->collection;
var_dump( $collection->findOne() );
?>

输出:

Fatal error: Uncaught exception ‘MongoCursorException’ with message
‘whisky:27017: unauthorized db:test2 ns:test2.collection lock type:0
client:127.0.0.1′ in …/mongo-connect-5.php.txt:6

因为我们的连接并没有执行test2数据库的授权验证,因而失败。如果我们执行验证,就会正常运行:

<?php
$m = new MongoClient( 'mongodb://user:user@whisky:27017/test' );

$db = $m->test2;
$db->authenticate('user2', 'user2' );
$collection = $db->collection;
$collection->findOne();

foreach ( $m->getConnections() as $c )
{
    echo $c['hash'], "n";
}
?>

输出:

whisky:27017;-;test/user/602b672e2fdcda7b58a042aeeb034376;26983
whisky:27017;-;test2/user2/984b6b4fd6c33f49b73f026f8b47c0de;26983

现在管理器中有两个已验证的连接:

图片 3

顺便提一句,如果你打开了E_DEPRECATED级别的错误提示,则会看到:

Deprecated: Function MongoDB::authenticate() is deprecated in
…/mongo-connect-6.php.txt on line 5

驱动建议通过创建两个MongoClient对象完成该类任务:

<?php
$mTest1 = new MongoClient( 'mongodb://user:user@whisky:27017/test', array( 'connect' => false ) );
$mTest2 = new MongoClient( 'mongodb://user2:user2@whisky:27017/test2', array( 'connect' => false ) );

$mTest1->test->test->findOne();
$mTest2->test2->test->findOne();

foreach ( $mTest2->getConnections() as $c )
{
    echo $c['hash'], "n";
}
?>

单个MongoDB服务器能支持的并发连接相当有限,如果使用PHP-FPM的话,每个worker进程有自己独立的连接池,那么很容易达到连接数的上限。因此,在生产环境中,不管有没有使用复制集,都要部署mongos,然后PHP-FPM连接mongos,这样可以减少mongod的连接数,并且PHP-FPM和mongos之间可以使用短连接(即每个请求结束时都显式调用close函数关闭MongoDB连接)。

2、既然要输入用户名和密码来进行授权,那么这个用户名和密码是如何来的呢?
  
默认情况下,数据库中是没有用户名和密码的,所以即使在开启服务时开启了权限控制,也是不需要任何用户名和密码就可以访问每个数据库。这也是必须的,因为我们要给数据库设置密码就必须要先可以访问数据库,这时就可以对我们的超级管理员某数据库管理员设置用户名和密码。

3、超级管理员和某数据库的管理员
  
超级管理员就是在admin数据库中添加的用户名和密码(可以查看数据库列表什么的….)
   某数据库管理员也就是对每个数据库添加用户名和密码

4、开始实践那么一下

  1. 开启服务(启用权限控制)
    mongod -dbpath=D:/MongoDB/Data
    -logpath=D:/MongoDB/Log/test.log -auth
    图片 4
  2. 默认没有用户名和密码的情况下是可以访问任意数据库的
    图片 5
  3. 在这此情况下,创建一个超级管理员
    输入:use admin
    输入:db.addUser(‘sa’,’123′)
    然后再访问admin数据库试试,就会出现错误提示,这是就是因为需要用户授权,当输入用户名和密码来授权之后,就OK
    图片 6
  4. 此时按Ctrl+C退出,然后再进入,用刚才创建的用户去访问admin以外的数据库,例如:test
    用admin创建的超级管理员无法直接访问其他数据库,但是可以间接的去访问。
    图片 7
  5. 利用超级管理员间接的去访问任意数据库,间接的去访问就是先通过授权进入admin数据库,然后再由admin数据库进入其他数据库
    即:以admin数据库为跳板,再进入其他数据库
    图片 8
  6. 为数据库添加用户名和密码,每个数据库都可以添加直接访问此库的用户和密码,并且是存放在本库的system.users表中的
    为test数据库创建直接访问用户:用户名=test     密码=123
    图片 9
  7. 继续第6步,按Ctrl+C退出,再次进入test数据库,并输入第6步中为test数据库创建的直接访问用户和密码来进行授权
    图片 10

    大功告成….
    此时有没有对数据库的权限问题有了些了解呢?

 5、几个简单的服务安装、服务卸载以及用户登录实例

  1. 安装:mongod -dbpath=D:/MongoDB/Data
  2. 安装:mongod -dbpath=D:/MongoDB/Data
    -logpath=D:/MongoDB/Log/test.log
  3. 安装:mongod -dbpath=D:/MonogDB/Data
    -logpath=D:/MongoDB/Log/test.log -logappend
  4. 安装:mongod -dbpath=D:/MonogDB/Data
    -logpath=D:/MongoDB/Log/test.log -logappend -fork
    卸载:db.shutdownServer()
    以上的cmd中的shell连接:mongo 或者mongo admin(指定的数据库名称)
                          C#连接:mongodb://localhost
  5. 安装:mongod -dbpath=D:/MongoDB/Data
    -logpath=D:/MongoDB/Log/test.log  port 2222 -install
    开启:net start MongoDB
    关闭:net stop MongoDB
    卸载:mongod -dbpath=D:/MongoDB/Data
    -logpath=D:/MongoDB/Log/test.log  port 2222 -remove
    命令连接:mongo 127.0.0.1:2222或mongo
    127.0.0.1:2222/admin(即:数据库名称)
     c#连接:mongodb://127.0.0.1:2222或Server=127.0.0.1:2222
  6. 安装:mongod -dbpath=D:/MongoDB/Data
    -logpath=D:/MongoDB/Log/test.log  port 2222  -auth -install
    与5类似
    命令连接:
     
    c#连接:mongodb://用户名:密码@localhost:2222   登录后将默认使用admin数据库
              mongodb://用户名:密码@localhost:2222/admin(即:数据库名称)

 

id=”articlecontent”>以下列出的uri样式并非被所有的数据库驱动所支持,请参考各驱动自己的文档,查看各驱动支持的连接字符串uri形式。如果下面的样式不支持,那么驱动会有自己的指定连接的替代方式。

id=”articlecontent”>mongodb:``//[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]

id=”articlecontent”>mongodb:``// 是一个必填的前缀,它标识当前字符串是标准连接格式。
id=”articlecontent”>{{username:password@}}可选。给出用户名和密码后,驱动将在连接到数据库后尝试登录。host1 uri里唯一的必填项,数据库的连接地址。
:portX 可选。默认连接到27017端口。
id=”articlecontent”>/database 是希望连接到的数据库名,只有在提供 username:password@ 后该值才有效。如果不指定,默认将连接``"admin"``数据库。 id=”articlecontent”>可以任意指定多个数据库和端口,这用于实现连接到replica pairs/sets。可选项options有:
  connect=direct|replicaset
   id=”articlecontent”>direct: 直接建立一个到服务器的连接。如果指定了多个host,将按先后顺序挨个尝试建立连接,直到连接建立成功为止。如果只指定了一个host,则 direct 为默认值。
   id=”articlecontent”>replicaset: 使用creplica ``set semantics建立连接(即使只提供了一个host)。指定的host作为种子列表来查找完整的replica``set``。当指定多个host时 replicaset 为默认值。
  replicaset=name
   id=”articlecontent”>驱动验证建立连接的replica ``set``的名字。隐含 connect=replicaset。
   id=”articlecontent”>slaveok=``true``|``falseo     ``true``: 对于 connect=direct 模式,驱动对列表中的第一个服务器建立连接,即使它不是主服务器。对 connect=replicaset 模式,驱动将所有写操作发送到主节点,将所有读操作按round robin顺序分发到从节点。
   id=”articlecontent”>false``: 对 connect=direct 模式,驱动按顺序尝试所有host直到找到主节点。对 connect=replicaset 模式,驱动将只连接到主节点,并将所有读操作和写操作都发送到主节点。 id=”articlecontent”>safe=``true``|``false
  true: 驱动在每次更新操作后都发送 getlasterror
命令以确保更新成功(参考 w 和 wtimeout)。
  false: 驱动每次更新操作后不发送 getlasterror 命令。

   w=no   
  驱动给 getlasterror 发送 { w : n } 命令。隐含 safe=true。

   wtimeout=ms
  驱动给 getlasterror 添加 { wtimeout : ms } 参数。隐含
safe=true。

  fsync=true|false
  true: 驱动给 getlasterror 添加 { fsync : true } 参数。隐含
safe=true。
  false: 驱动不添加 fsync 参数。

 

连接示例

连接到一个运行在本机默认端口(27017)的MongoDB
mongodb://localhost
连接到一个运行在本机默认端口(27017)的MongoDB,并以用户名"fred"和密码"foobar"登录,登录后将默认使用admin数据库
mongodb://fred:foobar@localhost
连接到一个运行在本机默认端口(27017)的MongoDB,并以用户名"fred"和密码"foobar"登录,登录后将使用baz数据库
mongodb://fred:foobar@localhost/baz
连接到一个replica pair,一台服务器在example1.com,另一台在example2.com
mongodb://example1.com:27017,example2.com:27017
连接到本机的一个replica set(端口分别为27017,27018,27019)
mongodb://localhost,localhost:27018,localhost:27019
连接到三台服务器的replica set,将所有写操作发送到主节点,所有读操作分发到从节点
mongodb://host1,host2,host3/?slaveok=true
连接到第一台服务器并响应,无论它是replica set的一台,也无论它是主节点还是从节点
mongodb://host1,host2,host3/?connect=direct;slaveok=true
注:这种类型的连接字符串可用于在你偏好使用某台服务器但有可供替换的服务器的时候。
使用safe模式连接到本机
mongodb://localhost/?safe=true
使用safe模式连接到replica set,等待备份在至少两台机器上完成,timeout时间为2秒
mongodb://host1,host2,host3/?safe=true;w=2;wtimeout=2000

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注