- A+
经过大量的实验(在 disqus.com和 getsentry.com上),我可以确切的说:uwsgi应该成为Python世界的标准。 把它和nginx结合,在基于 Python的Web应用程序上你能获得在线程(或非线程)之上更好的性能体验。
更新:忽略古老的说法“你给任何度量是慢”,我在这里说的请求是指后端节点,他们处理输入事件(从20KB到1MB大小的请求),在网络跳过数跳经过各种授权和配额策略,并最形成一些队列操作。卸载尽可能多的工作负载。(本段翻译有问题,请参考原文,译者注)
服务策略
目前已经有相当数量的方法可以用来运行Python应用程序。我不打算使用mod_wsgi,最重要的,我并不想说明事件模型如何工作。我不相信在Python的世界它们依旧使用,所以这篇文章的主题也不是关于传统的线程(或多进程)的Python应用程序。
相反,我将专注于两个最流行且我最熟悉的解决方案:gunicorn和uwsgi。
Gunicorn(Python UNIX平台的wsgi服务器)
回顾过去,Python的Web服务器的解决方案基本上只有mod_wsgi。其中最流行的(或理解为时尚)的方法是最近Gunicorn。
实际上,我仍然建议使用gunicorn,这样可以极大的减少不便:它可以漂亮的嵌入Django而且设置简单。
它也有10%的配置选项和uwsgi一致(这对某些人来说是件好事),除此之外,比较看来,它提供了与uwsgi(或任何其他Python Web服务器)几乎相同的基本特性。
uwsgi
在我看来这是唯一的选择,从Gunicorn到uwsgi。将有更高性能的,有更多极易明白的配置选项,通过协议可以与nginx交互也增加了优势。
它的配置也是相当简单,找到一篇文章相关文章就可以了,后来更多。
我开始使用uwsgi来跑一些应用,使用–processes=10和–threads=10来测试服务器的多CPU,目的有两个:
- 支持情况
- 测试降低内存使用量的可能性
- 测试线程安全的支持情况
(对于这些测试是否值得,DISQUS是 单线程运行的,我想保持尽可能的精简,把每个节点的能力发挥到极致)
不断趋向成功的迭代
我们使API平均响应时间降到40ms以内,我非常自豪。这里我说的API相应时间是指:从请求击中了Python服务器到服务器返回响应到代理所花费的时间。
不幸的是,当我们始获得越来越大的流量并出现访问尖峰后响应时间出现问题了,波动的响应时间不再符合我们开始的设想,尽管服务节点上我们仍然有大约30%的内存和60%的资源空余。
在不少调整后,我们停用了大量uwsgi进程的方法,让nginx的负载均衡它们(之前是让uwsgi本身负载平衡)。
这意味着什么呢,是不是做uwsgi过程= 10,我们运行10个单独的uwsgi实例代替–processes=10。
其结果是一个美丽的,一致的20ms的平均响应时间。
API响应时间
将他们组合在一起
我喜欢着手去做而非空谈,这里我给大家一些我们在线服务器的实际设置。
nginx
配置的第一块是Nginx的,我们需要实际计算并添加uwsgi的进程 后端数量,所以事情有点复杂。
我们首先建立在我们的网页配置列表:
# recipes/web.rb hosts = (0..(node[:getsentry][:web][:processes] - 1)).to_a.map do |x| port = 9000 + x "127.0.0.1:#{port}" end template "#{node['nginx']['dir']}/sites-available/getsentry.com" do source "nginx/getsentry.erb" owner "root" group "root" variables( :hosts => hosts ) mode 0644 notifies :reload, "service[nginx]" end
Nginx的配置很简单:
# templates/getsentry.erb upstream internal { <% @hosts.each do |host| %> server <%= host %>; <% end %> } server { location / { uwsgi_pass internal; uwsgi_param Host $host; uwsgi_param X-Real-IP $remote_addr; uwsgi_param X-Forwarded-For $proxy_add_x_forwarded_for; uwsgi_param X-Forwarded-Proto $http_x_forwarded_proto; include uwsgi_params; } }
现在,我们已经设置了uwsgi的主机数量并分配了权重值,从9000端口开始,它们都是被uwsgi配置使用的套接字地址。
uwsgi
另一方面,我们使用supervisor来控制uwsg进程,这也非常简单:
# recipes/web.rb command = "/srv/www/getsentry.com/env/bin/uwsgi -s 127.0.0.1:90%(process_num)02d --need-app --disable-logging --wsgi-file getsentry/wsgi.py --processes 1 --threads #{node['getsentry']['web']['threads']}" supervisor_service "web" do directory "/srv/www/getsentry.com/current/" command command user "dcramer" stdout_logfile "syslog" stderr_logfile "syslog" startsecs 10 stopsignal "QUIT" stopasgroup true killasgroup true process_name '%(program_name)s %(process_num)02d' numprocs node['getsentry']['web']['processes'] end
位置的选择
除非有人想出了一个非常有说服力的论据:为什么应该有另一种方式(或某种该情形下不能工作的情况),我希望能听到这种模式因为Python的世界变得更标准。最起码,我希望看到关于如何提高uwsgi内进程管理的一些辩论的火花。
如果你要精简这个帖子,留下这句话:uwsgi线程(或非线程)服务是Python的Web应用的程序唯一选择。
(我匆匆写了这篇文章来说明今天的一些研究结果,所以难免简单,错别字请谅解)