Bad Django/uwsgi表现

Mav*_*ick 49 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
Run Code Online (Sandbox Code Playgroud)

&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;
}
Run Code Online (Sandbox Code Playgroud)

这就是问题所在.在服务器上执行"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
Run Code Online (Sandbox Code Playgroud)

在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
Run Code Online (Sandbox Code Playgroud)

如您所见......服务器上的所有请求都因超时错误或"客户端过早断开连接"而失败或:

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

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

我已经安装了django-profiling&django调试工具栏来查看最新情况.在django-profiling这里是我在运行单个请求时得到的:

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()
Run Code Online (Sandbox Code Playgroud)

..等等

但是,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
Run Code Online (Sandbox Code Playgroud)

问题是"top"显示负载平均值快速上升,而我在本地服务器和网络中的远程计算机上运行的apache基准测试表明我没有提供很多请求/秒.问题是什么?这是我在分析代码时可以达到的,所以如果有人可以指出我在这里做什么,我将不胜感激.

编辑(23/02/2013):根据Andrew Alcock的回答添加更多细节: 需要我注意/回答的要点是(3)(3)我在MySQL上执行了"show global variables"并发现MySQL配置为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我对不同数量的工作者运行测试(并发级别为100):

  1. 1名工人,平均负载~1.85,19 reqs /秒,每次请求的时间:5229.520,0非2xx
  2. 2工人,平均负载~1.5,19 reqs /秒,每个请求的时间:516.520,0非2xx
  3. 4工人,平均负载~3,16 reqs /秒,每个请求的时间: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] 
Run Code Online (Sandbox Code Playgroud)

负载也高达~10.并且测试不需要花费太多时间,因为非2xx响应是1000中的923,这就是为什么这里的响应非常快,因为它几乎是空的.这也是对摘要中第4点的回复.

假设我在这里遇到的是基于I/O和网络的操作系统延迟,建议采取哪些措施进行扩展?新硬件?大服务器?

谢谢

And*_*ock 136

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

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

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

编辑4 Maverick的更新和小改进

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

  1. 平均负载基本上是运行多少进程或等待CPU注意.对于具有1个CPU内核的完美负载系统,负载平均值应为1.0; 对于4核系统,它应该是4.0.您运行Web测试的那一刻,线程火箭和您有很多进程在等待CPU.除非负载平均值超过CPU核心数量,否则不是问题
  2. 第一个'每个请求的时间'值4s与请求队列的长度相关 - 几乎瞬间转储到Django上的1000个请求,平均需要4个服务,其中大约3.4个在队列中等待.这是由于请求数量(100)与处理器数量(16)之间非常严重的不匹配导致84个请求在任何一个时刻等待处理器.
  3. 以并发100运行,测试需要41秒,每秒24个请求.您有16个进程(线程),因此每个请求处理大约700毫秒.根据您的交易类型,每次请求需要长时间.这可能是因为:

    1. Django中每个请求的CPU成本都很高(鉴于调试工具栏中的CPU值较低,这是非常不可能的)
    2. 操作系统是任务切换很多(特别是如果负载平均值高于4-8),延迟完全取决于拥有太多进程.
    3. 没有足够的数据库连接为16个进程提供服务,因此进程正在等待有一个进程可用.每个进程至少有一个连接可用吗?
    4. 数据库周围存在相当大的延迟:

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

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

所以,总结一下:

在此输入图像描述

  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,它可能在DB中,因此对数据库进行概要分析以查看是否存在大量小请求(附加地导致延迟)或一个或两个SQL语句是问题

  4. 如果没有捕获失败的响应,我就不能说500并发失败了

发展思路

单个机器上的平均负载> 10 非常讨厌,并且(正如您所观察到的)导致大量任务切换和一般缓慢行为.我个人不记得看到平均负载为19的机器(你有16个进程) - 祝贺它如此之高;)

数据库性能很好,所以我现在就给它一个清晰的.

分页:要回答有关如何查看分页的问题 - 您可以通过多种方式检测OS分页.例如,在顶部,标题有页面输入和输出(参见最后一行):

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 Benchmark的位置:一个进程的负载平均值为1.85,这表明您在与uwsgi相同的计算机上运行负载生成器 - 这是正确的吗?

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

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

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

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

    1. 磁盘:DB需要大量快速,冗余,备份的磁盘,而处理节点只需要一个基本磁盘
    2. CPU:处理节点需要您能够承受的最快的CPU,而数据库机器通常可以不用(通常其性能在磁盘和RAM上进行门控)
    3. RAM:数据库机器通常需要尽可能多的RAM(并且最快的DB 所有数据存储在RAM中),而许多处理节点需要更少(每个进程需要大约20MB - 非常小)
    4. 缩放:原子数据库通过拥有许多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攻击; 如果置于巨大的负载下,大多数请求会立即失败,几乎没有任何处理允许整个盒子仍然响应管理员.

扩展系统的一般建议

您最重要的考虑因素可能是最大化吞吐量.另一个可能需要最小化响应时间,但我不会在这里讨论.在最大化吞吐量时,您正在尝试最大化系统,而不是单个组件; 一些本地减少可能会提高整体系统吞吐量(例如,为了提高数据库性能而进行的更改会在Web层增加延迟,这是一个净收益).

根据具体情况:

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

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

  5. 在某些时候,您将需要更多的功能在Web层.您可以选择向机器添加更多CPU(相对简单),从而添加更多进程,和/或您可以添加更多处理节点(水平可伸缩性).后者可以在uwsgi使用所讨论的方法来实现这里通过卢卡斯Mierzwa

  • 对难以回答的问题进行非常详细和彻底的回答. (12认同)

Łuk*_*zwa 6

请运行基准测试超过一分钟(至少5-10),你真的不会从这么短的测试中获得太多信息.并使用uWSGI的碳插件将统计数据推送到碳/石墨服务器(您将需要一个),您将获得更多有关调试的信息.

当您向应用程序发送500个并发请求并且无法处理此类负载时,每个后端的侦听队列将很快填充(默认情况下为100个请求),您可能希望增加该请求,但如果工作人员无法处理请求快速和侦听队列(也称为积压)已满,linux网络堆栈将丢弃请求,您将开始收到错误.

您的第一个基准测试表明您可以在~42毫秒内处理单个请求,因此单个工作程序最多可以处理1000毫秒/ 42毫秒=每秒23个请求(如果并发数据库中的数据库和应用程序堆栈的其他部分没有减速) .所以要处理500个并发请求你需要至少500/23 = 21个工人(但实际上我会说至少40个),你只有16个,难怪它在这样的负载下会中断.

编辑:我混合率与并发 - 至少21名工作人员将允许您每秒处理500个请求,而不是500个并发请求.如果你真的想要处理500个并发请求,那么你只需要500个工作者.除非您将以异步模式运行您的应用,否则请查看uWSGI文档中的"Gevent"部分.

PS.uWSGI带有强大的负载均衡器和后端自动配置(阅读"Subscription Server"和"FastRouter"下的文档).您可以按照允许您根据需要热插拔新后端的方式设置它,您只需在新节点上启动工作人员,他们将订阅FastRouter并开始获取请求.这是水平缩放的最佳方式.使用AWS上的后端您可以自动执行此操作,以便在需要时快速启动新后端.

  • 只有当你拥有足够(或更好 - 无限制)的cpu和I/O能力时,你才可以期待这种简单的数学运算.如果这是单个ec2实例,那么在你达到理论请求率之前很久就会使cpu饱和.您可以在AWS中为每个实例使用漂亮的图表,使用它们;)另外 - 缩放后端是一回事,如果您有很多,那么DB将开始成为瓶颈.你不能只扩展堆栈的一部分,整个事情和最慢的组件一样慢. (2认同)