最近遇到应用频繁的响应缓慢,无法正常访问。帮忙一起定位原因,最后定位到的问题说起来真的是很小的细节问题,但是就是这些小细节导致了服务不稳定,真是细节决定成败。这里尝试着来分享下,希望对大家有所帮助。
问题 1:占着茅坑不拉屎
遇到问题首先要看的还是服务器错误日志。
错误日志中看到频繁有这样的一个异常报错:Error: ER_CON_COUNT_ERROR: Too many connections。这个报错是因为数据库的所有连接被客户端都占有了,没有空闲的连接可以使用。MySQL 默认的最大并发连接数是 100,然而我们的应用这边最多可能的并发也就 30~40 个任务,怎么也不太可能报这样的错误,推测很有可能是代码里面建立连接后没有及时的进行关闭。于是我们重点看了下执行 SQL 部分的代码,大概是下面这样(使用了node-mysql库):
var mysql = require('mysql'); // 建立连接池 var pool = mysql.createPool({ host: 'host', user: 'user', password: 'password', database: 'db' }); exports.query = function(sql, cb) { // 从池子里面取一个可用连接 pool.getConnection(function(err, connection) { if (err) throw err; // 执行sql connection.query(sql, function(err, rows, fields) { if (err) { return cosole.error(err); } cb(rows); }); // 释放此连接 connection.release(); }); }; |
刚开始我还真没看出来有什么问题,后来仔细读了 node-mysql 的文档及这个 issue,终于发现了我们的写法是有问题的。
再次看看上面的代码,pool.getConnection 后我们执行 connection.query,然后没等 SQL 执行完,直接调用了 connection.release,由于 JavaScript 的异步特性(虽然 SQL 可能很快就执行完,但是我们也必须在 connection.query 的 callback 里面才明确的知道 SQL 执行完了),这个时候此次连接是不会被释放的!代码里面所有的 SQL 执行都调用到这个函数,这意味着我们占着一堆数据库连接不释放,这时不断的有其他数据库连接过来,直接导致其他连接被阻塞,抛出连接太多的异常。这真是典型的“拉完不及时让坑,占着茅坑不拉屎”的行为。所以,我们一定要在 SQL 执行完成后就将连接及时进行释放。因为 SQL 执行一般是非常快的(零点几秒),如果我们执行完后不释放,在同一时间产生很多数据库连接时很有可能导致连接被阻塞,产生连接过多的异常。于是我们对代码进行了如下修改:
exports.query = function(sql, cb) { // 从池子里面取一个可用连接 pool.getConnection(function(err, connection) { if (err) throw err; // 执行sql connection.query(sql, function(err, rows, fields) { // 释放连接(一定要在错误处理前,不然出错的时候也会导致该连接得不到释放) connection.release(); if (err) { return cosole.error(err); } cb(rows); }); }); }; |
也可以用更简单的写法 pool.query,这个方法内部会在合适的时机来释放连接,不用我们手动操作。
完成此次修改后,这个异常没有再复现,但是响应缓慢的情况依然没有得到缓解。