糟糕的Django / uwsgi性能

我正在用nginx&uwsgi运行一个django应用程序。 以下是我如何运行uwsgi:

sudo uwsgi -b 25000 --chdir=/www/python/apps/pyapp --module=wsgi:application --env DJANGO_SETTINGS_MODULE=settings --socket=/tmp/pyapp.socket --cheaper=8 --processes=16 --harakiri=10 --max-requests=5000 --vacuum --master --pidfile=/tmp/pyapp-master.pid --uid=220 --gid=499 

&nginxconfiguration:

 server { listen 80; server_name test.com root /www/python/apps/pyapp/; access_log /var/log/nginx/test.com.access.log; error_log /var/log/nginx/test.com.error.log; # https://docs.djangoproject.com/en/dev/howto/static-files/#serving-static-files-in-production location /static/ { alias /www/python/apps/pyapp/static/; expires 30d; } location /media/ { alias /www/python/apps/pyapp/media/; expires 30d; } location / { uwsgi_pass unix:///tmp/pyapp.socket; include uwsgi_params; proxy_read_timeout 120; } # what to serve if upstream is not available or crashes #error_page 500 502 503 504 /media/50x.html; } 

问题来了。 当在服务器上做“ab”(ApacheBenchmark)时,我得到以下结果:

nginx版本:nginx版本:nginx / 1.2.6

uwsgi版本:1.4.5

 Server Software: nginx/1.0.15 Server Hostname: pycms.com Server Port: 80 Document Path: /api/nodes/mostviewed/8/?format=json Document Length: 8696 bytes Concurrency Level: 100 Time taken for tests: 41.232 seconds Complete requests: 1000 Failed requests: 0 Write errors: 0 Total transferred: 8866000 bytes HTML transferred: 8696000 bytes Requests per second: 24.25 [#/sec] (mean) Time per request: 4123.216 [ms] (mean) Time per request: 41.232 [ms] (mean, across all concurrent requests) Transfer rate: 209.99 [Kbytes/sec] received 

在500个并发级别上运行

 oncurrency Level: 500 Time taken for tests: 2.175 seconds Complete requests: 1000 Failed requests: 50 (Connect: 0, Receive: 0, Length: 50, Exceptions: 0) Write errors: 0 Non-2xx responses: 950 Total transferred: 629200 bytes HTML transferred: 476300 bytes Requests per second: 459.81 [#/sec] (mean) Time per request: 1087.416 [ms] (mean) Time per request: 2.175 [ms] (mean, across all concurrent requests) Transfer rate: 282.53 [Kbytes/sec] received 

正如您所看到的…服务器上的所有请求都会因超时错误或“客户端过早断开”而失败,或者:

 writev(): Broken pipe [proto/uwsgi.c line 124] during GET /api/nodes/mostviewed/9/?format=json 

下面是关于我的应用程序的更多信息:基本上,这是一个反映包含所有内容的MySQL表的模型集合。 在前端,我有django-rest-framework为客户提供json内容。

我已经安装了django-profiling&djangodebugging工具栏来查看正在发生的事情。 在Django的分析这里是我得到什么时,运行一个请求:

 Instance wide RAM usage Partition of a set of 147315 objects. Total size = 20779408 bytes. Index Count % Size % Cumulative % Kind (class / dict of class) 0 63960 43 5726288 28 5726288 28 str 1 36887 25 3131112 15 8857400 43 tuple 2 2495 2 1500392 7 10357792 50 dict (no owner) 3 615 0 1397160 7 11754952 57 dict of module 4 1371 1 1236432 6 12991384 63 type 5 9974 7 1196880 6 14188264 68 function 6 8974 6 1076880 5 15265144 73 types.CodeType 7 1371 1 1014408 5 16279552 78 dict of type 8 2684 2 340640 2 16620192 80 list 9 382 0 328912 2 16949104 82 dict of class <607 more rows. Type eg '_.more' to view.> CPU Time for this request 11068 function calls (10158 primitive calls) in 0.064 CPU seconds Ordered by: cumulative time ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.064 0.064 /usr/lib/python2.6/site-packages/django/views/generic/base.py:44(view) 1 0.000 0.000 0.064 0.064 /usr/lib/python2.6/site-packages/django/views/decorators/csrf.py:76(wrapped_view) 1 0.000 0.000 0.064 0.064 /usr/lib/python2.6/site-packages/rest_framework/views.py:359(dispatch) 1 0.000 0.000 0.064 0.064 /usr/lib/python2.6/site-packages/rest_framework/generics.py:144(get) 1 0.000 0.000 0.064 0.064 /usr/lib/python2.6/site-packages/rest_framework/mixins.py:46(list) 1 0.000 0.000 0.038 0.038 /usr/lib/python2.6/site-packages/rest_framework/serializers.py:348(data) 21/1 0.000 0.000 0.038 0.038 /usr/lib/python2.6/site-packages/rest_framework/serializers.py:273(to_native) 21/1 0.000 0.000 0.038 0.038 /usr/lib/python2.6/site-packages/rest_framework/serializers.py:190(convert_object) 11/1 0.000 0.000 0.036 0.036 /usr/lib/python2.6/site-packages/rest_framework/serializers.py:303(field_to_native) 13/11 0.000 0.000 0.033 0.003 /usr/lib/python2.6/site-packages/django/db/models/query.py:92(__iter__) 3/1 0.000 0.000 0.033 0.033 /usr/lib/python2.6/site-packages/django/db/models/query.py:77(__len__) 4 0.000 0.000 0.030 0.008 /usr/lib/python2.6/site-packages/django/db/models/sql/compiler.py:794(execute_sql) 1 0.000 0.000 0.021 0.021 /usr/lib/python2.6/site-packages/django/views/generic/list.py:33(paginate_queryset) 1 0.000 0.000 0.021 0.021 /usr/lib/python2.6/site-packages/django/core/paginator.py:35(page) 1 0.000 0.000 0.020 0.020 /usr/lib/python2.6/site-packages/django/core/paginator.py:20(validate_number) 3 0.000 0.000 0.020 0.007 /usr/lib/python2.6/site-packages/django/core/paginator.py:57(_get_num_pages) 4 0.000 0.000 0.020 0.005 /usr/lib/python2.6/site-packages/django/core/paginator.py:44(_get_count) 1 0.000 0.000 0.020 0.020 /usr/lib/python2.6/site-packages/django/db/models/query.py:340(count) 1 0.000 0.000 0.020 0.020 /usr/lib/python2.6/site-packages/django/db/models/sql/query.py:394(get_count) 1 0.000 0.000 0.020 0.020 /usr/lib/python2.6/site-packages/django/db/models/query.py:568(_prefetch_related_objects) 1 0.000 0.000 0.020 0.020 /usr/lib/python2.6/site-packages/django/db/models/query.py:1596(prefetch_related_objects) 4 0.000 0.000 0.020 0.005 /usr/lib/python2.6/site-packages/django/db/backends/util.py:36(execute) 1 0.000 0.000 0.020 0.020 /usr/lib/python2.6/site-packages/django/db/models/sql/query.py:340(get_aggregation) 5 0.000 0.000 0.020 0.004 /usr/lib64/python2.6/site-packages/MySQLdb/cursors.py:136(execute) 2 0.000 0.000 0.020 0.010 /usr/lib/python2.6/site-packages/django/db/models/query.py:1748(prefetch_one_level) 4 0.000 0.000 0.020 0.005 /usr/lib/python2.6/site-packages/django/db/backends/mysql/base.py:112(execute) 5 0.000 0.000 0.019 0.004 /usr/lib64/python2.6/site-packages/MySQLdb/cursors.py:316(_query) 60 0.000 0.000 0.018 0.000 /usr/lib/python2.6/site-packages/django/db/models/query.py:231(iterator) 5 0.012 0.002 0.015 0.003 /usr/lib64/python2.6/site-packages/MySQLdb/cursors.py:278(_do_query) 60 0.000 0.000 0.013 0.000 /usr/lib/python2.6/site-packages/django/db/models/sql/compiler.py:751(results_iter) 30 0.000 0.000 0.010 0.000 /usr/lib/python2.6/site-packages/django/db/models/manager.py:115(all) 50 0.000 0.000 0.009 0.000 /usr/lib/python2.6/site-packages/django/db/models/query.py:870(_clone) 51 0.001 0.000 0.009 0.000 /usr/lib/python2.6/site-packages/django/db/models/sql/query.py:235(clone) 4 0.000 0.000 0.009 0.002 /usr/lib/python2.6/site-packages/django/db/backends/__init__.py:302(cursor) 4 0.000 0.000 0.008 0.002 /usr/lib/python2.6/site-packages/django/db/backends/mysql/base.py:361(_cursor) 1 0.000 0.000 0.008 0.008 /usr/lib64/python2.6/site-packages/MySQLdb/__init__.py:78(Connect) 910/208 0.003 0.000 0.008 0.000 /usr/lib64/python2.6/copy.py:144(deepcopy) 22 0.000 0.000 0.007 0.000 /usr/lib/python2.6/site-packages/django/db/models/query.py:619(filter) 22 0.000 0.000 0.007 0.000 /usr/lib/python2.6/site-packages/django/db/models/query.py:633(_filter_or_exclude) 20 0.000 0.000 0.005 0.000 /usr/lib/python2.6/site-packages/django/db/models/fields/related.py:560(get_query_set) 1 0.000 0.000 0.005 0.005 /usr/lib64/python2.6/site-packages/MySQLdb/connections.py:8() 

..等等

不过,django-debug-toolbar显示如下:

 Resource Usage Resource Value User CPU time 149.977 msec System CPU time 119.982 msec Total CPU time 269.959 msec Elapsed time 326.291 msec Context switches 11 voluntary, 40 involuntary and 5 queries in 27.1 ms 

问题是,“顶部”显示负载平均值快速上升,我运行在本地服务器和networking内的远程机器上的Apache基准testing显示,我没有提供很多请求/秒。 问题是什么? 这是尽可能达到时分析代码,所以如果有人能指出我在这里做什么,将不胜感激。

编辑(23/02/2013):根据Andrew Alcock的回答添加更多细节:需要我注意/回答的要点是(3)(3)我在MySQL上执行了“显示全局variables”,发现MySQLconfiguration有max_connections设置151这是足够的服务为uwsgi我开始的工人。

(3)(4)(2)我描述的单个请求是最重的一个。 它根据django-debug-toolbar执行4个查询。 所发生的是,所有查询分别运行在:3.71,2.83,0.88,4.84 ms。

(4)这里你指的是内存分页? 如果是这样,我怎么知道?

(5)对16名工作者,100个并发率,1000个请求,负载平均值上升到12个。我对不同数量的工作者进行了testing(并发级别为100):

  1. 1名工人,平均负载〜1.85,19次/秒,每次请求时间:5229.520,非2xx
  2. 2名工人,平均负载〜1.5,19次/秒,每次请求时间:516.520,0非2xx
  3. 4名工人,平均负载〜3,16次/秒,每次请求时间:5929.921,0非2xx
  4. 8名工人,平均负载〜5,18次/秒,每次请求时间:5301.458,0非2xx
  5. 16名工人,平均负载〜19,15次/秒,每次请求时间:6384.720,0非2xx

正如你所看到的,我们拥有的工作人员越多,系统上的负担就越重。 我可以在uwsgi的守护进程日志中看到,当我增加工作人员的数量时,以毫秒为单位的响应时间增加。

在16位工作人员上,运行500个并发级别请求uwsgi开始logging错误:

  writev(): Broken pipe [proto/uwsgi.c line 124] 

负载也上升到~10。 而testing不需要太多的时间,因为non-2xx的响应是923(1000),这就是为什么这里的响应非常快,因为它几乎是空的。 这也是对总结中第4点的回复。

假设我在这里面临的是一个基于I / O和networking的操作系统延迟问题,build议采取什么行动来扩展它? 新硬件? 更大的服务器

谢谢

编辑1看看你有1个虚拟核心的评论,通过所有相关的点添加评论

编辑2从特立独行的更多信息,所以我消除了排除的想法和发展确认的问题。

编辑3填写更多关于uwsgi请求队列和缩放选项的细节。 改进的语法。

编辑4从特立独行和小的改进更新

评论太小,所以这里有一些想法:

  1. 负载平均基本上是有多less进程正在运行或等待CPU注意力。 对于1个CPU核心的完美加载系统,平均负载应为1.0; 对于一个4核心系统,应该是4.0。 当你运行networkingtesting的时候,线程火箭和你有很多进程在等待CPU。 除非平均负载超过了CPU内核的数量,否则这不是问题
  2. 第一个“每个请求的时间”值为4s与请求队列的长度相关 – 几乎是瞬间在Django上抛出1000个请求,并且平均需要4秒的服务时间,其中大约3.4秒在队列中等待。 这是由于请求数量(100)与处理器数量(16)之间的非常严重的不匹配,导致84个请求在任何时刻等待处理器。
  3. 以100的并发运行,testing以24个请求/秒的速度进行41秒。 你有16个进程(线程),所以每个请求处理大约700毫秒。 鉴于您的交易types,这是每个请求很长一段时间。 这可能是因为:

    1. 在Django中,每个请求的CPU成本都很高(考虑到debugging工具栏的CPU值太低,
    2. 操作系统是任务切换很多(特别是如果平均负载高于4-8),延迟纯粹是由于过程太多。
    3. 没有足够的数据库连接为16个进程提供服务,因此进程正在等待有一个可用的进程。 每个进程至less有一个连接可用?
    4. 数据库周围有相当长的延迟

      1. 数十个小的请求,每个都需要10ms,其中大部分是networking开销。 如果是这样,你可以引入caching或减lessSQL调用到一个较小的数字。 要么
      2. 一个或几个请求正在100毫秒的毫秒。 要检查这个,在数据库上运行分析。 如果是这样,则需要优化该请求。
  4. 系统和用户CPU成本之间的分离在系统中非常高,尽pipe总CPU很低。 这意味着Django中的大部分工作都与内核相关,比如networking或磁盘。 在这种情况下,这可能是networking成本(例如,接收和发送HTTP请求以及接收和发送请求到数据库)。 有时这会因分页而变高。 如果没有分页,那么你可能根本就不用担心这个问题。

  5. 你已经把stream程设置为16,但是具有很高的平均负载(你没有多高) 。 理想情况下,您应始终至less有一个进程等待CPU(以便CPU不会空转)。 这里的进程似乎不受CPU限制,但是具有显着的延迟,所以您需要比内核更多的进程。 还有多less? 尝试使用不同数量的处理器(1,2,4,8,12,16,24等)运行uwsgi,直到获得最佳吞吐量。 如果更改平均过程的延迟,则需要再次调整。
  6. 500并发级别肯定是一个问题,但它是客户端还是服务器? 报告说50(100)中有不正确的内容长度,这意味着服务器问题。 非2xx也似乎指向那里。 是否有可能捕获非2xx响应debugging – 堆栈跟踪或特定的错误消息将非常有用 (编辑),并由uwsgi请求队列运行,默认值为100引起的。

总之:

在这里输入图像说明

  1. Django似乎很好
  2. 负载testing(100或500)与进程(16)之间的并发性不匹配:对于要处理的进程数量,您正在向系统推送太多的并发请求。 一旦你超过了进程的数量,所有将发生的事情就是你将延长Web服务器中的HTTP请求队列
  3. 有一个很大的延迟,所以也是

    1. 进程(16)和CPU核心(1)之间不匹配:如果平均负载> 3,则可能过多。 再次尝试less量的进程

      1. 平均负载> 2 – >尝试8个进程
      2. 平均负载> 4 – >尝试4个进程
      3. 平均负载> 8 – >尝试2个进程
    2. 如果负载平均值<3,则可能是在DB中,因此对DB进行configuration以查看是否有小负载请求(累加性地导致延迟)或一个或两个SQL语句是问题

  4. 在没有捕获失败的响应的情况下,我没有太多的关于500个并发的失败的说法

发展的想法

在一台单独的有芯机器上,你的负载平均值> 10是非常不好的,并且(如你所观察到的)导致很多任务切换和一般的慢速行为。 我个人不记得看到一台平均负载为19的机器(你有16个进程) – 恭喜你获得这么高的机器)

数据库的performance非常好,所以我现在就给出一个非常明确的答案。

寻呼 :要回答你如何看到分页的问题 – 你可以通过几种方式来检测操作系统分页。 例如,在顶部,标题具有页面input和输出(参见最后一行):

 进程:总共170个,3个运行,4个卡住,163个睡眠,927个线程15:06:31
平均负载:0.90,1.19,1.94 CPU使用率:1.37%用户,2.97%sys,95.65%空闲SharedLibs:144M驻留,0B数据,24M链接。
 MemRegions:总共31726,2541M居民,120M私人,817M共享。  PhysMem:1420M有线,3548M有源,1703M无源,6671M使用,1514M免费。
 VM:392G vsize,1286M框架vsize,1534241(0)pageins,0(0)pageout。 networking:包:789684 / 288M in,912863 / 482M出。 磁盘:739807 / 15G读取,996745 / 24G写入。 

进程数量 :在当前configuration中,进程数量太高。 将进程数回到2 。 稍后我们可能会提高这个值,这取决于将这个服务器的进一步负载转移。

Apache Benchmark的位置 :对于一个进程,1.85的负载平均值表明你正在运行与uwsgi相同的机器上的负载生成器 – 是正确的吗?

如果是这样,你真的需要从另一台机器上运行它,否则testing运行不代表实际的负载 – 你正在从networking进程中获取内存和CPU以用于负载生成器。 另外,负载生成器的100或500个线程通常会给您的服务器带来压力,而这种方式在现实生活中不会发生。 事实上,这可能是整个testing失败的原因。

DB的位置 :一个进程的平均负载也表明你正在运行在Web进程所在的同一台机器上的数据库 – 这是正确的吗?

如果我对数据库是正确的,那么开始扩展的第一个也是最好的方法是把数据库移到另一台机器上。 我们这样做有几个原因:

  1. 数据库服务器需要来自处理节点的不同硬件configuration文件:

    1. 磁盘:数据库需要大量快速冗余的备份磁盘,而处理节点只需要一个基本的磁盘
    2. CPU:处理节点需要最快的CPU,而数据库机器通常可以不用(通常其性能在磁盘和RAM上进行门控)
    3. 内存:一台数据库机器通常需要尽可能多的内存(最快的数据库的所有数据在内存中),而许多处理节点需要less得多(每个进程你的需要大约20MB – 非常小
    4. 缩放: primefaces数据库通过拥有多个CPU的怪物机器进行缩放,而networking层(不具有状态)可以通过插入许多相同的小型装填器来扩展。
  2. CPU亲和力:CPU的平均负载为1.0,进程与单个内核具有亲和性会更好。 这样做可以最大限度地利用CPU高速caching,并最大限度地减less任务切换开销。 通过分离数据库和处理节点,您正在硬件中强制执行此关联。

500次并发与exception上图中的请求队列最多为100个 – 如果uwsgi在队列满时接收到一个请求,请求将被拒绝并出现5xx错误。 我认为这发生在你的500个并发负载testing中 – 基本上是队列中填满了前100个线程,然后其他400个线程发出剩余的900个请求,并收到即时的5xx错误。

要处理每秒500个请求,您需要确保两件事情:

  1. 请求队列大小configuration为处理突发:使用--listen参数uwsgi
  2. 如果500是正常条件,则系统可以以每秒500个请求的速度处理吞吐量,如果500是峰值,则系统可以处理吞吐量。 参见下面的缩放注释。

我想,uwsgi的队列设置为较小的数字,以更好地处理DDoS攻击; 如果处于巨大的负载下,大多数请求立即失败,几乎不进行任何处理,从而使得整个盒子仍然能够响应pipe理员。

对系统进行缩放的一般build议

您最重要的考虑可能是最大化吞吐量 。 另一个可能需要尽量减less响应时间,但我不会在这里讨论这个。 为了最大化吞吐量,您正试图最大化系统 ,而不是单个组件; 一些本地的降低可能会提高整个系统的吞吐量(例如, 为了提高数据库的性能,改变发生在networking层上的延迟是一个净增益)。

根据具体情况:

  1. 将数据库移到一台单独的机器上 。 在此之后,通过运行top和你最喜欢的MySQL监控工具,在你的负载testing期间对DB进行剖析。 你需要能够configuration文件。 将数据库移到一台单独的机器上会引起一些额外的延迟(几毫秒)每个请求,所以希望稍微增加Web层进程的数量,以保持相同的吞吐量。
  2. 确保uswgi请求队列足够大,可以使用--listen参数处理突发stream量。 这应该是系统可以处理的每秒最大稳定状态请求的几倍。
  3. 在Web /应用程序层: 平衡进程数与CPU内核数量以及进程中的固有延迟。 太多的进程会降低性能,太less意味着永远不能充分利用系统资源。 没有固定的平衡点,因为每个应用程序和使用模式是不同的,所以基准和调整。 作为指导,使用进程的延迟,如果每个任务有:

    • 0%的延迟,那么你需要每个核心1个进程
    • 50%的延迟(即CPU时间是实际时间的一半),则每个核心需要2个进程
    • 67%的延迟,那么你需要每个核心3个进程
  4. 在testing过程中检查top ,以确保您的CPU利用率高于90%(每个核心), 并且您的平均负载平均值略高于1.0。 如果负载平均值较高,则缩小进程。 如果一切顺利的话,在某些时候你将无法实现这个目标,DB现在可能成为瓶颈

  5. 在某些时候,你将需要更多的权力在networking层。 您可以select向机器添加更多的CPU(相对容易),因此可以添加更多的进程, 并且/或者您可以添加更多的处理节点(横向可伸缩性)。 后者可以使用uukasz Mierzwa 在这里讨论的方法在 uwsgi中实现

请运行基准testing时间超过一分钟(至less5-10次),您真的不会从这么短的testing中获得很多信息。 并使用uWSGI的碳插件将统计数据推送到碳/石墨服务器(您将需要有一个),您将有更多的信息进行debugging。

当您发送500个并发请求到您的应用程序,并且无法处理这种负载时,每个后端的侦听队列将被快速填充(缺省为100个请求),您可能需要增加该请求,但是如果工作人员无法处理请求快速和侦听队列(也称为积压)已满,Linuxnetworking堆栈将放弃请求,您将开始得到错误。

您的第一个基准testing表明,您可以在〜42 ms内处理单个请求,所以单个worker最多可以处理1000ms / 42ms =〜23个请求(如果db和其他部分的应用程序堆栈没有因并发性增加而减慢) 。 所以要处理500个并发请求你需要至less500/23 = 21个工人(但实际上我会说至less有40个),你只有16个,难怪它在这样的负载下破坏了。

编辑:我已经混合率和并发 – 至less21名工人将允许您处理每秒500个请求,而不是500个并发请求。 如果你真的想要处理500个并发请求,你只需要500名工作人员。 除非您将以asynchronous模式运行您的应用程序,否则请查看uWSGI文档中的“Gevent”部分。

PS。 uWSGI带有很好的后端自动configuration负载平衡器(阅读“订阅服务器”和“FastRouter”下的文档)。 您可以按照允许您根据需要热插新的后端的方式进行设置,只需在新节点上启动工作人员,他们就会订阅FastRouter并开始获取请求。 这是水平缩放的最佳方式。 在AWS上有后端您可以自动执行此操作,以便在需要时快速启动新的后端。

添加更多的工人并减lessr / s意味着你的请求是“纯CPU”,没有IO等待另一个工人可以用来服务另一个请求。

如果你想扩展你将需要使用更多(或更快)CPU的另一台服务器。

然而,这是一个综合testing,您获得的r / s数量是您正在testing的确切请求的上限,一旦生产,就会有更多variables影响性能。