Django/uwsgi性能差

2024-06-16 08:32:05 发布

您现在位置:Python中文网/ 问答频道 /正文

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

下面是关于我的申请的更多信息: 基本上,它是反映包含所有内容的MySQL表的模型集合。在前端,我有django rest框架,它向客户端提供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调试工具栏显示以下内容:

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

问题是,“top”显示平均负载快速上升,而我在本地服务器和网络中的远程计算机上运行的apache基准测试显示我每秒没有处理很多请求。 怎么了?这是我在分析代码时所能达到的程度,因此如果有人能指出我在这里所做的事情,我将不胜感激。

编辑(2013年2月23日):根据Andrew Alcock的回答添加更多详细信息: 需要我注意/回答的要点是 (3) (3)我在MySQL上执行了“show global variables”,发现MySQL的配置有151个用于max_connections的设置,这已经足够服务于我为uwsgi启动的工人了。

(3)(4)(2)我正在分析的单个请求是最重的请求。它根据django调试工具栏执行4个查询。发生的情况是,所有查询都在中运行: 分别为3.71、2.83、0.88、4.84ms。

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

(5)在16个工作线程上,100个并发率,1000个请求的平均负载达到~12 我在不同数量的工作线程上运行测试(并发级别为100):

  1. 1名工人,平均负载~1.85,19请求/秒,每次请求时间:5229.520,0非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将开始记录错误:

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

负载也增加到~10。测试不需要花费太多时间,因为非2xx响应是1000个响应中的923个,这就是为什么这里的响应非常快,因为它几乎是空的。这也是对你在总结中第4点的答复。

假设我在这里所面临的是基于i/O和网络的操作系统延迟,那么建议采取什么措施来扩大这种延迟?新硬件?更大的服务器?

谢谢


Tags: djangopyrestdbtimemodelslibpackages
3条回答

添加更多的工作线程和获得更少的r/s意味着您的请求“是纯CPU”,并且没有IO等待,其他工作线程可以用来服务另一个请求。

如果您想扩展,您将需要使用另一台cpu更多(或更快)的服务器

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

请运行比一分钟(至少5-10分钟)长得多的基准测试,这样短的测试不会给你带来太多信息。使用uWSGI的carbon插件将统计信息推送到carbon/graphite服务器(您需要一个),您将有更多的调试信息。

当您向应用程序发送500个并发请求,但它无法处理这样的负载时,每个后端上的侦听队列将很快被填满(默认情况下是100个请求),您可能希望增加该值,但如果工作人员无法处理如此快的请求,并且侦听队列(也称为backlog)已满,则linux网络堆栈将丢弃请求并你会开始犯错误。

第一个基准测试表明,您可以在~42ms内处理单个请求,因此单个工作进程最多可以处理1000ms/42ms=~23个请求/秒(如果数据库和应用程序堆栈的其他部分没有随着并发性的增加而减慢)。因此,要处理500个并发请求,您至少需要500/23=21个工人(但实际上至少需要40个),您只有16个,难怪在这样的负载下它会崩溃。

编辑:我已经混合了并发率-至少21个工人将允许您每秒处理500个请求,而不是500个并发请求。如果你真的想处理500个并发请求,你只需要500个工人。除非你将在异步模式下运行你的应用程序,请检查uWSGI文档中的“Gevent”部分。

uWSGI配有强大的负载均衡器和后端自动配置(阅读“订阅服务器”和“快速路由器”下的文档)。您可以设置它,允许您根据需要热插拔新的后端,您只需在新节点上启动工作人员,他们将订阅FastRouter并开始获取请求。这是水平缩放的最佳方式。有了AWS上的后端,您可以将其自动化,以便在需要时快速启动新的后端。

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

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

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

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

评论太小,下面是一些想法:

  1. 平均负载基本上是有多少进程正在运行或等待CPU的关注。对于具有1个CPU核的完全加载系统,平均负载应为1.0;对于4核系统,平均负载应为4.0。运行web测试的那一刻,线程启动,有很多进程在等待CPU。除非平均负载超过CPU核心的数量有很大的差距,否则不需要担心
  2. 4s的第一个“每次请求的时间”值与请求队列的长度相关-1000个请求几乎瞬间转储到Django上,平均需要4s服务,其中约3.4s在队列中等待。这是由于请求数(100)与处理器数(16)之间的严重不匹配导致84个请求在任何时刻等待处理器。
  3. 测试以100的并发运行,以24个请求/秒的速度运行41秒。您有16个进程(线程),因此每个请求的处理时间大约为700ms。根据您的事务类型,每个请求的时间都是的。这可能是因为:

    1. 在Django中,每个请求的CPU开销都很高(考虑到调试工具栏中的低CPU值,这是极不可能的)
    2. 操作系统经常进行任务切换(特别是当负载平均值高于4-8时),而延迟纯粹是因为进程太多。
    3. 没有足够的数据库连接为16个进程提供服务,因此进程正在等待一个可用的数据库连接。每个进程至少有一个可用连接吗?
    4. 数据库周围有相当大的延迟,或者

      1. 每个请求占用数十个小请求,比如10毫秒,其中大部分是网络开销。如果是这样,您是否可以引入缓存或将SQL调用减少到较小的数目。或
      2. 一个或几个请求需要100毫秒。若要检查此问题,请在数据库上运行分析。如果是,您需要优化该请求。
  4. 在系统中,系统和用户CPU成本的比例异常高,尽管总CPU很低。这意味着Django中的大部分工作都与内核相关,比如网络或磁盘。在这种情况下,可能是网络成本(例如接收和发送HTTP请求以及接收和发送请求到DB)。有时这会很高,因为分页。如果没有分页,那么您可能根本不必担心这个问题。

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

因此,总而言之:

enter image description here

  1. Django看起来不错
  2. 负载测试的并发性(100或500)与进程(16)之间的不匹配:您将太多并发请求推送到系统中,无法处理进程数。一旦超过进程数,将发生的所有事情就是延长web服务器中的HTTP请求队列
  3. 有很大的延迟,所以

    1. 进程(16)和CPU核心(1)之间不匹配:如果平均负载为3,则可能是进程太多。用较少的进程重试

      1. 平均加载>;2->;尝试8个进程
      2. 平均加载>;4->;尝试4个进程
      3. 平均负载>;8->;尝试2个进程
    2. 如果负载平均值<;3,则它可能在数据库中,因此分析数据库以查看是否有小请求负载(额外导致延迟)或一个或两个SQL语句是问题所在

  4. 如果不捕获失败的响应,对于500并发时的失败,我就不能说太多了

发展创意

你在一台单芯机器上的平均负载是10,非常讨厌,而且(正如你观察到的)会导致很多任务切换和一般的慢行为。我个人不记得看到一台平均负载为19的机器(你有16个进程的负载)-恭喜你把它弄得这么高;)

数据库的性能很好,所以我现在就把它说清楚。

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

Processes: 170 total, 3 running, 4 stuck, 163 sleeping, 927 threads                                                                                                        15:06:31
Load Avg: 0.90, 1.19, 1.94  CPU usage: 1.37% user, 2.97% sys, 95.65% idle  SharedLibs: 144M resident, 0B data, 24M linkedit.
MemRegions: 31726 total, 2541M resident, 120M private, 817M shared. PhysMem: 1420M wired, 3548M active, 1703M inactive, 6671M used, 1514M free.
VM: 392G vsize, 1286M framework vsize, 1534241(0) pageins, 0(0) pageouts. Networks: packets: 789684/288M in, 912863/482M out. Disks: 739807/15G read, 996745/24G written.

进程数:在当前配置中,进程数太高。将进程数缩放回2。我们稍后可能会将此值调高,具体取决于是否将此服务器的负载进一步转移。

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

如果是这样,您确实需要从另一台机器上运行该程序,否则测试运行就不能代表实际的负载—您将从web进程中获取内存和CPU,以便在负载生成器中使用。此外,负载生成器的100或500个线程通常会以现实生活中不会发生的方式给服务器带来压力。事实上,这可能是整个测试失败的原因。

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

如果我对DB的看法是正确的,那么开始缩放的第一个也是最好的方法是将DB移到另一台机器上。我们这样做有几个原因:

  1. 数据库服务器需要与处理节点不同的硬件配置文件:

    1. 磁盘:数据库需要大量快速、冗余、备份的磁盘,而处理节点只需要一个基本磁盘
    2. CPU:一个处理节点需要你能负担得起的最快的CPU,而DB机器通常可以不用(通常它的性能是在磁盘和RAM上选通的)
    3. RAM:DB机器通常需要尽可能多的RAM(最快的DB在RAM中有all它的数据),而许多处理节点需要更少的RAM(你的每个进程需要大约20MB-非常小
    4. 扩展:AtomicDBs通过拥有多个CPU的大型计算机来扩展,而web层(没有状态)可以通过插入多个标识来扩展我的小盒子。
  2. CPU关联性:CPU的平均负载为1.0,进程与单个核心的关联性更好。这样做可以最大限度地使用CPU缓存,并最大限度地减少任务切换开销。通过分离DB和处理节点,您可以在HW中强制执行此关联。

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

要处理每秒500个请求,您需要确保以下两点:

  1. 请求队列大小配置为处理突发:使用--listen参数来uwsgi
  2. 如果500是正常情况,系统可以以每秒500个以上的请求处理吞吐量,如果500是峰值,系统可以以每秒500个以上的请求处理吞吐量。请参阅下面的缩放注释。

我认为uwsgi将队列设置为一个较小的数字,以便更好地处理DDoS攻击;如果放置在巨大的负载下,大多数请求会立即失败,几乎没有处理,从而使整个框仍能对管理员作出响应。

缩放系统的一般建议

您最重要的考虑可能是最大化吞吐量。另一个可能的需求是最小化响应时间,但我不会在这里讨论这个问题。在最大化吞吐量时,您试图最大化系统,而不是单个组件;一些本地减少可能会提高整个系统的吞吐量(例如,为了提高DB的性能而进行的更改会增加web层的延迟,这是一种净增益)。

具体内容:

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

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

  5. 在某种程度上,你将需要更多的权力在网络层。您可以选择向计算机添加更多的CPU(相对容易),因此添加更多的进程,和/或您可以添加更多的处理节点(水平可伸缩性)。后者可以在uwsgi中使用由Łukasz Mierzwa讨论的方法来实现

相关问题 更多 >