最近有项目需要用到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<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:真正远程连接部分。

View Code

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

View Code

/// <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;
        }

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

View Code

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

转载于:https://www.cnblogs.com/ASPNET2008/archive/2011/08/04/2127287.html

通过mongodb客户端samus代码研究解决问题相关推荐

  1. mongodb客户端编程

    mongodb客户端编程 2013-02-19 11:46 5428人阅读 评论(17) 收藏 举报 from : http://blog.csdn.net/fuxingdaima/article/d ...

  2. 《MongoDB入门教程》第04篇 MongoDB客户端

    本篇我们介绍 MongoDB 客户端工具 mongo 的使用. mongo shell mongo shell 是一个用于连接 MongoDB 的交互式 JavaScript 接口.mongo she ...

  3. java http 下载网页代码_Java下http下载文件客户端和上传文件客户端实例代码

    Java下http下载文件客户端和上传文件客户端实例代码 发布于 2021-1-14| 复制链接 摘记: 一.下载客户端代码 ```java package javadownload; import ...

  4. 【Groovy】使用 Groovy 语言开发服务器 Server 和客户端 Client 套接字程序 ( 服务器客户端完整代码示例 | 运行服务器端与客户端效果及过程分析 )

    文章目录 一.服务器端完整代码 Server.groovy 二.客户端完整代码 Client.groovy 三.运行服务器端与客户端效果及过程分析 一.服务器端完整代码 Server.groovy 参 ...

  5. HDFS的API调用,创建Maven工程,创建一个非Maven工程,HDFS客户端操作数据代码示例,文件方式操作和流式操作

    1. HDFS的java操作 hdfs在生产应用中主要是客户端的开发,其核心步骤是从hdfs提供的api中构造一个HDFS的访问客户端对象,然后通过该客户端对象操作(增删改查)HDFS上的文件 1.1 ...

  6. 最佳的MongoDB客户端管理工具

    <最佳的MongoDB客户端管理工具> 作者:chszs,未经博主允许不得转载.经许可的转载需注明作者和博客主页:http://blog.csdn.net/chszs 一个好的MongoD ...

  7. java socket 编程 客户机服务器_Java Socket编程服务器响应客户端实例代码

    通过输入流来读取客户端信息,相应的时候通过输出流来实现. 服务端类的代码: import java.io.BufferedReader; import java.io.IOException; imp ...

  8. java wsdl反向生成源码,并使用CXF实现客户端调用代码

    1. 查询对方提供的wsdl链接,通过该链接以及wsdl工具生成源码文件. eg: wsdl地址为:http://sersh.passport.189.cn/UDBAPPInterface/UDBAP ...

  9. 【HMS core】【Wallet Kit】【解决方案】华为钱包的客户端示例代码为何无法运行

    [问题描述] 在华为开发者联盟官方文档上下载的华为钱包(Wallet Kit)的客户端示例代码,导入到Android Studio中无法运行,下面跟着我一起来看一看究竟是怎么回事吧! 首先我们找到华为 ...

最新文章

  1. 2019微生物组—宏基因组分析专题研讨会
  2. Collection接口的常用方法
  3. Linux服务器的最大内存和CPU数
  4. Struts2 注解中跳转 action
  5. Scala函数的可变参数和参数默认值及带名参数
  6. Min_25筛学习Tip+链接
  7. Mysql数据库安全性问题【防注入】
  8. 为什么从网页上打印怎们好像被缩放_全网最详细关于3D打印的zbrush技术
  9. java : nio 学习
  10. 20个软件开发常用设计文档大全下载
  11. 面对互联网上的汩汩恶意,如何构建反欺诈体系?
  12. 第二篇、python进阶篇
  13. Elasticearch 搜索引擎(1
  14. 网站数据采集器-文章采集工具-关键词文章采集工具
  15. CentOS7查看和关闭防火墙
  16. [生命科学] snapgene 构建载体方法分享
  17. Android 接口的default 方法运行时报错AbstractMethodError
  18. Mybatis+spring知识点
  19. 7-76 查询水果价格
  20. checkbox 实现互斥选择

热门文章

  1. 网站内容收录除了原创性和质量其他因素也少不了
  2. java book打印机_Java调用打印机进行打印
  3. graphviz linux教程,程序员绘图利器 — Graphviz
  4. LINUx设置ip导致内核挂死,Linux之TCPIP内核参数优化
  5. linux上热编译react,如何使用react进行热加载
  6. append函数_高质量python代码:考虑用生成器来改写直接返回列表的函数
  7. mysql ES 同步中间件
  8. Java Web servletRequest
  9. Fastadmin 写关联命名时,最好前后台用同一个model,方便管理(会出现命名空间问题)...
  10. jQuery 追加元素的方法如append、prepend、before