npm模块generic-pool源码分析

About generic-pool

github地址:https://github.com/coopernurse/node-pool
description:
Generic resource pool. Can be used to reuse or throttle expensive resources such as database connections.
由此可以看出,该模块是一般的资源池,用来重复使用昂贵的资源,比如数据库连接等。

使用这个模块

Step 1:创建资源池

//Step 1 - Create pool using a factory object

// Create a MySQL connection pool with,创建Mysql连接
// a max of 10 connections, a min of 2, and a 30 second max idle time
var Pool = require('generic-pool').Pool;
var mysql = require('mysql'); // v2.10.x

var pool = new Pool({
    name     : 'mysql',
    create   : function(callback) {
        //创建连接
        var c = mysql.createConnection({
                user: 'scott',
                password: 'tiger',
                database:'mydb'
        })

        // parameter order: err, resource
        callback(null, c);
    },
    //销毁连接
    destroy  : function(client) { client.end(); },
    //最大并发数
    max      : 10,
    // optional. if you set this, make sure to drain() (see step 3),如果设置了并发数,记得在不使用时执行drain()方法,(英文意思为排干,流尽)
    //最小并发数
    min      : 2,
    // specifies how long a resource can stay idle in pool before being removed,idle:资源超时时间
    idleTimeoutMillis : 30000,
     // if true, logs via console.log - can also be a function
     //是否打印日志,log可以为一个函数,执行函数log
    log : true 
});

Step 3 - Drain pool during shutdown (optional)

If you are shutting down a long-lived process, you may notice that node fails to exit for 30 seconds or so. This is a side effect of the idleTimeoutMillis behavior -- the pool has a setTimeout() call registered that is in the event loop queue, so node won't terminate until all resources have timed out, and the pool stops trying to manage them.

This behavior will be more problematic when you set factory.min > 0, as the pool will never become empty, and the setTimeout calls will never end.

In these cases, use the pool.drain() function. This sets the pool into a "draining" state which will gracefully wait until all idle resources have timed out. For example, you can call:

// Only call this once in your application -- at the point you want
// to shutdown and stop using this pool.
pool.drain(function() {
    pool.destroyAllNow();
});

Step 2:使用资源池(acquire,release)

//Step 2 - Use pool in your code to acquire/release resources

// acquire connection - callback function is called
// once a resource becomes available
pool.acquire(function(err, client) {
    if (err) {
        // handle error - this is generally the err from your
        // factory.create function
    }
    else {
        client.query("select * from foo", [], function() {
            // return object back to pool,将资源对象返回给资源池
            pool.release(client);
        });
    }
});

Step 3:把资源池的水排干,可选

在一个已经运行了很长的进程中,如果你想把资源池关闭了,你可能会发现,有节点会在30s后才执行关闭动作。这个是由设置的idleTimeoutMillis 引发的副作用:资源池存在一个setTimeout()调用,该调用被添加到事件循环队列中,所以该节点不能被关闭,直到所有的资源都超时,此时,资源池将不会对这些资源进行管理。

这样的行为,当设置最小并发数大于0的时候,这样如果关闭的话,将会导致该资源池永远都不能为空,而存在于事件循环队列的setTimeout()调用将永远得不到执行。

如上面的情况,应该使用pool.drain()函数,这个函数会将资源池里的所有资源都清除干净,在任何你想关闭或者停止使用资源池的地方调用它,并且这个函数只能被执行一次 如:

pool.drain(function() {
    pool.destroyAllNow();
});

Pool

git clone https://github.com/coopernurse/node-pool.git

发现主要的代码就位于lib文件夹中的generic-pool.js,打开代码,发现只有580行,这更激起我阅读该模块的兴趣了。。
源码的第580行导出了Pool,故先看Pool类

exports.Pool = Pool

Pool构造函数

/**
 * Generate an Object pool with a specified `factory`.
 *
 * @class
 * @param {Object} factory
 *   用来生成和删除实例
 * @param {String} factory.name
 *   工厂的名字,主要用来写log用
 * @param {Function} factory.create
 *   生成实例所必须的方法,生成后执行回调,将以回调参数的形式返回给调用资源池者。
 * @param {Function} factory.destroy
 *  温柔滴关闭正在运行的资源。
 * @param {Function} factory.validate
 * 在acquire中返回前判断资源是否有效,默认为有效
 * @param {Function} factory.validateAsync
 * 同步调用
 * @param {Number} factory.max
 *   最大并发数,默认为1.
 * @param {Number} factory.min
 *   最小并发数,默认为0
 *  一旦资源池被创建或者资源被回收的时候,需要检查资源池的资源是否小于并发数,以下,如果小于并发数以下,应该创建新的资源并将其加入到资源池中。
 * @param {Number} factory.idleTimeoutMillis
 *   超时时间,单位为毫秒,超时回收
 * @param {Number} factory.reapIntervalMillis
 *   检查空闲资源的频率 (default 1000),
 * @param {Boolean|Function} factory.log
 *  true/false or function 如果为true,log,info,verbose,信息会被传送到console.log("")中,如果为function,则需要两个参数
 * log string
 * log level ('verbose', 'info', 'warn', 'error')
 * @param {Number} factory.priorityRange
 *  int  1 to x  ,如果没有资源可使用,将根据工厂的优先级,依次从工厂队列中寻求资源。默认工厂优先级为1
 * @param {RefreshIdle} factory.refreshIdle
 * 默认为true ,默认情况先选择空闲资源被销毁后重新创建,时间即为超时时间。
 * @param {Bool} [factory.returnToHead=false]
 * 默认为false,如果为真,那么每次资源
 * 返回到available对象数组的头部那么下一次获取的资源就为该数组的的第一个资源,这样就将资源队列变成了资源栈了
 **/

function Pool (factory) {
    if (!(this instanceof Pool)) {
        return new Pool(factory)
    }
    if (factory.validate && factory.validateAsync) {
        throw new Error('Only one of validate or validateAsync may be specified')
    }
    // defaults,默认设置
    factory.idleTimeoutMillis = factory.idleTimeoutMillis || 30000
    factory.returnToHead = factory.returnToHead || false
    factory.refreshIdle = ('refreshIdle' in factory) ? factory.refreshIdle : true
    factory.reapInterval = factory.reapIntervalMillis || 1000
    factory.priorityRange = factory.priorityRange || 1
    factory.validate = factory.validate || function () { return true }
    factory.max = parseInt(factory.max, 10)
    factory.min = parseInt(factory.min, 10)
    factory.max = Math.max(isNaN(factory.max) ? 1 : factory.max, 1)
    factory.min = Math.min(isNaN(factory.min) ? 0 : factory.min, factory.max - 1)

    this._factory = factory
    this._inUseObjects = []/*正在使用的资源*/
    this._draining = false
    this._waitingClients = new PriorityQueue(factory.priorityRange) /*优先级队列*/
    this._availableObjects = [] /*可用的资源*/
    this._count = 0 /*引用计数*/
    this._removeIdleTimer = null
    this._removeIdleScheduled = false
    // create initial resources (if factory.min > 0)
    this._ensureMinimum()/*此处即为保证资源池中存在最少并发数资源*/
}

上述代码的最后一句写明,该方法是用来保证资源池中存在最少并发数资源,那么来看下这个方法是如何实现的。这个也是个人学习源码的一点经验。

Pool.prototype._ensureMinimum 保证最小并发数

私有方法
实现代码就几行

Pool.prototype._ensureMinimum = function _ensureMinimum () {
    var i, diff
    if (!this._draining && (this._count < this._factory.min)) {
        diff = this._factory.min - this._count
        for (i = 0; i < diff; i++) {
            this._createResource()
        }
    }
}

* ___* 上述代码中,如果没有将资源池排干或者当前资源计数小于最小并发数,那么将根据差值的个数构造相应多的资源。
那么来看下如何创建资源的:

Pool.prototype._createResource 创建资源

私有方法
先不看这个函数的具体实现,首先根据已经得到的知识,可以得到,至少创建一个资源,引用数加1,往_inUseObjects中push一个资源,还有就是打印日志等。

Pool.prototype._createResource = function _createResource () {
    this._count += 1
    this._log('createResource() - creating obj - count=' + this._count + ' min=' + this._factory.min + ' max=' + this._factory.max, 'verbose')
    var self = this
    this._factory.create(function () {
        var err, obj 
        //从优先级队列中弹出一个Clinet客户应用
        var clientCb = self._waitingClients.dequeue()
        if (arguments.length > 1) {
            err = arguments[0]
            obj = arguments[1]
        } else {
            err = (arguments[0] instanceof Error) ? arguments[0] : null
            obj = (arguments[0] instanceof Error) ? null : arguments[0]
        }
        if (err) {
            self._count -= 1 //错误则-1 
            if (self._count < 0) self._count = 0
            if (clientCb) {
                clientCb(err, obj)
            }
            process.nextTick(function () {
                self._dispense() //下次循环事件中_dispense()很关键
            })
        } else {
            self._inUseObjects.push(obj) //push一个资源
            if (clientCb) {
                clientCb(err, obj)
            } else {
                self.release(obj)//错则释放
            }
        }
    })
}

Pool.prototype._dispense 工作

私有方法
这个方法用来:获取一个client使其工作,并且清理资源池里的空闲资源

  1. 如果有要连接的client正在等待,则将client后进先出(LIFO),并执行回调函数。
  2. 如果没有client等待,则尝试构造一个client,不超过最大连接数
  3. 如果构造的client超过了最大值,则将这个client加入等待队列中
Pool.prototype._dispense = function dispense () {
...
...
    var waitingCount = this._waitingClients.size()
    if (waitingCount > 0) {
        if (this._factory.validateAsync) {
            doWhileAsync(function () {
                return self._availableObjects.length > 0
            }, function (next) {
                objWithTimeout = self._availableObjects[0]
                self._factory.validateAsync(objWithTimeout.obj, function (valid) {
                    if (!valid) {
                        self.destroy(objWithTimeout.obj)
                        next()
                    } else {
                        self._availableObjects.shift()//移出
                        self._inUseObjects.push(objWithTimeout.obj)
                        clientCb = self._waitingClients.dequeue() 
                        clientCb(err, objWithTimeout.obj)//回调
                    }
                })
            }, function () {
                if (self._count < self._factory.max) {
                    self._createResource()
                }
            })
            return
        }

        while (this._availableObjects.length > 0) {
            this._log('dispense() - reusing obj', 'verbose')
            objWithTimeout = this._availableObjects[0]
            if (!this._factory.validate(objWithTimeout.obj)) {
                this.destroy(objWithTimeout.obj)
                continue
            }
            this._availableObjects.shift()
            this._inUseObjects.push(objWithTimeout.obj)
            clientCb = this._waitingClients.dequeue()
            return clientCb(err, objWithTimeout.obj)
        }
        if (this._count < this._factory.max) {
            this._createResource()
        }
    }
}

上述代码可能不好理解,因为用到一个函数doWhileAsync

function doWhileAsync (conditionFn, iterateFn, callbackFn) {
    var next = function () {
        if (conditionFn()) {
            iterateFn(next)
        } else {
            callbackFn()
        }
    }
    next()
}

所以根据上述的方法,可以得到_dispense中,如果self._availableObjects.length大于0,那么执行第二个函数,该函数从availableObjects中获取一个资源,并将其放入inUseObjects中,然后从client等待队列中选取一个进行回调,并进行递归调用。直到没有可用资源,最后执行第三个函数,就是继续创造资源。

Pool.prototype.acquire从资源池中获取资源

Pool.prototype.acquire = function acquire (callback, priority) {
    if (this._draining) {
        throw new Error('pool is draining and cannot accept work')
    }
    ////回调函数和优先级入队列,在变量clientCb中执行回调
    this._waitingClients.enqueue(callback, priority)
    this._dispense()//回调就在此处执行,使client执行。
    return (this._count < this._factory.max)
}

Pool.prototype.release将资源返回到资源池中

Pool.prototype.release = function release (obj) {
    //如果在可用资源对象中已经存在了这样的obj,那么说明资源已经被回收,而这个release被调用了多次。那么不处理,只打印日志
    if (this._availableObjects.some(function (objWithTimeout) { return (objWithTimeout.obj === obj) })) {
        this._log('release called twice for the same resource: ' + (new Error().stack), 'error')
        return
    }

    //判断要释放的资源是否在in use对象数组中,如果存在则remove
    var index = this._inUseObjects.indexOf(obj)
    if (index < 0) {
        this._log('attempt to release an invalid resource: ' + (new Error().stack), 'error')
        return
    }
    // this._log("return to pool")
    this._inUseObjects.splice(index, 1)//在正在使用的对象数组中删除
    var objWithTimeout = { obj: obj, timeout: (new Date().getTime() + this._factory.idleTimeoutMillis) }
    //是否返回到available对象数组的头部
    if (this._factory.returnToHead) {
    //从0开始,不删除项目(0),在头中添加objWithTimeoutd对象
        this._availableObjects.splice(0, 0, objWithTimeout)
    } else {
        this._availableObjects.push(objWithTimeout)//尾部添加
    }
    this._log('timeout: ' + objWithTimeout.timeout, 'verbose')
    this._dispense() //clean up idles items
    this._scheduleRemoveIdle()//移出多余的idle资源,因为资源返回了,故可能需要,将idle资源回收。
}
this._scheduleRemoveIdle()

此方法不是重点,但是通过查看源码可以得知,如果设置是this._factory.reapInterval idle资源回收频率,那么模块将在Timeout时间内执行._removeIdle()函数,该函数的执行就是先获取需要remove的资源,放入到数组toRemove中,然后逐一调用this.destroy(toRemove[i]);注意remove的资源个数应为当前引用个数-最小并发数。

Pool.prototype.drain

/**
 * 不允许任何新请求,和已请求分离
 *
 * @param {Function} callback
 *   可选,如果所有的操作做完,所有的clients断开连接则执行回调函数
 */
Pool.prototype.drain = function drain (callback) {
 ...
    this._draining = true
    var check = function () {
        if (self._waitingClients.size() > 0) {
            // wait until all client requests have been satisfied.
            setTimeout(check, 100)
        } else if (self._availableObjects.length !== self._count) {
            // wait until all objects have been released.
            setTimeout(check, 100)
        } else if (callback) {
            callback()
        }
    }
    check()
}

上述代码,就是一直在检查一直在检查,检查好了,执行回调操作

pool.drain(function() {
    pool.destroyAllNow();
});

最后来看一下:

Pool.prototype.destroyAllNow 清空所有连接

Pool.prototype.destroyAllNow = function destroyAllNow (callback) {
    this._log('force destroying all objects', 'info')
    var willDie = this._availableObjects
    this._availableObjects = [] //清空
    var obj = willDie.shift()
    while (obj !== null && obj !== undefined) {
        this.destroy(obj.obj)
        obj = willDie.shift()
    }
    this._removeIdleScheduled = false
    clearTimeout(this._removeIdleTimer)
    if (callback) {
        callback()
    }
}

有了前面代码的详细解释,这里的代码就能一看就懂了。

总结

  1. 内部使用了优先级队列,看了源码,只是几个简单的入队和出队等方法,故没有详细阐述,若有兴趣,可以查看源码学习之
  2. 若有错误,请一定要指出,相互学习。

你可能感兴趣的:(Node,npm,源码,pool)