公司内部有个项目,很简单,几乎没什么业务逻辑,就是根据几个 Key 到 mongodb 中去查询,然后前端返回 JSON
一开始是使用 rails 配合 grape 出的第一版,跑在 passenger 下,然后被 passenger 蛋疼的并发模式伤到了,切到了 rack 配合 grape 跑在 thin 上,快了很多,2 核机器 4 个 process 下,性能提高了 2 倍,但是由于历史原因还是载入整个 rails 环境,而且,Mongodb 使用了 Mongoid 的驱动
看了 robbin 同志关于 api 后台的描述,后面又不满足了,去掉了 rails 环境,单独使用 grape 配合 mongodb 的原生驱动,加入 rainbows 的线程池模式,4 个 process,64 个线程池加上 64 个连接池,大概也就提升了 50%
后面,闲着无聊,觉得 mongodb 的原始驱动是基于阻塞的 socket 模式,在线程池模式下,将会带来大量的线程切换的消耗,因此萌生了基于 Eventmachine 搞一个异步 io 查询的连接池,先尝试了下用 Node 写了个基本的从 Mongod 中查询 api,然后基于 mongodb 的原生驱动写了个基本可用报错不管的异步游标的实现,对比下,顿时,我和我的小伙伴都惊呆了
使用ruby
+ 自制的异步游标 + rainbows
, 单进程大概可以负载 15 个请求每秒,而同样的查询同样的数据源,NODE 单个进程可以负担 50+ 的请求每秒,而且,平均消耗时间都在 ruby 的一半以下
ruby 是 1.9.3--p286,有点老, node 用的是 apt-get 的装的 在 2K 的数据中查询出 113 个数据,数据 JSON 化之后大概有 70+KB,有点大, 但是差距真的感觉十分明显啊
具体代码在: https://github.com/shengxiang205/async_cursor_test.git
node 的项目在 node/query_app
下
代码写的有点乱,但是结果还是很震惊啊,我一直以为如果用 EM 配合异步 IO 的话,虽然可能会比 node 差一点,但是应该也差的不多,自己测试了下,结果还是不是这样的。。。
不知道是不是我代码有问题。。
内牛满面啊
最新修正
将 113 个数据切换成 1 个数据再次做对比
ruby 大概跑出了400+ req/s
, node 跑出了 760 req/s
在接受 @robbin 的提议,加入 GC 参数的设置:
RUBY_HEAP_MIN_SLOTS=600000
RUBY_FREE_MIN=200000
RUBY_GC_MALLOC_LIMIT=60000000
export RUBY_HEAP_MIN_SLOTS RUBY_FREE_MIN RUBY_GC_MALLOC_LIMIT
ruby 大概提升到了 530 req/s
, 提升巨大,虽有差距,但是在这种情况下差的也不多了
得出以下结论:
有时间尝试下基于 celluloid-io
做个类似的 demo
@jimrokliu ruby 的 VM 还是不够给力啊。。。虽然写起来很爽,但是,也就像 rails 一样,不能在 ruby 的温床里面不肯出来啊
大概主要瓶颈在 mongo 的 driver 实现上,另外 EM 做的 buffer 拷贝排队工作有点太多...
现在 1.9.3 都 p448 了... 换 ruby 2.1 试试吧?
再强大如果么有配套的工具,实用的应用功能的话,我也不会选择,先前用 cnodejs,后来发现他们的代码库基本不更新了,所以我才转到 ruby-china 来的,学了一个礼拜,基本上可以开始工作了
#16 楼 @killyfreedom 很难说... 用 rack, rainbow, mongo, grape 这个 Gemfile 里已经东西一大堆了吧?如果就一个很素的功能,用 nodejs 或者 go 也可以啊,不用纠结。
另外试过 https://github.com/bcg/em-mongo 了没?
@luikore EMMongo 不支持 Replica Sets 啊,真是内牛满面,现在这个还不是瓶颈点,真的成为瓶颈点了也只能用 node 写了,功能还是满素的,只是有这么大的差距,不甘心啊....哈
@jjym 就是基于 EM::Connection 写的,
module EMMongoSocket
BINARY_ENCODING = Encoding.find("binary")
attr_reader :buff
def initialize(opt = {})
@buff = ''.force_encoding(BINARY_ENCODING)
@running = false
@pool = opt[:pool]
@proc = nil
end
def post_init
end
def add_processor(proc)
@proc = proc
unless @buff.empty?
EM.next_tick { @proc.call(@buff) if @proc }
end
end
def remove_processor(proc)
@proc = nil if @proc == proc
end
def checkin
@pool.checkin(self)
end
def receive_data(data)
if data
@buff << data
unless @buff.empty?
@proc.call(@buff) if @proc
end
end
end
def unbind
end
end
楼主可以试试 sinatra-synchrony + em-mongo 搭配 thin -s 4 .
我做的一个简单的 insert 测试 https://github.com/nickelchen/sinatra-synchrony-template
在这个基础上再加上 em-synchrony/em-mongo
效果更好。
用了不同的 gem 肯定会有一些是阻塞 IO 的,即使使用 EM,你的代码里是阻塞的,那最后还是那样。Node 的话,估计他的插件本身都是异步 IO 的,所以这样比较还是不公平的。我觉得问题不在 EM 啊!
@kenshin54 这个我知道呢,所以,我基于 EM 的 Reactor 机制重写了通讯的 socket,从某种角度上来说和 EMMongo 是类似的
@nickelchen 我看了下你的测试数据,大致对比了下单核性能,如果只查询一条数据,我在 Intel(R) Pentium(R) Dual CPU E2220 @ 2.40GHz
的机器上大概可以做到 400 个请求每秒,单进程,但是 Node 可以做到 700+ 的请求每秒,我觉得你可以尝试下用 node 做一个同样功能的版本,我相信性能数据会让你吃惊的
呵呵。。。
https://github.com/fredwu/spiky_xml
ruby parsing 27000ms
ruby concurrent parsing 27000ms
jruby parsing 19000ms
jruby concurrent parsing 11000ms
nodejs xml2js parsing: 27616ms
nodejs libxmljs parsing: 1333ms
这数据对比还好吧。node 的强项就是 IO 并发,做这种测试其实就是测 IO 并发,你的 node 性能是 ruby 的 3 倍,说明 ruby 已经很不错啦。而 ruby 本身在 IO 方面就算传统弱项,这个差距在我看来真的算很小了。
另外你如果调整一下 ruby 的 GC 参数,还能够提高性能 50-100%。
@robbin 我实测的时候,内存峰值不到 60M,这个时候 GC 影响大么?这个真没什么经验..
自己尝试了下,用了你 robbin_site 下的 GC 参数,在只读取 1 个数据的情况下,从 400 req/s 提升到了 530 req/s
读取 113 个数据的情况下,从 17 req/s 提升到了 25req/s每秒
果然,GC 影响这么大...
#23 楼 @killyfreedom mongodb 我没测试过,但是我测试过 memcached 在 EM 和 Node 的性能,用的 EM 自己提供的 memcached 协议实现,Node 用的是 https://github.com/3rd-Eden/node-memcached ,结果是 EM 快,不包含 web 环境。
@wxianfeng 尝试关注下这个,但是和 NGINX 整合在一起应该只适合很素的需求吧 @sevk ruby 2.0 感觉和 1.9 比不是颠覆性的升级,性能感觉提升不是特别明显
@kenshin54 说错了,我 100 个并发,1000 个请求,em-mongo 不带连接池的吧
我写了个类似的,有一个 256 的连接池,还是基于我原来的异步游标
纯性能大概到了 1300req/s
你的没有 JSON 序列化,我的机器上大概跑出了 1500req/s
已经比基于 express 的快了
#encoding: utf-8
lib_path = File.dirname(File.expand_path(__FILE__)) + '/lib'
$:.unshift lib_path
require 'rubygems'
require 'mongo'
require 'json'
require 'eventmachine'
host = %w{ 192.168.10.223 27017 }
$mongo_connection = Mongo::MongoClient.new(host[0], host[1], :pool_size => 64)
$db = $mongo_connection['data']
require 'async'
Async.host = '192.168.10.223'
Async.port = '27017'
class Server < EventMachine::Connection
def receive_data(data)
# $collection.find(:creator => '[email protected]', :as => 'task_state_log').limit(1).each do |doc|
# send_data doc
# close_connection_after_writing
# end
cursor = $db['data'].find(
{ :creator => '[email protected]', :as => 'task_state_log' }
).limit(1)
# puts Thread.list.inspect
Async::AsyncCursor.new(cursor: cursor).to_array do |data|
send_data JSON.dump(data)
close_connection_after_writing
end
end
end
EM.run do
EM.next_tick do
end
EM.start_server '0.0.0.0', 4001, Server
end
#53 楼 @killyfreedom em-mongo 不带连接池,但是 em-synchrony 有一个可以创建 Pool 的东西。
你可以在 ruby 2.0 下跑跑看我的代码,我跑下来速度不差。
@kenshin54 你的 ruby 2.0.0 提升好大啊 你的代码我换到 ruby-2.0.0-p247 下大概也就跑到了 1600req/s
, 远没有你提升这么巨大.... 尝试把你的结果 JSON 化出来看看
@kenshin54 这个场景用 c 100 n 1000 来测似乎到不了 CPU 的极限啊,用 c 200 n 10000 测试,数据就稳定多了
@kenshin54 你们有没有用 Goliath + Nginx 测试过。用 Nginx 做反向代理,配置负载均衡,后端根据需要启动多个 Goliath sever 进程。Goliath 就是专为这种场景设计的,内部使用了 EM。并且非常稳定,我们的服务在生产环境上,从 2013.4 月份启动到现在 就没出现过问题,也没重启过。
@outman 纯粹探索在单进程模型下,同样 EM 的实现,ruby 和 node 有多少差距,以此来推断如果逻辑很素的但是性能要求很高的服务比较适合哪个体系来做
最初的目的是同样的 api 服务器,基于 rack 的 em 的 ruby 和基于 express 的 node 的差距比较大,所以比较好奇,差距在哪里
Fiber 作为编程很友好的并发形式,其实我个人还是很喜欢
@killyfreedom 哦,了解了。其实用 nodejs 来做这种业务我还没用过,以前只是用来做一些前端的消息 PUSH。nodejs 经过几年的发展,现在应该很强了吧。
@outman 我也是尝试了解下 nodejs,感觉应该发展的不错,但是不尝试么又不知道,就自己写点代码摸索摸索,对比对比,就出了这次的事情,哈哈
楼主,继续给你优化建议哈:
ruby 的 json gem 性能其实是比较一般的,建议你换成 oj,估计还能整体提升 10% 的性能。
另外,如果你不怕麻烦的时候,可以到 github 上找找 ruby2.0 的性能补丁,反复测试测试各种参数各种补丁,运气好的话,还能提升 30%。
不过 ruby 真的没有必要和 node 比较。node 那种嵌套回调不适合写复杂逻辑,非常反人性,两者应用场景有差别。个人看法:n 年以后,Go 会消灭 node,不信等着看。
几乎没什么业务逻辑,就是根据几个 Key 到 mongodb 中去查询,然后前端返回 JSON
这么素的业务逻辑可不可以把相关要查的 key 全部丢到 route 里面,然后查询结果直接生成静态 cache 丢给 nginx,性能也许还能提高个十几倍呢……
@robbin 同意这个观点,基于回调的写法真的太反人类了,再挖掘性能就等日后有兴趣再说了,毕竟现在还没有这么大的性能需求
不过还是挺有收获的,自己这么摸索几下
感谢@kenshin54 后面配合做的测试,理解也深刻了许多啊
#82 楼 @robbin V8 已经支持了 Generators 特性,现在还没有默认开启。 看看这个 https://github.com/visionmedia/co ,异步嵌套问题不再是恶魔
Ruby 也是这样做的。这里有两篇文章。 http://www.igvita.com/2009/05/13/fibers-cooperative-scheduling-in-ruby/ http://www.igvita.com/2010/03/22/untangling-evented-code-with-ruby-fibers/
这文章作者有写几个 gem, 主要用途也是以同步的来执行异步调用。
我和楼上诸位观点略有差异,认为 node.js 未来发展肯定会比 go/Er 好(毕竟前端 browser 人家是唯一语言,优势太大。。),.v11.2 的 yield 出了以后,javascript 这边应该是也有了 ruby 的 yield 能力(也就是 85 楼的 generator),然后 javascript 允许 function 作为 first class 处理,函数返回函数,这个能力似乎在异步编程里面很有用,诸如promise的异步编程手法也不能说完全反人类,只是比较新的想法,目前大家都一时不太能转过弯来吧。。
刚好我做了两个基本同样的网站,分别用 ruby 和 nodejs,大家可以比较一下看: ruby: http://xmzdm.com nodejs: http://wjzdm.com 说明下,ruby 的是 sinatra+sqlite+thin,nodejs 的是 express+mongodb+nginx 话说,只要不用 ror 框架,sinatra 的性能也是非常不错的。
EventMachine
require "eventmachine"
class Echo < EventMachine::Connection
def receive_data(data)
send_data("Hello World")
close_connection_after_writing
end
end
EventMachine.run do
EventMachine.start_server('0.0.0.0', 10000, Echo)
end
nodejs
var http = require('http');
http.createServer(function (req, res) {
res.end('Hello World');
}).listen(10000, '127.0.0.1');
ab -n 6000 -n 66 http://localhost:10000/
EventMachine:261.55 rps nodejs : 254.77 rps
未来发展的话个人看好 node 1,群众基础好,大家不见国内推动 nodejs 发展的主要来自前端的兄弟么,后端当然更不用说。 2,商业支持好啊 目前 joyent 打前锋,微软 axure 也想插一杠子。 3,rails/ruby 社区为 nodejs 打下坚实基础了,不见 n 多 nodejs 框架工具都是 ror 翻译过去,n 多大牛也转向 node 社区 4,游戏行业之 nodejs 主要采用者,做 tcp,二进制,高并发的好东西。