部署 Unicorn 与 Puma 的区别,nginx 与哪个搭配使用更好?

luoganglong · 2017年08月09日 · 最后由 luikore 回复于 2017年08月27日 · 6043 次阅读

unicorn 与 puma 的区别,nginx 与哪个搭配使用更好?

现在流行 Puma 了,Puma 首先是 Rails 5 的默认 web 容器,其次如果你使用 ActionCable 或类似的 websocket 后端的话,Puma 不需要你单独再启动额外服务来运行了。

至于和 Nginx 搭配,两个在 Nginx 的配置方式是完全相同的

unicorn 是基于进程的。

puma 是基于线程的。

你的代码线程安全吗?如果安全,推荐 puma

puma 在 MRI 上体现的优势并不明显,ActionCabel/Websocket 的直接支持倒是它的特性优势。在我们自己的应用上对比了 puma 和 unicorn,并发能力差别不是很大。

其并发优势需要在真正多线程环境上 (JRuby Rubinius) 上才能体现出来。

题外话:JRuby 作为 ruby 与 java 的胶水也是个不错的方式。我也很好奇有多少人在生产环境上使用 Rubinius.

edwardzhou 回复

在好网络并发看起来并无差别,如果网站移动终端用户多,差别就大了。

比如某些移动用户网络不稳定导致一个请求要 5 秒在请求完成。 在 unicorn 的 2 进程下,5 秒内只能两个并发(unicorn 一个进程同一只能处理一个请求)。而 2 进程 32 线程的 puma,却可以处理 64 个并发(puma 一个线程处理一个请求)。

unicorn 跑 MRI,puma 跑 JVM,因为前者多进程,后者是多线程(我看了源码,是客户端 waiting full 的 Thread_pool),多线程在 MRI 有 GIL,相当于没并行,加上 IO 会堵塞(除了 IO 会释放),所以(基本)是串行的,那样环境下的 PUMA 等于(不亚于)单进程单线程。 楼上说的代码线程安全,加锁就好,另外,多进程有缺陷:互相之间超难通信,过程复杂,而且传输的东西有时候不全(用 pipe + marshal)

jakit 回复

所以是串行的

不要听风就是雨好不好

你自己试试 MRI puma 单进程两个线程, 两个并发请求会不会堵塞再来说话

7 楼 已删除
Rei 回复

Thank you very much~ That's what I need

jakit 回复

建议补一补 GIL

cxh116 回复

请求处理时间长的,即便通过线程方式,同一时间能接入更多请求,但也只能是接入,并不意味着能并发在处理这些请求,其结果是,请求接入数增加,请求处理的等待时间也在增加,超时数也在增加。

luikore 回复

再次回复:

终于抽时间写了个 test

require "benchmark"

result = Benchmark.measure("normal") do
  100.times do
    f = File.open("/Users/***/Downloads/***.dmg", "r")
    s = f.read
    f.close
    # p "finish #{s.length}"
  end
end

p result

result = Benchmark.measure("concurrent") do
  threads = []
  100.times do
    t = Thread.new do
      f = File.open("/Users/***/Downloads/***.dmg", "r")
      # p f
      s = f.read
      f.close
      # p "finish #{s.length}"
    end
    threads << t
  end
  threads.each do |t|
    t.join
  end
end

p result

while true

end

运行结果:

#<Benchmark::Tms:0x007fecde927d90 @label="normal", @real=0.2543760000007751, @cstime=0.0, @cutime=0.0, @stime=0.24, @utime=0.010000000000000002, @total=0.25>
#<Benchmark::Tms:0x007fecde917648 @label="concurrent", @real=0.1437469999982568, @cstime=0.0, @cutime=0.0, @stime=0.31000000000000005, @utime=0.020000000000000004, @total=0.33000000000000007>

证实以下结论:

IO 是不堵塞的

没什么太大的好处,但也并不是完全没有用,刚比较了一下,小并发,还是比较快的,但是数据量一大就不行了(因为 Ruby 都是把数据不管二进制还是字符流存在 String,数据大了很卡)。。。

另外,IO 密集的程序应该是做日志系统吧。。。但是日志系统一般都有逻辑,而不会直接就执行一行 read 和 write,所以还是没有优势。

不过如果是简单的日志系统,单线程的,那还是比较有价值的。

适用场景:个人博客(基本不涉及计算,而且偏向静态所以多数都是 IO 操作)、展示型网站、bug 追踪系统、工作流系统。

GIL 让密集计算串行起来其实让开发者更安心用多线程,其实 “并行” 还是有的,只是模拟(通过给定每个 Thread 执行时间作业),对于非高需求的开发者,实际上这样可以降低开发难度(满足简单软件开发)。所以也是满足了这门语言适合对 CS(Computer Science)不 Geek 的人群使用。

另外,一开始是我用 C 多线程来改写了 amber-kit 项目部分的代码,后来发现把 Ruby 的 VM 弄死了(回去再补图)。所以才在 5 楼回复了一下,结果被楼上楼下的各种误会(其实没有人知道我做了什么)。

在 C 多线程 pthread 下,MRI 或无法回收 pthread 里面执行的堆栈的 VALUE,混编需要注意这点。

然后跑了 1000 个并发(其实没到 1000,大概在 200 - 400 左右)就挂了,然后报错 segment fault,一堆 ruby gc 相关的错误 trace 就出现了。

其实我只是想绕开 GIL,但是还是失败了

jakit 回复

因为 Ruby 都是把数据不管二进制还是字符流存在 String,数据大了很卡

没有任何因果关系....

MRI 或无法回收 pthread 里面执行的堆栈的 VALUE

可以的, 你 segment fault 的原因可能是没有把 thread 注册进去 Ruby VM, 导致 GC mark 不到各个 thread 的 stack, mark 不到就会回收掉. 不是无法回收而是过早回收了...

如何让 GC 知道所有 thread 呢?

VM 对所有在 Ruby 中创建的线程有跟踪, rb_vm_t 有个列表字段叫 living_threads, 其中的每个元素的类型是 rb_thread_t.

rb_thread_t 结构体有个 machine 字段, 它里面有 stack_start/stack_end/register_stack_start/register_stack_end 字段.

所以在 GC 的时候 living_threads 的所有 stack 的 VALUE 都可以 mark 到.

简单的办法就是: 你直接用 Ruby 的 thread API 去创建线程就好了. 如果你还是坚持自己用 pthread 或者 win_thread 去创建, 你可以把创建后的 thread 包装成 Ruby thread 然后注册到 GC 的 root set 中去.

@jakit 你说的多线程编程问题在所有 GC 里都有, 如果你在 GC 体系外自己创建了新线程, 而新线程里带有待 GC 对象, 你就得向 GC 注册你自己创建的线程. 例如 Boehm GC 就得调用:

GC_register_my_thread / GC_unregister_my_thread 去处理

Geek 人群是可以关闭 GVL 的 -- 首先你得知道你在干啥, 会不会有影响. 对应的 API 是 rb_thread_call_without_gvl

luikore 回复

谢谢,我看这些在很多网站都找不到资源,也不知道怎么解决,不过你倒是帮我解决了这个问题,再次谢谢~

其实当时我已经意识到没有 mark 那个变量,然后尝试各种手动删除,但是翻了半天没找到方案。

luikore 回复

另外还有一个问题,就是我坚持用 pthread 再注册能不能绕开 GIL ?

jakit 回复

说不定哪个操作里一不小心就锁上了, 最好用 rb_thread_call_without_gvl

需要 登录 后方可回复, 如果你还没有账号请 注册新账号