热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

通过MongoDB客户端samus代码研究解决问题

最近有项目需要用到mongodb,于是在网上下载了mongodb的源码,根据示例写了测试代码,但发现一个非常奇怪的问题:插入记录的速度比获取数据的速度还要快,而且最重要的问题是获取数据的速度无法让人接受。测试场景:主文档存储人员基本信息,子文档一存储学

  最近有项目需要用到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= "mongodb://localhost"; ),然后发现有两个重要的参数:

   1:COnnectionLifetime=300000,从字面意思来看,是说连接的生命周期,而它的数值设置如此大,显然说明此连接不会被立即关闭,这和sql server的做法有所区别;

   2:Pooled=true,从字面意思来看,应该是有连接池的概念。

   分析:从上面的连接参数来看,我之前所理解的连接,就是客户端与服务端之间的连接,它需要在使用完之后马上关闭,即客户端与服务端不在有tcp连接。但我没有很好的理解连接池的作用。连接池实际上从存储很多个已经和服务端建立tcp连接的connection,在它的生命周期内一直保持和服务端的连接,生命周期过后会变成失效连接等待回收。

   重新修改连接字符串再进行测试,问题解决,只有第一次请求时,由于需要创建tcp连接,性能会受影响,后面的请求,因为有连接池的存在,性能得到成倍提高。

   最后看了下samus源码,就可以看出它是如何使用连接池的。

   先看下我写的一个mongodb的帮助类:里面有创建Mongo对象等常规操作。

public class MongodbFactory2: 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 mongoCollection;

        public  MongodbFactory2()

        {      

mOngo= GetMongo();

mOngoDatabase= mongo.GetDatabase(databaseName) as MongoDatabase;

mOngoCollection= mongoDatabase.GetCollection() as MongoCollection;

mongo.Connect();

        }

        public void Dispose()

        {

this.mongo.Disconnect();

        }

        ///

 

        /// 配置Mongo,将类T映射到集合 

        ///

 

        private Mongo GetMongo()

        {

var cOnfig= new MongoConfigurationBuilder();

config.Mapping(mapping =>

{

mapping.DefaultProfile(profile =>

{

profile.SubClassesAre(t => t.IsSubclassOf(typeof(T)));

});

mapping.Map();

});

config.ConnectionString(connectionString);

return new Mongo(config.BuildConfiguration());

        }

     从上面的代码中可以看到有这么一句:mongo.Connect(),我第一印象就是创建客户端与服务端的连接,其实有了连接池,这个操作并非每次都创建远程连接,有的情况只是从连接池中直接返回可用连接对象而已。

   从源码分析是如何利用连接池,连接是如何创建的。

   1:Mongo类的Connect函数:需要跟踪_connection对象。

///

        ///   Connects to server.

        ///

        ///

        /// Thrown when connection fails.

        public void Connect()

        {

_connection.Open();

        }

    2:再看这句:return new Mongo(config.BuildConfiguration());

///

        ///   Initializes a new instance of the class.

        ///

        /// The mongo configuration.

        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这个参数了,这的值就是连接串中的参数。

///

        /// Creates the factory.

        ///

        /// The connection string.

        ///

        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()

///

        /// Ensures the size of the minimal pool.

        ///

        private void EnsureMinimalPoolSize()

        {

lock(_syncObject)

while(PoolSize

_freeConnections.Enqueue(CreateRawConnection());

        }

    5:真正远程连接部分。

///

        /// Creates the raw connection.

        ///

        ///

        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 _authenticatedDatabases = new List();

        private bool _isDisposed;

        ///

        /// Initializes a new instance of the class.

        ///

        /// The end point.

        /// The connection timeout.

        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,如果发现连接已经过期,则将连接放入不可用队列,将此连接从空闲连接中删除掉。

///

        /// Checks the free connections alive.

        ///

        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);

}

        }

     ///

        /// Determines whether the specified connection is alive.

        ///

        /// The connection.

        ///

        ///  true if the specified connection is alive; otherwise, false.

        ///

        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()

///

        ///   Disconnects this instance.

        ///

        ///

        public bool Disconnect()

        {

_connection.Close();

return _connection.IsConnected;

        }

        继续往下就会定位到如下核心内容:

///

        ///   Returns the connection.

        ///

        /// The connection.

        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本身问题,也非网络,非数据问题,而是在于没有正确使用好客户端连接,不容易啊,在此谢谢老代的指点。


推荐阅读
  • MongoDB核心概念详解
    本文介绍了NoSQL数据库的概念及其应用场景,重点解析了MongoDB的基本特性、数据结构以及常用操作。MongoDB是一个高性能、高可用且易于扩展的文档数据库系统。 ... [详细]
  • 本文介绍了如何使用Node.js通过两种不同的方法连接MongoDB数据库,包括使用MongoClient对象和连接字符串的方法。每种方法都有其特点和适用场景,适合不同需求的开发者。 ... [详细]
  • H5技术实现经典游戏《贪吃蛇》
    本文将分享一个使用HTML5技术实现的经典小游戏——《贪吃蛇》。通过H5技术,我们将探讨如何构建这款游戏的两种主要玩法:积分闯关和无尽模式。 ... [详细]
  • 在CentOS 7环境中安装配置Redis及使用Redis Desktop Manager连接时的注意事项与技巧
    在 CentOS 7 环境中安装和配置 Redis 时,需要注意一些关键步骤和最佳实践。本文详细介绍了从安装 Redis 到配置其基本参数的全过程,并提供了使用 Redis Desktop Manager 连接 Redis 服务器的技巧和注意事项。此外,还探讨了如何优化性能和确保数据安全,帮助用户在生产环境中高效地管理和使用 Redis。 ... [详细]
  • V8不仅是一款著名的八缸发动机,广泛应用于道奇Charger、宾利Continental GT和BossHoss摩托车中。自2008年以来,作为Chromium项目的一部分,V8 JavaScript引擎在性能优化和技术创新方面取得了显著进展。该引擎通过先进的编译技术和高效的垃圾回收机制,显著提升了JavaScript的执行效率,为现代Web应用提供了强大的支持。持续的优化和创新使得V8在处理复杂计算和大规模数据时表现更加出色,成为众多开发者和企业的首选。 ... [详细]
  • Oracle字符集详解:图表解析与中文乱码解决方案
    本文详细解析了 Oracle 数据库中的字符集机制,通过图表展示了不同字符集之间的转换过程,并针对中文乱码问题提供了有效的解决方案。文章深入探讨了字符集配置、数据迁移和兼容性问题,为数据库管理员和开发人员提供了实用的参考和指导。 ... [详细]
  • 小王详解:内部网络中最易理解的NAT原理剖析,挑战你的认知极限
    小王详解:内部网络中最易理解的NAT原理剖析,挑战你的认知极限 ... [详细]
  • MongoDB高可用架构:深入解析Replica Set机制
    MongoDB的高可用架构主要依赖于其Replica Set机制。Replica Set通过多个mongod节点的协同工作,实现了数据的冗余存储和故障自动切换,确保了系统的高可用性和数据的一致性。本文将深入解析Replica Set的工作原理及其在实际应用中的配置和优化方法,帮助读者更好地理解和实施MongoDB的高可用架构。 ... [详细]
  • Java虚拟机及其发展历程
    Java虚拟机(JVM)是每个Java开发者日常工作中不可或缺的一部分,但其背后的运作机制却往往显得神秘莫测。本文将探讨Java及其虚拟机的发展历程,帮助读者深入了解这一关键技术。 ... [详细]
  • Asynchronous JavaScript and XML (AJAX) 的流行很大程度上得益于 Google 在其产品如 Google Suggest 和 Google Maps 中的应用。本文将深入探讨 AJAX 在 .NET 环境下的工作原理及其实现方法。 ... [详细]
  • 本文详细介绍了Oracle 11g中的创建表空间的方法,以及如何设置客户端和服务端的基本配置,包括用户管理、环境变量配置等。 ... [详细]
  • 本文介绍了SIP(Session Initiation Protocol,会话发起协议)的基本概念、功能、消息格式及其实现机制。SIP是一种在IP网络上用于建立、管理和终止多媒体通信会话的应用层协议。 ... [详细]
  • 华为与红帽联手,加速开源电信软件革新
    华为与红帽携手合作,旨在加速开源电信软件的发展,以满足大型电信运营商对灵活网络解决方案的需求。 ... [详细]
  • CentOS下ProFTPD的安装与配置指南
    本文详细介绍在CentOS操作系统上安装和配置ProFTPD服务的方法,包括基本配置、安全设置及高级功能的启用。 ... [详细]
  • 解决JavaScript中法语字符排序问题
    在开发一个使用JavaScript、HTML和CSS的Web应用时,遇到从SQLite数据库中提取的法语词汇排序不正确的问题,特别是带重音符号的字母未按预期排序。 ... [详细]
author-avatar
刘刘刘存乐_626
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有