ruby/Rack应用快速入门
以下指导将带你安装和运行一个基于Ruby的uWSGI发行版 ,旨在运行Rack应用。
安装带Ruby支持的uWSGI
要构建uWSGI,你需要一个C编译器 (支持gcc和clang) 和Python二进制文件 (用来运行uwsgiconfig.py脚本,它将执行各种编译步骤)。
因为我们正在构建一个带Ruby支持的uWSGI二进制文件,因此我们也需要Ruby开发头文件 (基于Debian发行版上的 ruby-dev
包)。
你可以手工构建uWSGI —— 这些都是等价的:
- make rack
- UWSGI_PROFILE=rack make
- make PROFILE=rack
- python uwsgiconfig.py --build rack
但是如果你懒癌犯了,那么你可以一次性下载、构建和安装一个uWSGI + Ruby二进制文件:
- curl http://uwsgi.it/install | bash -s rack /tmp/uwsgi
或者以一种更“Ruby友好型”方式:
- gem install uwsgi
这些方法都构建一个“单片”uWSGI二进制文件。uWSGI项目是由许多插件注册的。你可以选择构建服务器核心,并为每个特性使用一个插件(需要时加载),或者你可以构建一个带有所有你需要的特性的单个二进制文件。后者这种构建称之为“单片”。
这个快速入门假设使用一个单片二进制文件 (因此你无需加载插件)。如果你更喜欢使用你的包发行版 (而不是从官方来源构建uWSGI),那么见下。
关于发布包的注意事项
你的发行版非常有可能包含一个uWSGI包集合。那些uWSGI包趋向于高模块化的 (并且偶尔高度过时),因此,除了核心部分之外,你需要安装所需插件。必须在你的uWSGI配置中加载插件。在学习阶段,我们强烈建议不要使用发行版包,从而轻松跟着文档和教程。
一旦你适应了“uWSGI方式”,你就可以为你的部署选择最佳方式。
例如,本教程利用”http”和”rack”插件。如果你正使用模块化构建,那么确保通过 —plugins http,rack
选项加载它们。
你的第一个Rack应用
Rack是编写Ruby web应用的标准方式。
这是一个标准的Rack Hello world脚本 (称之为app.ru):
- class App
- def call(environ)
- [200, {'Content-Type' => 'text/html'}, ['Hello']]
- end
- end
- run App.new
扩展名 .ru
表示”rackup”,这是Rack发行版中包含的部署工具。Rackup使用了一点DSL,因此,要将其用到uWSGI中,你需要安装rack gem:
- gem install rack
现在,我们准备好部署uWSGI了:
- uwsgi --http :8080 --http-modifier1 7 --rack app.ru
(如果‘uwsgi’并不在你当前的$PATH中,记得替换它)
或者如果你正使用的是一个模块化构建 (就像你的发行版的那个)
- uwsgi --plugins http,rack --http :8080 --http-modifier1 7 --rack app.ru
使用这个命令行,我们生成了一个HTTP代理,路由每个请求到一个进程 (名为’worker’),进程管理它并把响应发送回HTTP路由器 (接着路由器发送回客户端)。
如果你对为什么生成两个进程有疑问,那么(我告诉你)是因为这是你将会在生产上使用的正常架构 (一个带有一个后端应用服务器的前线web服务器)。
如果你不想要生成HTTP代理,而是直接强制worker应答HTTP请求,那么仅需将命令行修改为
- uwsgi --http-socket :8080 --http-socket-modifier1 7 --rack app.ru
现在,你有了单个管理请求的进程了 (但记住,直接将应用服务器公开一般是危险的,并且并不通用)。
那个’–http-modifier1 7’东东是啥?
uWSGI支持多种语言和平台。当服务器接收到一个请求的时候,它必须知道将其“路由” 到哪里。
每个uWSGI插件都有一个分配的数字 (即modifier),ruby/rack这个使用的是7。因此, —http-modifier1 7
意思是“路由到rack插件”。
虽然uWSGI也有一个更加“人类友好型”的 internal routing system ,但是,使用modifier是最快的方式,所以,尽可能使用它们。
使用一个完整的web服务器:nginx
提供的HTTP路由器,是 (是哒,真的已经够了) 只是一个路由器。你可以将它当成一个负载均衡器或者代理使用,但是如果你需要一个完整的web服务器 (用于有效提供静态文件,或者一个web服务器所擅长的所有那些任务),那么你可以摆脱uwsgi HTTP路由器 (如果你正使用模块化构建,那么记得修改–plugins http,rack为–plugins rack),然后将你的应用放在Nginx之后。
为了与Nginx通信,uWSGI可以使用多个协议:HTTP, uwsgi, FastCGI, SCGI, 等等。
最有效的是uwsgi。Nginx默认支持uwsgi协议。
在一个uwsgi socket上运行你的rack应用:
- uwsgi --socket 127.0.0.1:3031 --rack app.ru
然后在你的nginx配置中添加一个location节
- location / {
- include uwsgi_params;
- uwsgi_pass 127.0.0.1:3031;
- uwsgi_modifier1 7;
- }
重载你的nginx服务器,然后它应该开始代理请求到你的uWSGI实例。
注意,你不需要配置uWSGI来设置一个指定的modifier,nginx将会使用 uwsgi_modifier1 5;
指令来实现它。
添加并发性
通过前面的例子,你部署了一个能够服务单个请求的栈。
要增加并发性,你需要添加更多的进程。如果你希望有一个魔法数学公式可以找到要生成的正确进程数,嗯……很抱歉,我们没有。你需要进行实验,并且监控你的应用,以找到正确的值。考虑到每个进程是你的应用的一个完全拷贝,因此,必须考虑内存使用。
要添加更多的进程,只需使用 –processes <n> 选项:
- uwsgi --socket 127.0.0.1:3031 --rack app.ru --processes 8
将生成8个进程。
Ruby 1.9/2.0引入了一个改进的线程支持,uWSGI通过’rbthreads’插件支持它。当你编译uWSGI+ruby (>=1.9)单片二进制文件的时候,会自动内建这个插件。
要添加更多线程:
- uwsgi --socket 127.0.0.1:3031 --rack app.ru --rbthreads 4
或者线程+进程
- uwsgi --socket 127.0.0.1:3031 --rack app.ru --processes --rbthreads 4
有其他(一般更高级/复杂)的方式来提高并发性 (例如,’fiber’),但是,大多时候,你最终会得到一个普通的旧式多进程或者多线程模型。如果你感兴趣,那么请查看 Rack
之上的完整文档。
添加健壮性:Master进程
It is highly recommended to have the uWSGI master process always running on productions apps.
It will constantly monitor your processes/threads and will add fun features like the uWSGI Stats服务器.
To enable the master simply add —master
- uwsgi --socket 127.0.0.1:3031 --rack app.ru --processes 4 --master
使用配置文件
uWSGI有数百个选项 (但一般来说,你不会使用超过数十个)。通过命令行处理它们有点蠢,因此,试着总是使用配置文件。
uWSGI支持各种标准 (XML, INI, JSON, YAML, 等等)。从一个移到另一个相当简单。你可以通过命令行使用的相同的选项也可以在配置文件中使用,只需将前缀 —
移除:
- [uwsgi]
- socket = 127.0.0.1:3031
- rack = app.ru
- processes = 4
- master = true
或者xml:
- <uwsgi>
- <socket>127.0.0.1:3031</socket>
- <rack>app.ru</rack>
- <processes>4</processes>
- <master/>
- </uwsgi>
要通过配置文件运行uWSGI,只需将其当做一个参数指定:
- uwsgi yourconfig.ini
如果出于某些原因,你的配置不以期望的扩展名 (.ini, .xml, .yml, .js) 结尾,那么你可以通过这种方式,强制二进制文件使用一个指定的解析器:
- uwsgi --ini yourconfig.foo
- uwsgi --xml yourconfig.foo
- uwsgi --yaml yourconfig.foo
等等等等。
你甚至可以通过管道使用配置 (使用破折号强制从标准输入中读取):
- ruby myjsonconfig_generator.rb | uwsgi --json -
当你生成多个进程时的fork()问题
uWSGI某种程度上是“Perl化的”,我们没法隐藏它。它的大多数抉择 (从“不止一种实现的方式”开始) 来自于Perl世界 (更一般来说,是来自于传统的UNIX系统管理员方法)。
当应用到其他语言/平台上的时候,这个方法有时会导致意外行为。
当你开始学习uWSGI的时候,你会面对的其中一个“问题”是它的 fork()
使用。
默认情况下,uWSGI在第一个生成的进程中加载你的应用,然后多次 fork()
自身。
这意味着,你的应用被单次加载,然后被拷贝。
虽然这个方法加速了服务器的启动,但是有些应用在这项技术下会出问题 (特别是那些在启动时初始化db连接的应用,因为将会在子进程中继承连接的文件描述符)。
如果你对uWSGI使用的粗暴的preforking不确定,那么只需使用 —lazy-apps
选项来禁用它。它将会强制uWSGI在每个worker中完全加载你的应用一次。
部署Sinatra
让我们忘掉fork(),回到有趣的事情上来。这次,我们部署一个Sinatra应用:
- require 'sinatra'
- get '/hi' do
- "Hello World"
- end
- run Sinatra::Application
将其另存为 config.ru
,然后如前所见那样运行:
- [uwsgi]
- socket = 127.0.0.1:3031
- rack = config.ru
- master = true
- processes = 4
- lazy-apps = true
- uwsgi yourconf.ini
好吧,或许你已经注意到,基本与前面的app.ru例子没啥区别。
这是因为,基本上,每个现代的Rack应用都将其自身作为一个.ru文件(一般称为config.ru)公开,因此,无需使用多个选项来加载应用 (例如,像Python/WSGI世界中的那样)。
部署RubyOnRails >= 3
从3.0起,Rails就是完全兼容Rack的,并且它公开了一个你可以直接加载的config.ru文件 (就像我们用Sinatra那样)。
与Sinatra的唯一不同是,你的项目有一个特定的布局/约定,期望你当前的工作目录是包含项目的那个目录,因此,让我们添加一个chdir选项:
- [uwsgi]
- socket = 127.0.0.1:3031
- rack = config.ru
- master = true
- processes = 4
- lazy-apps = true
- chdir = <path_to_your_rails_app>
- env = RAILS_ENV=production
- uwsgi yourconf.ini
除了chdir之外,我们添加了’env’选项,它设置 RAILS_ENV
环境变量。
从4.0起,Rails支持多线程 (只适用于ruby 2.0):
- [uwsgi]
- socket = 127.0.0.1:3031
- rack = config.ru
- master = true
- processes = 4
- rbthreads = 2
- lazy-apps = true
- chdir = <path_to_your_rails_app>
- env = RAILS_ENV=production
部署更老的RubyOnRails
较老的Rails版本并非完全Rack兼容的。出于这样的原因,uWSGI中有一个特定的选项,用来加载较老的Rails应用 (你也会需要’thin’这个gem)。
- [uwsgi]
- socket = 127.0.0.1:3031
- master = true
- processes = 4
- lazy-apps = true
- rails = <path_to_your_rails_app>
- env = RAILS_ENV=production
所以,长话短说,指定 rails
选项,将rails应用的目录(而不是Rackup文件)作为参数传递。
Bundler和RVM
Bundler是用来管理依赖的标准的事实上的Ruby工具。基本上,你在Gemfile文本文件中指定你的应用所需的gem,然后启动bundler来安装它们。
要让uWSGI遵守bundler安装,你只需要添加:
- rbrequire = rubygems
- rbrequire = bundler/setup
- env = BUNDLE_GEMFILE=<path_to_your_Gemfile>
(第一个并非ruby 1.9/2.x要求的必须节。)
基本上,这些行强制uWSGI加载bundler引擎,并且使用在 BUNDLE_GEMFILE
环境变量中指定的Gemfile。
当使用Bundler的时候 (就像现代框架所做的那样),你的常见部署配置将会是:
- [uwsgi]
- socket = 127.0.0.1:3031
- rack = config.ru
- master = true
- processes = 4
- lazy-apps = true
- rbrequire = rubygems
- rbrequire = bundler/setup
- env = BUNDLE_GEMFILE=<path_to_your_Gemfile>
除了Bundler之外,RVM是另一个常见的工具。
它允许你在单个系统中安装多个 (独立的) Ruby版本 (带自己的gemset)。
要指示uWSGI使用一个指定RVM版本的gemset,只需使用 –gemset 选项:
- [uwsgi]
- socket = 127.0.0.1:3031
- rack = config.ru
- master = true
- processes = 4
- lazy-apps = true
- rbrequire = rubygems
- rbrequire = bundler/setup
- env = BUNDLE_GEMFILE=<path_to_your_Gemfile>
- gemset = ruby-2.0@foobar
只是注意,对于每个Ruby版本(是Ruby版本,不是gemset!),你都需要一个uWSGI二进制文件(或者如果你使用模块化构建,则是一个插件)。
如果你有兴趣,那么这里是构建uWSGI核心+每个rvm中安装的Ruby版本1个插件的命令列表。
- # build the core
- make nolang
- # build plugin for 1.8.7
- rvm use 1.8.7
- ./uwsgi --build-plugin "plugins/rack rack187"
- # build for 1.9.2
- rvm use 1.9.2
- ./uwsgi --build-plugin "plugins/rack rack192"
- # and so on...
然后,如果你想要使用ruby 1.9.2和@oops gemset:
- [uwsgi]
- plugins = ruby192
- socket = 127.0.0.1:3031
- rack = config.ru
- master = true
- processes = 4
- lazy-apps = true
- rbrequire = rubygems
- rbrequire = bundler/setup
- env = BUNDLE_GEMFILE=<path_to_your_Gemfile>
- gemset = ruby-1.9.2@oops
开机时自动启动uWSGI
如果你正想着打开vi,然后编写一个init.d脚本来生成uWSGI,那么坐下(并且淡定),首先确保你的系统并没有提供一个更好(更现代)的方法。
每个发行版都选择了一个启动系统 (Upstart, Systemd…),并且有大量可用的进程管理器 (supervisord, god, monit, circus…)。
uWSGI将与它们完美集成(我们希望),但是如果你计划部署大量的应用,那么看看uWSGI Emperor - 它或多或少是每个devops工程师的梦想。
安全性和可用性
总是避免以root运行你的uWSGI实例。你可以使用uid和gid选项来移除特权。
- [uwsgi]
- socket = 127.0.0.1:3031
- uid = foo
- gid = bar
- chdir = path_toyour_app
- rack = app.ru
- master = true
- processes = 8
web应用部署的一个常见问题是“卡住的请求”。你所有的线程/worker都卡住了 (请求阻塞) ,而你的应用无法接收更多的请求。
要避免这个问题,你可以设置一个 harakiri
定时器。它是一个监控器 (由master进程管理),会摧毁那些卡住超过指定秒数的进程。
- [uwsgi]
- socket = 127.0.0.1:3031
- uid = foo
- gid = bar
- chdir = path_toyour_app
- rack = app.ru
- master = true
- processes = 8
- harakiri = 30
这将会摧毁那些阻塞超过30秒的worker。小心选择 harakiri
值!
除此之外,自uWSGI 1.9起,stats服务器导出了全部请求变量,因此,你可以(实时)看到你的实例正在做什么 (对于每个worker, thread 或者异步核)。
启用统计信息服务器是很简单的:
- [uwsgi]
- socket = 127.0.0.1:3031
- uid = foo
- gid = bar
- chdir = path_to_your_app
- rack = app.ru
- master = true
- processes = 8
- harakiri = 30
- stats = 127.0.0.1:5000
只需将其绑定到一个地址 (UNIX或者TCP) 上,并且只需连接 (你也可以使用telnet) 到它上面,来接收你的实例的一个JSON内容。
uwsgitop
应用 (你可以在官方的github仓库中找到它) 是一个使用统计信息服务器来实现类top实时监控的工具的例子 (有颜色!!!)
内存使用
低内存使用是整个uWSGI项目的卖点之一。
不幸的是,默认对内存的积极态度也许(看好:是也许)会导致某些性能问题。
默认情况下,uWSGI Rack插件在每次请求之后调用Ruby GC (垃圾回收器)。如果你想要减少这个频率,只需添加–rb-gc-freq <n>
选项,其中,n是调用GC之后的请求数。
如果你计划进行uWSGI的基准 (或者把它与其他方法对比),那么考虑它对GC的使用。
Ruby可以是一个真正的内存吞噬者,因此,我们更喜欢默认积极对待内存,而不是取悦hello-world基准点。
卸载
uWSGI 卸载(offloading)子系统 允许你在某些特定的模式匹配的时候尽快释放你的worker,并且能够委托给一个纯c线程。例如,发送来自文件系统的静态文件,从网络传输数据到客户端,等等。
卸载是非常复杂的,但是它的使用对终端用户是透明的。如果你想要试一试,那么只需添加–offload-threads <n>,其中,<n>是要生成的线程数 (一个cpu一个线程是个不错的值)。
当启用了卸载线程时,所有可以被优化的部分将会被自动检测到。
以及现在
有了这些许概念,你应该已经可以上生产了,但是uWSGI是个巨大的项目,它有数百个特性和配置。如果你想要成为一个更好的系统管理员,那么请求继续阅读完整的文档。
欢迎进入uWSGI的世界!