python - 不良的Django/uwsgi性能

标签 python django uwsgi django-rest-framework

我正在使用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

&nginx配置:
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

以下是有关我的应用程序的更多信息:
基本上,它是模型的集合,这些模型反射(reflect)了包含所有内容的MySQL表。在前端,我有django-rest-framework,它将json内容提供给客户端。

我已经安装了django-profiling和django调试工具栏以查看发生了什么。在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 e.g. '_.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

问题是,“最高”显示平均负载快速上升,而我在本地服务器和网络中的远程计算机上均运行的apache基准表明,我每秒没有处理很多请求。
问题是什么?这是我在分析代码时可以达到的范围,因此如果有人可以指出我在这里所做的工作,将不胜感激。

编辑(23/02/2013):根据安德鲁·阿尔考克的答案添加更多详细信息:
需要我注意/回答的要点是
(3)(3)我在MySQL上执行了“显示全局变量”,发现MySQL配置的max_connections设置为151,足以为我正在为uwsgi启动的工作人员提供服务。

(3)(4)(2)我正在分析的单个请求是最重的请求。它根据django-debug-toolbar执行4个查询。发生的是所有查询都在以下位置运行:
分别为3.71、2.83、0.88、4.84 ms。

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

(5)在16个 worker 上,并发率100,1000个请求的平均负载达到〜12
我对不同数量的 worker (并发级别为100)进行了测试:
  • 1位 worker ,平均负载〜1.85,19请求/秒,每个请求的时间:5229.520,0非2xx
  • 2个工作程序,平均负载〜1.5,19请求/秒,每个请求的时间:516.520,0非2xx
  • 4位工作人员,平均负载〜3,16请求/秒,每个请求的时间:5929.921,0非2xx
  • 8位工作人员,平均负载为5〜18请求/秒,每个请求的时间:5301.45,0非2xx
  • 16位 worker ,平均负载〜19,15请求/秒,每个请求的时间:6384.720,0非2xx

  • 如您所见,我们拥有的工作人员越多,系统上的负载就越大。我在uwsgi的守护程序日志中看到,当我增加工作人员数量时,响应时间(以毫秒为单位)会增加。

    在16个工作线程上,运行500个并发级别请求uwsgi将启动loggin错误:
     writev(): Broken pipe [proto/uwsgi.c line 124] 
    

    负载也上升到〜10。而且测试不会花费很多时间,因为非2xx响应在1000中占923,这就是为什么这里的响应非常快的原因,因为它几乎是空的。这也是对摘要中第4点的答复。

    假设我在这里面临的是基于I/O和网络的OS延迟,建议采取什么行动来扩大规模?新硬件?更大的服务器?

    谢谢

    最佳答案

    编辑1 看到您拥有1个虚拟核心的评论,并在所有相关的点上添加了评论

    编辑2 来自Maverick的更多信息,因此,我消除了排除在外的想法,并开发了已确认的问题。

    编辑3 填写了有关uwsgi请求队列和扩展选项的更多详细信息。改进语法。

    编辑4 来自Maverick的更新和较小的改进

    评论太少,因此有一些想法:

  • 平均负载基本上是指正在运行或正在等待CPU注意的进程数。对于具有1个CPU内核的完美负载系统,平均负载应为1.0;否则,平均负载应为1.0。对于4核系统,应为4.0。在运行Web测试的那一刻,线程数量激增,并且有许多进程在等待CPU。除非平均负载超过CPU核心数量大幅度,否则就不用担心
  • 第一个“每个请求的时间”值为4s与请求队列的长度相关-1000个请求几乎同时在Django上转储,平均花费4s进行服务,其中约3.4s在队列中等待。这是由于请求数量(100)与处理器数量(16)之间的严重不匹配导致在任何时刻有84个请求正在等待处理器。
  • 以100的并发速度运行,测试耗时41秒,每秒24个请求。您有16个进程(线程),因此每个请求的处理时间约为700ms。根据您的交易类型,每个请求的时间很长。这可能是因为:
  • 在Django中,每个请求的CPU开销都很高(考虑到调试工具栏上的CPU值较低,这不太可能发生)
  • 操作系统的任务切换很多(尤其是平均负载高于4-8的情况下),而延迟纯粹是因为进程太多。
  • 没有足够的数据库连接来为16个进程提供服务,因此进程正在等待一个可用的数据库连接。每个进程至少有一个可用连接吗?
  • 数据库周围存在相当大的延迟,或者:
  • 数十个小请求每个花费10毫秒,其中大部分是网络开销。如果是这样,您是否可以引入缓存或将SQL调用减少到较小数目。或
  • 一个或几个请求占用100毫秒。要检查这一点,请在数据库上运行分析。如果是这样,则需要优化该请求。
  • 尽管总CPU数量很低,但系统和用户CPU成本之间的差距在系统中异常高。这意味着Django中的大多数工作都是与内核相关的,例如网络或磁盘。在这种情况下,可能是网络成本(例如,接收和发送HTTP请求以及向DB接收和发送请求)。有时由于分页会很高。如果没有进行分页,那么您可能根本不必担心这一点。
  • 您已将进程设置为16,但平均负载较高(未声明的数量)。理想情况下,您应该始终至少有一个进程在等待CPU(以便CPU不会空转)。这里的进程似乎不受CPU的限制,但是具有显着的延迟,因此您需要比内核更多的进程。还有多少?尝试使用不同数量的处理器(1、2、4、8、12、16、24等)运行uwsgi,直到获得最佳吞吐量。如果您更改平均流程的延迟,则需要再次进行调整。
  • 500并发级别肯定是一个问题,但这是客户端还是服务器?该报告说50个(共100个)的内容长度不正确,这意味着服务器出现问题。非2xx也似乎指向那里。是否可以捕获非2xx响应以进行调试-堆栈跟踪或特定错误消息将非常有用(EDIT),并且由uwsgi请求队列(默认值为100)运行引起。

    因此,总而言之:

  • Django似乎不错
  • 负载测试(100或500)的并发与进程(16)之间的不匹配:您将太多的并发请求插入系统,以处理的进程数。一旦超过了进程数,所有将要发生的事情就是您将延长Web服务器
  • 中的HTTP请求队列。
  • 延迟很长,所以要么
  • 进程(16)与CPU内核(1)之间不匹配:如果平均负载大于3,则可能是太多进程。使用更少的进程再试一次
  • 平均负载> 2->尝试8个进程
  • 平均负载> 4->尝试4个进程
  • 平均负载> 8->尝试2个进程
  • 如果平均负载<3,则可能在数据库中,因此请对数据库进行概要分析,以查看是否存在小请求(加重导致延迟)的负载或一两个SQL语句是问题
  • 如果不捕获失败的响应,那么关于500并发
  • 的失败我无话可说

    发展思路

    单核计算机上的平均负载> 10实在令人讨厌,并且(如您所观察到的)导致许多任务切换和一般缓慢的行为。我个人不记得看到一台平均负载为19(一台机器有16个进程)的机器-恭喜,它的性能如此之高;)

    数据库性能非常好,所以我现在就给出一个清晰的结论。

    分页:要回答有关如何查看分页的问题-您可以通过多种方式检测操作系统分页。例如,在顶部,页眉具有页入和页出(请参阅最后一行):

    进程: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数据,24Mlinkedit。
    内存区域:总计31726个,居民2541万,私有(private)1.2亿,共享8.17亿。 PhysMem:有线1420M, Activity 3548M,未 Activity 1703M,已使用6671M,空闲1514M。
    虚拟机:392G vsize,1286M框架vsize,1534241(0)pageins,0(0)pageout。网络:数据包:789684/288M输入,912863/482M输出。磁盘:读取739807/15G,写入996745/24G。

    进程数:在当前配置中,进程数太高了。 将进程数缩放回2 。我们可能稍后再调高该值,具体取决于将更多负载转移到该服务器上。

    Apache基准测试的位置:一个进程的平均负载为1.85,这向我暗示您正在与uwsgi一起在同一台计算机上运行负载生成器-是正确的吗?

    如果是这样,您确实需要从另一台计算机上运行它,否则测试运行不能代表实际负载-您正在从Web进程中获取内存和CPU,以供负载生成器使用。此外,负载生成器的100或500个线程通常会以一种现实生活中不会发生的方式使服务器承受压力。确实,这可能是整个测试失败的原因。

    数据库的位置:一个进程的平均负载还表明您正在与Web进程在同一台计算机上运行数据库-这是正确的吗?

    如果我对数据库是正确的,那么开始扩展的第一个也是最好的方法是将数据库移至另一台计算机。我们这样做有两个原因:
  • 数据库服务器需要与处理节点不同的硬件配置文件:
  • 磁盘:数据库需要大量快速,冗余,备份的磁盘,而处理节点仅需要基本磁盘
  • CPU:一个处理节点需要您可以负担得起的最快CPU,而一台DB计算机通常可以做到(通常其性能取决于磁盘和RAM)
  • RAM:一个数据库机器通常需要尽可能多的RAM(最快的DB将其所有数据都存储在RAM中),而许多处理节点则需要更少的RAM(每个进程大约需要20MB-很小的
  • 扩展:原子 DB可以通过使怪物机器具有许多CPU来实现最佳扩展,而Web层(不具有状态)可以通过插入许多相同的小Boxen来进行扩展。
  • CPU亲和力:最好让CPU的平均负载为1.0,并使进程对单个内核具有亲和力。这样做可以最大程度地利用CPU缓存,并最大程度地 reduce task 切换开销。通过分离数据库节点和处理节点,可以在硬件中强制执行此关联。

  • 500并发,但有异常(exception)上图中的请求队列最多为100-如果在队列已满时uwsgi接收到请求,则该请求将被拒绝,并出现5xx错误。我认为这是在您的500个并发负载测试中发生的-基本上队列中充满了前100个左右线程,然后其他400个线程发出了其余900个请求并立即收到5xx错误。

    要每秒处理500个请求,您需要确保两件事:
  • 将请求队列大小配置为处理突发:使用--listen参数uwsgi
  • 如果正常情况下500,则系统每秒可以处理500个请求以上的吞吐量,如果峰值500,则系统可以处理每秒以下的吞吐量。请参阅下面的缩放注释。

  • 我认为uwsgi的队列设置为较小的数目,以便更好地处理DDoS攻击。如果负载很大,大多数请求将立即失败,几乎不进行任何处理,从而使整个请求箱仍然可以响应管理员。

    扩展系统的一般建议

    您最重要的考虑因素可能是最大化吞吐量。另一个可能的需求是使响应时间最小化,但是我这里不再讨论。为了最大化吞吐量,您正在尝试最大化系统,而不是单个组件。局部减少可能会提高整体系统吞吐量(例如,进行更改以增加Web层的延迟以提高DB的性能是一种净 yield )。

    详细说明:
  • 将数据库移动到另一台机器。之后,通过运行top和您最喜欢的MySQL监视工具,在负载测试期间对数据库进行概要分析。您需要能够进行分析。将数据库移到单独的计算机上会为每个请求引入一些额外的延迟(几毫秒),因此期望稍微增加Web层的进程数以保持相同的吞吐量。
  • 确保uswgi请求队列足够大,以使用--listen参数处理突发流量。这应该是系统可以处理的每秒最大稳态请求的几倍。
  • 在Web/应用程序层上:在进程数与CPU核心数和进程固有延迟之间取得平衡。过多的进程会降低性能,而过少的进程则意味着您将永远不会充分利用系统资源。没有固定的平衡点,因为每个应用程序和使用模式都不同,因此请进行基准测试和调整。作为指导,如果每个任务都具有以下条件,请使用进程的延迟:
  • 0%的延迟,那么每个核心
  • 需要1个进程
  • 50%的延迟(即CPU时间是实际时间的一半),那么每个核心
  • 需要2个进程
  • 67%的延迟,那么每个核心
  • 需要3个进程
  • 在测试过程中检查top,以确保您的CPU利用率(每个内核)均高于90%,平均负载略高于1.0。如果平均负载较高,请缩减流程。如果一切顺利,在某些时候您将无法实现此目标,DB现在可能成为瓶颈
  • 在某些时候,您将需要Web层中的更多功能。您可以选择向计算机添加更多的CPU(相对容易),然后添加更多的进程和/或,可以添加更多的处理节点(水平可伸缩性)。后者可以使用here
  • 讨论的Łukasz Mierzwa方法在uwsgi中实现

    关于python - 不良的Django/uwsgi性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14962289/

    相关文章:

    android - 如何使用kivy从TextInput写入并保存到文本文件中

    python - 将元组列表拆分为同一元组字段的子列表

    python - 计算成对距离矩阵 : is a scalable, Python 中可用的大数据就绪方法?

    Django taggit,使用自定义标记名

    python - Chef 安装 uwsgi libiconv.so.2 没有那个文件或目录

    python - 如何将项目输入 Google AppEngine 数据存储区?

    python - 无法覆盖默认的 django 数据库设置

    css - 使用 STATIC_URL 的 Django CSS 背景图片

    python - 有太多 uwsgi OSError 消息

    python - 将 $ssl_client_s_dn 从 nginx/uwsgi 传递到 flask app