最近有项目需要用到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
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(),我第一印象就是创建客户端与服务端的连接,其实有了连接池,这个操作并非每次都创建远程连接,有的情况只是从连接池中直接返回可用连接对象而已。 /// <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的生成过程。 /// <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:真正远程连接部分。 ![]() /// <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); } ![]() /// <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; } public void Dispose()
{ this.mongo.Disconnect(); }
再看看mongo.Disconnect() /// <summary>
/// Disconnects this instance. /// </summary> /// <returns></returns> public bool Disconnect() { _connection.Close(); return _connection.IsConnected; } ![]() /// <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本身问题,也非网络,非数据问题,而是在于没有正确使用好客户端连接,不容易啊,在此谢谢老代的指点。 |
|