转载自:http://blog.itpub.net/30341463/viewspace-1758585/
1 背景介绍
在运维mongodb数据库工作中,发现研发同学编写代码访问mongodb数据库时,经常遇到一个问题:在建立初始化mongodb连接时,选择mongoclient还是Mongo类,二者究竟有什么区别?
由于mongodb驱动本身逻辑复杂,本文不做过多描述,只针对该问题,从源代码层面分析MongoClient类和Mongo类写性能差异的原因。
首先,Mongo和MongoClient类均在mongodb驱动中定义的,我们结合php和java两种语言,分别介绍这两个类的却别。
2 原因分析
首先我们来分析下php-mongodb驱动源码。
说明:以下是基于php-mongodb-1.4.5驱动版本为例介绍。
我们分别看下Mongo()和MongoClient()构造函数:
Mongo()类:
PHP_METHOD(Mongo, __construct)
{
php_mongo_ctor(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
}
MongoClient()类
PHP_METHOD(MongoClient, __construct)
{
php_mongo_ctor(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}
从构造函数来看,很明显,Mongo类(以下简称Mongo)和MongoClient类(以下简称MongoClient)构造函数,调用同一个函数:
php_mongo_ctor()
该函数原型为:
void php_mongo_ctor(INTERNAL_FUNCTION_PARAMETERS, int bc)
关于第二个参数的代码片段如下:
if (bc) {
/* Default to WriteConcern=0 for Mongo */
link->servers->options.default_w = 0; }
else {
/* Default to WriteConcern=1 for MongoClient */
link->servers->options.default_w = 1;
}
default_w是结构体mongo_servers的一个属性,意思为:写策略
很显然,Mongo默认写策略为0;MongoClient默认写策略为1
我们来看下写策略的详细介绍:
写策略 | 意义 | 描述 |
w=0 | Unacknowledged | 写操作不需要确认 |
w=1 | Acknowledged | 主库写操作需要确认 |
w=N | Replica Set Acknowledged | 主库和N-1从库写操作需要确认 |
w=majority | Majority Acknowledged | 集群半数以上节点写操作需要确认 |
w= | Replica Set Tag Set Acknowledged | 集群所有及诶单写操作均需要确认 |
表格1 写策略设置
从以上表格来看,写策略的含义是当应用写操作后,是否需要得到确认(Acknowledged),然后再继续写操作。
很显然,写策略设置为0时,不需要确认写操作是否ok,不过,会返回诸如socket异常或网络错误信息给应用程序。这种情况下,写性能明显会比较高;而当写策略设置为1时,需要副本集主库节点或者单点确认写操作ok后,再继续处理(后续)写操作,因为这个“确认”环节,降低了写性能。当写策略设置为N时,除了主库节点需要确认写操作ok,还需要N-1哥从苦节点也需要“确认”写操作ok,以此类推。
在php-mongodb驱动里,Mongo()类默认写策略设置为0,MongoClient()类写策略默认设置为1.除此以外,二者没有区别。
到这里,我们就很容易理解了,应用程序使用Mongo(0类,比MongoClient()类,写性能要高的缘由了。
我们再分析下java-mongodb驱动源代码。
说明:以下是基于mongo-java-driver-2.13.1版本来分析二者区别。
MongoClient类存在多种构造函数,如下:
public MongoClient(final ServerAddress addr, final List credentialsList, final MongoClientOptions options) {
super(addr, credentialsList, options);
}
public MongoClient(final List seeds) {
this(seeds, new MongoClientOptions.Builder().build());
}
public MongoClient(final List seeds, final List credentialsList) {
this(seeds, credentialsList, new MongoClientOptions.Builder().build());
}
public MongoClient(final List seeds, final MongoClientOptions options) {
super(seeds, options);
}
public MongoClient(final List seeds, final List credentialsList, final MongoClientOptions options) {
super(seeds, credentialsList, options);
}
无论是哪一种构造函数,都存在一个参数该参数的原型是:
MongoClientOptions()
该类调用Builder类,其构造函数片段如下:
public static class Builder {
private String description;
private ReadPreference readPreference = ReadPreference.primary();
private WriteConcern writeConcern = WriteConcern.ACKNOWLEDGED;
……
}
在这里,可以看到,WriteConcern是写策略,默认定义为:ACKNOWLEDGED,
在WriteConcern.java中,WriteConcern的取值常量为:
public static final WriteConcern ACKNOWLEDGED = new WriteConcern(1);
public static final WriteConcern UNACKNOWLEDGED = new WriteConcern(0);
public static final WriteConcern NORMAL = UNACKNOWLEDGED;
public static final WriteConcern SAFE = ACKNOWLEDGED;
很显然,写策略默认设置为1.
再来看Mongo()类的构造函数:
public Mongo(final String host) {
this(new ServerAddress(host), createLegacyOptions());
}
createLegacyOptions()函数定义如下:
private static MongoClientOptions createLegacyOptions() {
return MongoClientOptions.builder().legacyDefaults().build();
}
legacyDefaults()函数定义如下:
public Builder legacyDefaults() {
this.connectionsPerHost(10).writeConcern(WriteConcern.UNACKNOWLEDGED);
return this;
}
writeConcern()函数定义为:
public Builder writeConcern(final WriteConcern writeConcern) {
this.writeConcern = notNull(“writeConcern”, writeConcern);
return this;
}
notNull()函数定义为:
public static T notNull(final String name, final T value) {
if (value == null) {
throw new IllegalArgumentException(name + ” can not be null”);
}
return value;
}
从以上函数调用关系看,Mongo()类写策略默认值为0
从java-mongodb驱动源码看,MongoClient()类写策略默认值为1,Mongo()的写策略默认值为0,和php-mongodb源码情况一样。
3 测试验证
当修改源代码中MongoClient默认写策略为0时,重新编译驱动,进行读写测试,发现Mongo和MongoClient在写操作上,性能基本一致。
这就验证了Mongo比MongoClient写性能高的原因就在于写策略不同
测试结果如下:
MongoClient(cost) | Mongo(cost) | |
Insert | 5565.1779174805s | 674.36003684998s |
Update | 28807.404994965s | 20184.952020645s |
Find | 24.696826934814s | 22.372961044312s |
表格二 测试统计
以上是调用MongoClient类和Mongo类,分别操作10000次,耗时统计结果
很明显,写操作方面,Mongo性能高于MongoClient类,尤其是insert操作,性能提高了7倍,update性能也提高了42%;读操作性能基本一致。
4 结论
从以上分析和验证结果来看,Mongo类和MongoClient类主要区别在于写策略的默认值不同;而驱动本身提供了w参数,可以手动进行设置。如果手动设置写策略为相同值,二者在写性能上基本一致。
官方文档和源代码均建议使用MongoClient类,而且,在不久的将来,会废弃Mongo类。
但是,由于目前的php-mongodb驱动和java-mongodb驱动,均保留了Mongo类,而且,java-mongodb驱动里,MongoClient类继承了Mongo类。个人认为,两者均可以使用,如果业务对写性能要求高,可以设置写策略为0;否则,建议设置为1;不建议设置其他值