1、同时有50个并发请求,
2、每个请求将连接PgSQL并执行一个事务: 调用2个不同的Model Bill与Profile,执行 Bill.insert 与 Profile.update
3、PgSQL最大连接数为20
因为使用到事务,所以同一个数据库连接不可能同用于两个不同的http请求
如果在PHP中,那么开20个不同的php-cgi,并对每个php-cgi进行操作,其余30个在请求nginx队列
1、nginx接受50个http请求
2、php-cgi接受20个请求,并建立20个全局持久化连接 db
3、Model Bill 与 Profile 通过全局变量 db 进行操作
4、cgi结束进行各种资源,连接回收
5、对队列中下一个请求进行操作
在node中行为将变成
1、接受50个http请求
2、从最大连接数为20的连接池中取得一个连接局域变量db,其余30个连接请求加入队列
3、Model Bill 与 Profile 通过某种途径访问局域变量进行操作
4、单个请求完毕,对资源连接回收
5、对队列中下一个请求进行操作
由此,对比PHP与node,本质上的不同仅仅是 db 变量的全局与局域的区别,
一、解决方法
以下测试均在CPU Atom D510 1.66G 单核下测试。1. 通过vm
通过vm的runInNewContext可以把局域变量变成全局变量,但性能上大打折扣, 对于空脚本的script.runInNewContext() 为 1386 tps,
script.runInThisContext() 为 416667 tps
eval 为 13966 tps,
空循环 为 Infinity tps
由上述可列公式,
在runInNewContext中的最终性能为 1386x / (1386 + x)
在runInThisContext中的最终性能为 416667x / (416667 + x)
比如,原程序通过使用node达到了1000 rps,也是node使用http.createServer跑hello world的性能
重新设计并使用scriptInNewContext后,那么性能最佳也只能有 1386k / 2386 = 580 rps,
而在实际使用中,包含一次redis查询的完整应用,包含render,cache等部份可以达到 550 rps,那么runInNewContext性能降至 316 rps,
而相对应的php,包含一次redis查询的更复杂的完整应用,也可以保持在240 rps,对比,使用了scriptInNewContext后,node不再具有性能优势。
如果使用scriptInThisContext,性能只下降到 416667k / 417667 = 997 rps,几乎没有影响。但在这里没啥用。scriptInThisContext用于模版rendering是非常之不错。
2. 通过建立model后赋于变量
每个Model实例添加额外的变量,用以保存当前请求将要用到的连接。那么 model 则由new Model(arg1, arg2, ...) 变成 req.create(Model, arg1, arg2, ...),似乎能很好的解决创建问题。
但若要执行静态函数,Model.find_or_create 等, 变成 req.exec(Model, Model.find_or_create, arg1, arg2, ....),感觉上就不漂亮了。
3. Model模版
使用闭包生成一个新的Model类,是个好方法,在Model的使用上也无区别。对性能的影响为 714k tps, 如果每次必须重新生成10个Model, 则为71k,对性能的影响也是微乎其微。ModelTemplate模型 ToffeeScript 代码
ModelTemplate = (req) ->
{db} = req
class Model
@create: ->
console.info "create #{db}"
st = Date.now()
for i in [1..1000]
Model = ModelTemplate({db: 'hello'})
console.info Date.now() - st
比如 class Bill, class Profile,改进成
ModelTemplate = (req) ->
{db} = req
class Bill
@create: ->
console.info "create bill #{db}"
class Profile
@create: ->
console.info "create profile #{db}"
{Bill, Profile}
st = Date.now()
for i in [1..1e4]
models = ModelTemplate({db: :hello})
{Bill} = models
console.info 1e4 * 1e3 / (Date.now() - st)
ModelTemplateBill = (req) ->
{db} = req
class Bill
@create: ->
console.info "create bill #{db}"
ModelTemplateProfile = (req) ->
class Profile
@create: ->
console.info "create profile #{db}"
ModelTemplate = (req) ->
{db} = req
{Bill} = ModelTemplateBill(req)
{Profile} = ModelTemplateProfile(req)
{Bill, Profile}
st = Date.now()
for i in [1..1e4]
models = ModelTemplate({db: :hello})
{Bill} = models
console.info 1e4 * 1e3 / (Date.now() - st)
# models/bill.toffee
ModelTemplateBill = (req) ->
{db} = req
class Bill
@create: ->
console.info "create bill #{db}"
module.exports = ModelTemplateBill
#----
ModelTemplate = (req) ->
@BillTemplate ?= require('./models/bill')
@ProfileTemplate ?= require('./models/profile')
Bill = @BillTemplate(req)
Profile = @ProfileTemplate(req)
{Bill, Profile}
st = Date.now()
for i in [1..1e4]
models = ModelTemplate({db: :hello})
{Bill} = models
console.info 1e4 * 1e3 / (Date.now() - st)
4. 资源池
由于Model的引入的最主要目的是对连接的控制,达到类似连接池的效果,因此由此脱离req,建立持久化连接池才是根本目的代码则变成
class ResourcePool
# 代码略
st = Date.now()
res_pool = ResourcePool.create({db: :hello})
for i in [1..1e4]
res = res_pool.create()
{Bill, db} = res
res.close()
console.info 1e4 * 1e3 / (Date.now() - st)