Django很慢

7 投票
2 回答
9782 浏览
提问于 2025-04-16 06:40

一些性能分析显示,模板渲染是问题的根源。(我在一个只有缓存查询的页面上进行测试。)

不过,这个模板其实很简单。

最复杂的部分是一个嵌套循环,最多运行10次,但如果一切正常,这个嵌套循环就不会运行,因为它是缓存的。(就像我测试时的情况)

也就是说

{% for p in posts %}
 --{{p.by.username}}
 --{{p.text}}
 {% cache 600 p p.timestamp %}
    {% for img in p.images.all %}
      --{{img.path}}
    {% endfor %}
 {% endcache %}
{% endfor %}

在开发服务器上,这个页面的请求速度大约是每秒80个请求。

(我发现这个数字在生产环境中可以乘以3)

作为对比,我在一个只包含短静态字符串的简单模板上可以达到每秒1000个请求。

这是一个已知的问题吗?我该如何去修复或避免它呢?

2 个回答

3

(显然我还不够“有 karma”来发评论,要不然我就把这个当评论发了,而不是作为回答)

你能详细解释一下“仅缓存查询”是什么意思吗?

除此之外,我觉得你的问题可能是因为在模板渲染时,你频繁地访问数据库。

{% for p in posts %}
 --{{p.by.username}} {# 1 #}
 --{{p.text}}
 {% cache 600 p p.timestamp %}
    {% for img in p.images.all %} {# 2 #}
      --{{img.path}}
    {% endfor %}
 {% endcache %}
{% endfor %}

你给模板提供了“帖子”;这就是一次查询,你说这个查询有100个结果。

那么在遍历这些帖子时,你在{# 1 #}又要访问数据库来获取 p.by,我猜这是指向一个用户的外键。

除此之外,在缓存无效的情况下(第一次运行),你在{# 2 #}又要访问数据库来获取这个帖子的图片列表。

所以对于100个项目来说,第一次请求时你要访问数据库201次,而如果缓存已经填充好,获取图片时就只需要访问101次。

试着在你的帖子查询中使用 select_related,这样可以在一次查询中拉取这些额外的结果。如果可能的话,可以试试像 posts = Post.objects.select_related('by', 'images').filter(...) 这样的写法,虽然我知道 select_related 在处理反向外键和多对多字段时有一些限制(根据你的模型结构,可能对图片不适用)。

5

在开发模式下,Django会做很多事情来帮助开发,比如:代码自动重载;每次请求时都渲染模板(如果使用了模板)等等。

而在生产模式下,建议在Django之前部署一个WSGI服务器。这样的WSGI服务器可以是gunicornuWSGI等。

一个典型的生产环境的服务器布局可能是:nginx -> gunicorn -> django。

下面是一个简单的对比(使用Django官方教程中的代码,展示一个非常简单的“Hello World! @ time”模板):

{% block content %}
    <p> Hello World! @ {{ now }}</p>
{% endblock %}

开发模式

直接用Django运行

python manage.py runserver

用apache-ab进行测试

ab -n1000 -c10 http://127.0.0.1:8000/polls/helloworld

Server Software:        WSGIServer/0.2 # django
Server Hostname:        127.0.0.1
Server Port:            8000

Document Path:          /polls/helloworld
Document Length:        59 bytes

Concurrency Level:      10
Time taken for tests:   3.438 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      242000 bytes
HTML transferred:       59000 bytes
Requests per second:    290.87 [#/sec] (mean)
Time per request:       34.380 [ms] (mean)
Time per request:       3.438 [ms] (mean, across all concurrent requests)
Transfer rate:          68.74 [Kbytes/sec] received

生产模式

用gunicorn运行

../bin/gunicorn -w4 mysite.wsgi # 启动4个工作进程

用apache-ab进行测试

ab -n1000 -c10 http://127.0.0.1:8000/polls/helloworld

Server Software:        gunicorn/19.7.1  # gunicorn
Server Hostname:        127.0.0.1
Server Port:            8000

Document Path:          /polls/helloworld
Document Length:        59 bytes

Concurrency Level:      10
Time taken for tests:   0.618 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      248000 bytes
HTML transferred:       59000 bytes
Requests per second:    1619.10 [#/sec] (mean)
Time per request:       6.176 [ms] (mean)
Time per request:       0.618 [ms] (mean, across all concurrent requests)
Transfer rate:          392.13 [Kbytes/sec] received

撰写回答