通过mongodb客户端samus代码研究解决查询慢问题
最近有项目需要用到mongodb,于是在网上下载了mongodb的源码,根据示例写了测试代码,但发现一个非常奇怪的问题:插入记录的速度比获取数据的速度还要快,而且最重要的问题是获取数据的速度无法让人接受。
测试场景:主文档存储人员基本信息,子文档一存储学生上课合同数据集合,这个集合多的可达到几百,子文档二存储合同的付款记录集合,集合大小一般不会超过50。根据人员ID查询人员文档,序列化后的大小为180K不到,但消耗的时间在400ms以上。
我的主要问题在于不能接收获取一个180K的记录需要400ms以上,这比起传统的RDBMS都没有优势,而且mongodb也是内存映射机制,没道理性能如此之差,而且网络上关于它的性能测试数据远远好于我的测试结果。
排除方式一:是不是因为有子文档的原因?
找一个没有任何合同记录的文档查询,发现结果依旧,没有明显的改善;
排除方式二:没有创建索引?
在搜索列ID上创建索引,结果依旧;
排除方式三:是不是文档数量过大?
一万多行只是小数目,没理由,mongodb管理上千万的文档都是没有问题的,于时还是决定试一试,将记录全部删除,插入一条记录然后查询,结果依旧;
排除方式四:是不是由于客户端序列化的问题?
由于我存储的是自定义的对象,不是默认的Document,所以决定尝试直接存储Document,Document就两个字段,获取速度还是需要180ms。
排除方式五:是否由于客户机器是32位,而mongodb服务是64?
将程序放在64位机器上测试,问题依旧。
排除方式六:是否由于网络传输问题?
没道理啊,测试的客户端以及服务端均在同一局域网,但还是尝试将客户端程序直接在mongodb服务器上执行,问题一样;
上面的六种方式都已经尝试过,没有解决,最后决定求助于老代,毕竟是用过mongodb的高人,给我两个建议就搞定了:
排除方式七:查看mongodb数据文件,看是否已经很大?
经查看,总大小才64M,这比32位文件上限的2G来讲,可以基本忽略;
排除方式八:连接字符串。
Servers=IP:27017;ConnectTimeout=30000;ConnectionLifetime=300000;MinimumPoolSize=8;MaximumPoolSize=256;Pooled=true
我一看到这个参考字符串,第一印象是,我的写法和它不一样(string connectionString =
""
;
),然后发现有两个重要的参数:
1:ConnectionLifetime=300000,从字面意思来看,是说连接的生命周期,而它的数值设置如此大,显然说明此连接不会被立即关闭,这和sql server的做法有所区别;
2:Pooled=true,从字面意思来看,应该是有连接池的概念。
分析:从上面的连接参数来看,我之前所理解的连接,就是客户端与服务端之间的连接,它需要在使用完之后马上关闭,即客户端与服务端不在有tcp连接。但我没有很好的理解连接池的作用。连接池实际上从存储很多个已经和服务端建立tcp连接的connection,在它的生命周期内一直保持和服务端的连接,生命周期过后会变成失效连接等待回收。
重新修改连接字符串再进行测试,问题解决,只有第一次请求时,由于需要创建tcp连接,性能会受影响,后面的请求,因为有连接池的存在,性能得到成倍提高。
最后看了下samus源码,就可以看出它是如何使用连接池的。
先看下我写的一个mongodb的帮助类:里面有创建Mongo对象等常规操作。
public class MongodbFactory2<T>: IDisposable where T : class
{
//public string connectionString = "mongodb://10.1.55.172";
public string connectionString = ConfigurationManager.AppSettings["mongodb"];
public string databaseName = "myDatabase";
Mongo mongo;
MongoDatabase mongoDatabase;
public MongoCollection<T> mongoCollection;
public MongodbFactory2()
{
mongo = GetMongo();
mongoDatabase = mongo.GetDatabase(databaseName) as MongoDatabase;
mongoCollection = mongoDatabase.GetCollection<T>() as MongoCollection<T>;
mongo.Connect();
}
public void Dispose()
{
this.mongo.Disconnect();
}
/// <summary>
/// 配置Mongo,将类T映射到集合
/// </summary>
private Mongo GetMongo()
{
var config = new MongoConfigurationBuilder();
config.Mapping(mapping =>
{
mapping.DefaultProfile(profile =>
{
profile.SubClassesAre(t => t.IsSubclassOf(typeof(T)));
});
mapping.Map<T>();
});
config.ConnectionString(connectionString);
return new Mongo(config.BuildConfiguration());
}
从上面的代码中可以看到有这么一句:mongo.Connect(),我第一印象就是创建客户端与服务端的连接,其实有了连接池,这个操作并非每次都创建远程连接,有的情况只是从连接池中直接返回可用连接对象而已。
从源码分析是如何利用连接池,连接是如何创建的。
1:Mongo类的Connect函数:需要跟踪_connection对象。
/// <summary>
/// Connects to server.
/// </summary>
/// <returns></returns>
/// <exception cref = "MongoDB.MongoConnectionException">Thrown when connection fails.</exception>
public void Connect()
{
_connection.Open();
}
2:再看这句:return new Mongo(config.BuildConfiguration());
/// <summary>
/// Initializes a new instance of the <see cref = "Mongo" /> class.
/// </summary>
/// <param name = "configuration">The mongo configuration.</param>
public Mongo(MongoConfiguration configuration){
if(configuration == null)
throw new ArgumentNullException("configuration");
configuration.ValidateAndSeal();
_configuration = configuration;
_connection = ConnectionFactoryFactory.GetConnection(configuration.ConnectionString);
}
上面代码的最后一句有_connection的生成过程。
3:可以跟踪到最终生成connection的函数,终于看到builder.Pooled这个参数了,这的值就是连接串中的参数。
/// <summary>
/// Creates the factory.
/// </summary>
/// <param name="connectionString">The connection string.</param>
/// <returns></returns>
private static IConnectionFactory CreateFactory(string connectionString){
var builder = new MongoConnectionStringBuilder(connectionString);
if(builder.Pooled)
return new PooledConnectionFactory(connectionString);
return new SimpleConnectionFactory(connectionString);
}
4:再看PooledConnectionFactory是如何创建连接的:这的作用就是将可用连接放入连接池中,而最终真正创建连接的函数是CreateRawConnection()
/// <summary>
/// Ensures the size of the minimal pool.
/// </summary>
private void EnsureMinimalPoolSize()
{
lock(_syncObject)
while(PoolSize < Builder.MinimumPoolSize)
_freeConnections.Enqueue(CreateRawConnection());
}
5:真正远程连接部分。
/// <summary>
/// Creates the raw connection.
/// </summary>
/// <returns></returns>
protected RawConnection CreateRawConnection()
{
var endPoint = GetNextEndPoint();
try
{
return new RawConnection(endPoint, Builder.ConnectionTimeout);
}catch(SocketException exception){
throw new MongoConnectionException("Failed to connect to server " + endPoint, ConnectionString, endPoint, exception);
}
}
private readonly TcpClient _client = new TcpClient();
private readonly List<string> _authenticatedDatabases = new List<string>();
private bool _isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="RawConnection"/> class.
/// </summary>
/// <param name="endPoint">The end point.</param>
/// <param name="connectionTimeout">The connection timeout.</param>
public RawConnection(MongoServerEndPoint endPoint,TimeSpan connectionTimeout)
{
if(endPoint == null)
throw new ArgumentNullException("endPoint");
EndPoint = endPoint;
CreationTime = DateTime.UtcNow;
_client.NoDelay = true;
_client.ReceiveTimeout = (int)connectionTimeout.TotalMilliseconds;
_client.SendTimeout = (int)connectionTimeout.TotalMilliseconds;
//Todo: custom exception?
_client.Connect(EndPoint.Host, EndPoint.Port);
}
接着我们来看下,连接的生命周期是如何实现的:主要逻辑在PooledConnectionFactory,如果发现连接已经过期,则将连接放入不可用队列,将此连接从空闲连接中删除掉。
/// <summary>
/// Checks the free connections alive.
/// </summary>
private void CheckFreeConnectionsAlive()
{
lock(_syncObject)
{
var freeConnections = _freeConnections.ToArray();
_freeConnections.Clear();
foreach(var freeConnection in freeConnections)
if(IsAlive(freeConnection))
_freeConnections.Enqueue(freeConnection);
else
_invalidConnections.Add(freeConnection);
}
}
/// <summary>
/// Determines whether the specified connection is alive.
/// </summary>
/// <param name="connection">The connection.</param>
/// <returns>
/// <c>true</c> if the specified connection is alive; otherwise, <c>false</c>.
/// </returns>
private bool IsAlive(RawConnection connection)
{
if(connection == null)
throw new ArgumentNullException("connection");
if(!connection.IsConnected)
return false;
if(connection.IsInvalid)
return false;
if(Builder.ConnectionLifetime != TimeSpan.Zero)
if(connection.CreationTime.Add(Builder.ConnectionLifetime) < DateTime.Now)
return false;
return true;
}
最后我们来看我最上面的mongodb帮忙类的如下方法:即释放连接,而这里的释放也不是直接意义上将连接从客户端与服务端之间解除,只不过是将此连接从忙队列中删除,重新回归到可用队列:
public void Dispose()
{
this.mongo.Disconnect();
}
再看看mongo.Disconnect()
/// <summary>
/// Disconnects this instance.
/// </summary>
/// <returns></returns>
public bool Disconnect()
{
_connection.Close();
return _connection.IsConnected;
}
继续往下就会定位到如下核心内容:
/// <summary>
/// Returns the connection.
/// </summary>
/// <param name = "connection">The connection.</param>
public override void Close(RawConnection connection)
{
if(connection == null)
throw new ArgumentNullException("connection");
if(!IsAlive(connection))
{
lock(_syncObject)
{
_usedConnections.Remove(connection);
_invalidConnections.Add(connection);
}
return;
}
lock(_syncObject)
{
_usedConnections.Remove(connection);
_freeConnections.Enqueue(connection);
Monitor.Pulse(_syncObject);
}
}
总结:经过各位不同的尝试,终于解决了mongodb查询慢的原因,并非mongodb本身问题,也非网络,非数据问题,而是在于没有正确使用好客户端连接,不容易啊,在此谢谢老代的指点。
参考资料:
http://www.360doc.com/content/16/0720/17/35239163_577069265.shtml
monogodb find 方法调用javascript where
通过mongodb客户端samus代码研究解决查询慢问题的更多相关文章
- Ningx代码研究.
概述 研究计划 参与人员 研究文档 学习emiller的文章 熟悉nginx的基本数据结构 nginx 代码的目录结构 nginx简单的数据类型的表示 nginx字符串的数据类型的表示 内存分配相关 ...
- 10 行 Python 代码实现模糊查询/智能提示
10 行 Python 代码实现模糊查询/智能提示 1.导语: 模糊匹配可以算是现代编辑器(如 Eclipse 等各种 IDE)的一个必备特性了,它所做的就是根据用户输入的部分内容,猜测用户想要的 ...
- CentOS6.3安装MongoDB2.2 及 安装PHP的MongoDB客户端
下载源码:(放到 /usr/local/src 目录下) 到官网 http://www.mongodb.org/downloads 下载源码 https://fastdl.mongodb.org/li ...
- 关于github在mac 10.10上无法提交代码的解决办法---备用
接下来是正文:本文主要说明在mac 10.10版本下github无法提交代码的问题 首先如果你是一个用终端提交代码的,你可以不用看这篇文章了,这篇文章主要是用于解决github客户端提交代码的问题,此 ...
- Python 代码实现模糊查询
Python 代码实现模糊查询 1.导语: 模糊匹配可以算是现代编辑器(如 Eclipse 等各种 IDE)的一个必备特性了,它所做的就是根据用户输入的部分内容,猜测用户想要的文件名,并提供一个推荐列 ...
- wxpython分割窗研究(解决sashPosition=0无效的BUG)
用wxpython开发一个简单的exe其实很简单的,但是在开发的过程中会遇到若干的坑.疑问.甚至bug,让人摸不清头脑!恰恰关于这方面的文档是少之又少,看来看去大家还是在官方的文档上加以引用说明,但是 ...
- linux下MongoDB客户端shell基本操作
MongoDB 是一款NoSql数据库,没有固定的模式,即同一个集合中的不同文档结构可以不同,如:第一条记录{name:”xiaoming”},第二条记录:{name:”xiaoli”,age:15} ...
- Elasticsearch集群搭建及使用Java客户端对数据存储和查询
本次博文发两块,前部分是怎样搭建一个Elastic集群,后半部分是基于Java对数据进行写入和聚合统计. 一.Elastic集群搭建 1. 环境准备. 该集群环境基于VMware虚拟机.CentOS ...
- 从代码上解决Jenkins 发送邮件中文乱码问题
在实践中,使用Jenkins发送测试报告,收到邮件,邮件内容中的中文为乱码,邮件发送的方式是在Jenkins发邮件设置中设置邮件内容为:${FILE,path="report_ug.html ...
随机推荐
- asp.net mvc中应用缓存依赖文件(xml)的一个小demo
最近项目中加了一个通用模块,就是根据一些特殊的tag,然后根据处理这些tag在同一个视图中加载不同的model(个人觉得此功能无任何意义,只是把不同的代码放在了同一个View中). 我的处理思路是这样 ...
- 利用Quartz2D推图的另一个方法 (使用CGMutalePathRef进行分层次)
可以利用 CGMutablePathRef 创建每个不同图形,然后再一起添加到CGContext中 - (void)drawRect:(CGRect)rect { CGContextRef ctx = ...
- ZooKeeper分布式集群安装
我特意选择了稳定版...... 奇数意思是说奇数和偶数对故障的容忍度是一致的....所以建议配置奇数个,并不是必须奇数... 一.master节点上安装配置 1.下载并解压ZooKeeper-3.4. ...
- hdu 5241 数学题= =
题意:balabala 题意里给出了好多集合之间的关系,一开始以为要用离散一步一步推什么的... [然而其实并没有什么卵用 对于每一种语言来说,这种语言谁会谁不会是的方案数一定,而且语言之间相互独立的 ...
- Uva10881 Piotr's Ants
蚂蚁相撞会各自回头.←可以等效成对穿而过,这样移动距离就很好算了. 末状态蚂蚁的顺序和初状态其实是相同的. 那么剩下的就是记录每只蚂蚁的标号,模拟即可. /*by SilverN*/ #include ...
- <转>iOS9 Day-by-Day:iOS开发者必须了解的iOS 9新技术与API
iOS9 Day-by-Day是作者Chris Grant新开的一个系列博客,覆盖了iOS开发者必须知道的关于iOS 9的新技术与API,并且还进行了实际操作演练,每篇文章中相关的代码Chris都会将 ...
- CentOS编译安装nodejs
1. 从node.js官网下载最新版的node.js安装包,node.tar.gz wget https://nodejs.org/dist/v4.3.1/node-v4.3.1.tar.gz ...
- python实现自动输入命令回车操作
苦逼的在sf上等了一天(问题链接),都没人来解答,只好自己想办法,东平西凑还是勉强实现了,记录一下: 安装完python2.7后,在cmd命令行输入python回车,后出现python相关的提示信息, ...
- scala break & continue
Scala没有提供break和continue,我们可以自己实现一个,参考例子: import util.control.Breaks._ object BreakDemo { def main(ar ...
- 当spring 容器初始化完成后执行某个方法
在做web项目开发中,尤其是企业级应用开发的时候,往往会在工程启动的时候做许多的前置检查. 比如检查是否使用了我们组禁止使用的Mysql的group_concat函数,如果使用了项目就不能启动,并指出 ...