mongodb查询速度慢是什么原因?

通过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 = "mongodb://localhost"; ),然后发现有两个重要的参数:

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

   2ooled=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 < Builder.MinimumPoolSize)

                    _freeConnections.Enqueue(CreateRawConnection());

        }

 

 

    5:真正远程连接部分。

    

View Code ///

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

     

View Code ///

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

        }

 

        

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

         

View Code ///

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