通過mongodb用戶端samus代碼研究解決查詢慢問題

來源:互聯網
上載者:User

標籤:

最近有項目需要用到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:真正遠端連線部分。

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本身問題,也非網路,非資料問題,而是在於沒有正確使用好用戶端串連,不容易啊,在此謝謝老代的指點。

 

參考資料:

MongoDB學習筆記

http://www.360doc.com/content/16/0720/17/35239163_577069265.shtml

monogodb find 方法調用javascript where

MongoDB下samus源碼初探

Mongodb 與sql 語句對照

在MongoDB中實現開放式並行存取控制

Mongodb insert save 區別

通過mongodb用戶端samus代碼研究解決查詢慢問題

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.