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

  • 相关阅读:
    Climbing Stairs 爬楼梯问题,每次可以走1或2步,爬上n层楼梯总方法 (变相fibonacci)
    Search Insert Position 查找给定元素在数组中的位置,若没有则返回应该在的位置
    Remove Duplicates from Sorted List 去除链表中重复值节点
    Populating Next Right Pointers in Each Node 设置二叉树的next节点
    Binary Tree Inorder/Preorder Traversal 返回中序和前序/遍历二叉树的元素集合
    Same Tree 比较两个二叉树是否完全相同
    Linked List Cycle 判断一个链表是否存在回路(循环)
    Reverse Integer 旋转数字
    Maximum Depth of Binary Tree 二叉树的深度
    Single Number 数组中除了某个元素出现一次,其他都出现两次,找出这个元素
  • 原文地址:https://www.cnblogs.com/ASPNET2008/p/2127287.html
Copyright © 2020-2023  润新知