在测试Adhesive的时候发现一个Mongodb官方驱动1.1.0.4184比较严重的BUG

测试Adhesive的时候发现,使用过一段时间后台之后,就会不能连接到数据库,查看日志发现报的错误大致是

connection refused because too many open connections: 819

通过mongostat查看连接数的确很夸张有近819个连接,尝试在启动的时候指定最大连接数到10000,重新启动Mongodb这个问题似乎解决了

但是在刷新几下页面之后连接数串到了4000,以前在使用的过程中即使在线上非常大的压力情况下连接数也在50之内,几千的连接数还是第一次见到

首先想是不是程序问题引起的,因为在程序中我们的折线图一次需要进行100次左右的Count操作来得到每一个点的总数:

image

虽然在程序中使用了并行库但是在只有一条折线的情况下是单线程的,因此排除并行库问题

而且发现每一次刷页面连接数都会大幅上涨,根本没有回落的时候

这想到了是连接没有释放,经过查看官方文档知道并不需要在每次调用之后显式释放连接

写了一个最简单的程序来测试:

      static MongoServer server = MongoDB.Driver.MongoServer.Create("mongodb://192.168.129.172:20000/?slaveok=true");
        static void Main(string[] args)
        {
            while (true)
            {
                Console.ReadLine();
                var db = server.GetDatabase("qqtest");
                var col = db.GetCollection("test");
                try
                {
                    Console.WriteLine(col.Count());
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
        }

很明显,按一次回车,连接数涨1!但是奇怪的是,写入数据的Master却不会有这个问题,于是把连接字符串修改为Master的地址

mongodb://192.168.129.142:20000/

居然正常了,不管怎么按回车,或是循环几百次连接始终只增加了一个,说明连接复用了

至此,肯定是连接Slave的时候才会有这个问题,这问题也算是隐蔽。

当时的这个版本是直接从NuGet引用的,没有源代码,于是下载到1.1.0.4184的源代码调试

在调试后发现MongoServer.cs其中有一个方法:

 private void InstanceStateChanged(
            object sender,
            object args
        ) {
            lock (instances) {
                if (instances.Any(i => i.State == MongoServerState.Connected && i.IsPrimary)) {
                    // Console.WriteLine("Server state: Connected");
                    state = MongoServerState.Connected;
                    return;
                }

                if (settings.SlaveOk) {
                  if (instances.Any(i => i.State == MongoServerState.Connected && (i.IsSecondary || i.IsPassive))) {
                        // Console.WriteLine("Server state: Connected");
                        state = MongoServerState.Connected;
                        return;
                    }
                }

                if (instances.Any(i => i.State == MongoServerState.Connecting)) {
                    // Console.WriteLine("Server state: Connecting");
                    state = MongoServerState.Connecting;
                    return;
                }

                // Console.WriteLine("Server state: Disconnected");
                state = MongoServerState.Disconnected;
            }
        }

把注释语句去掉,看看运行的情况:

image

这个方法中会根据连接状态改变来改变服务器的状态,在连接上数据库之后,由于SlaveOk进入了黑体的语句,instances其中已经有一个状态是Connected的项了,但是由于i又不是IsSecondary也不是IsPassive,因此不能满足条件,直接变为了state = MongoServerState.Disconnected。也就是对于SlaveOk每次都是不能认为数据库已连接,每次都是重新连接!从图中可以看到,每次最后都转到了state = MongoServerState.Disconnected,其实应该是运行state = MongoServerState.Connected。我想这里作者的意思大概是为了增加一个SlaveOk的判断吧,而对于Slave的情况,IsSecondary和IsPassive都为false的,那么我把黑体代码改为如下:

 if (instances.Any(i => i.State == MongoServerState.Connected && (!i.IsPrimary))) {

再运行程序,发现这个问题解决了。。。再来看看运行情况:

image

这次就对了,在进行一次连接之后,后面的请求都没有再初始化一个连接

 

想到是否可以更新下客户端看看最新的客户端版本是否解决了这个问题,去官网下载了最新的1.3版本,发现没有了这个问题。那么我想看看作者是怎么修改的,查看这个文件的历史,在某一个历史中看到了这段代码的改动记录:

image

image

可以看到原先的这段判断被删除了,这下思路清晰很多了!直接进行对应状态的设置,SlaveOk的判断和状态的设置不在有关系。

分享此次排查问题的过程,抛砖引玉,提醒下大家在使用开源组件的时候需要小心测试。

posted @ 2011-11-04 20:40 lovecindywang 阅读(...) 评论(...) 编辑 收藏