暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Django性能优化大全和使用通用类视图开发任务管理CRUD小应用

牛建邦 2021-12-28
642

性能优化指标

在对一个Web项目进行性能优化时,我们通常需要考虑如下几个指标:

响应时间

最大并发连接数

代码的行数

函数调用次数

内存占用情况

CPU占比

响应时间(服务器从接收用户请求,处理该请求并返回结果所需的总的时间)通常是最重要的指标。

在开发环境中,我们一般建议使用django-debug-toolbar和django-silk来进行性能监测分析。它们提供了每次用户请求的响应时间,并告诉你程序执行过程哪个环节(比如SQL查询)最消耗时间。

对于中大型网站或Web APP而言,最影响网站性能的就是数据库查询部分了。一是反复从数据库读写数据很消耗时间和计算资源,二是当返回的查询数据集queryset非常大时还会占据很多内存。

 

数据库查询优化

利用Queryset的惰性和缓存,避免重复查询

1: 利用了缓存特性 - Good

article_list = Article.objects.filter(title__contains="django")

for article in article_list:

    print(article.title)

 

2: Bad

for article in Article.objects.filter(title__contains="django"):

    print(article.title)

只希望了解查询的结果是否存在或查询结果的数量,这时可以使用exists()和count()方法。这样就不会浪费资源查询一个用不到的数据集,还可以节省内存。

3: Good

article_list = Article.objects.filter(title__contains="django")

if article_list.exists():

    print("Records found.")

else:

    print("No records")

    

4: Good

count = Article.objects.filter(title__contains="django").count()

一次查询所有需要的关联模型数据

假设我们有一个文章(Article)模型,其与类别(Category)是单对多的关系(ForeignKey), 与标签(Tag)是多对多的关系(ManyToMany)。我们需要编写一个article_list的函数视图,以列表形式显示文章清单及每篇文章的类别和标签,

#模板文件

<ul>

{% for article in articles %}

    <li>{{ article.title }} </li>

    <li>{{ article.category.name }}</li>

    <li>

        {% for tag in article.tags.all %}

           {{ tag.name }},

        {% endfor %}

    </li>

{% endfor %}

</ul>

在模板里每进行一次for循环获取关联对象category和tag的信息,Django就要单独进行一次数据库查询,造成了极大资源浪费。我们完全可以使用select_related方法和prefetch_related方法一次性从数据库获取单对多和多对多关联模型数据,这样在模板中遍历时Django也不会执行数据库查询了。

仅获取文章数据 - Bad

def article_list(request):

    articles = Article.objects.all()

    return render(request, 'blog/article_list.html',{'articles': articles, })

 

一次性提取关联模型数据 - Good

def article_list(request):

    articles = Article.objects.all().select_related('category').prefecth_related('tags')

    return render(request, 'blog/article_list.html', {'articles': articles, })

仅查询需要用到的数据

可以使用values和value_list方法按需提取数据,比如只获取文章的id和title,节省查询时间和内存

例子5: Bad

article_list = Article.objects.all()

if article_list:

    print(article.title)

 

例子6: Good - 字典格式数据

article_list = Article.objects.values('id''title')

if article_list:

    print(article.title)

 

例子7: Good - 元组格式数据

article_list = Article.objects.values_list('id''title')

if article_list:

    print(article.title)

    

例子8: Good - 列表格式数据

article_list = Article.objects.values_list('id''title'flat=True)

if article_list:

    print(article.title)

Django项目还可以使用defer和only这两个查询方法来实现这一点。第一个用于指定哪些字段不要加载,第二个用于指定只加载哪些字段。

使用分页,限制最大页数

使用分页仅展示用户所需要的数据,而不是一下子查询所有数据。同时使用分页时也最好控制最大页数。

LIMIT = 100 * 200

 

data = Articles.objects.all()[:(LIMIT + 1)]

if len(data) > LIMIT:

    raise ExceededLimit(LIMIT)

 

return data

数据库设置优化

如果你使用单个数据库,你可以采用如下手段进行优化:

建立模型时能用CharField确定长度的字段尽量不用不用TextField,可节省存储空间;

可以给搜索频率高的字段属性,在定义模型时使用索引(db_index=True);

持久化数据库连接。

没有持久化连接,Django每个请求都会与数据库创建一个连接,直到请求结束,关闭连接。如果数据库不在本地,每次建立和关闭连接也需要花费一些时间。设置持久化连接时间,仅需要添加CONN_MAX_AGE参数到你的数据库设置中。

DATABASES = {

    "default": {

        "ENGINE""django.db.backends.postgresql_psycopg2",

        "NAME""postgres",

        "CONN_MAX_AGE"60# 60

    }

}

CONN_MAX_AGE也不宜设置过大,因为每个数据库并发连接数有上限的(比如mysql默认的最大并发连接数是100个)。如果CONN_MAX_AGE设置过大,会导致mysql 数据库连接数飙升很快达到上限。当并发请求数量很高时,CONN_MAX_AGE应该设低点,比如30s, 10s或5s。当并发请求数不高时,这个值可以设得长一点,比如60s或5分钟。

当你的用户非常多、数据量非常大时,你可以考虑读写分离、主从复制、分表分库的多数据库服务器架构。

缓存

对于中大型网站而言,使用缓存减少对数据库的访问次数是提升网站性能的关键之一。

视图缓存

from django.views.decorators.cache import cache_page

 

@cache_page(60 * 15)

def my_view(request):

    ...

使用@cached_property装饰器缓存计算属性

对于不经常变动的计算属性,可以使用@cached_property装饰器缓存结果。

缓存临时性数据比如sessions

Django的sessions默认是存在数据库中的,这样的话每一个请求Django都要使用sql查询会话数据,然后获得用户对象的信息。对于临时性的数据比如sessions和messages,最好将它们放到缓存里,也可以减少SQL查询次数。

SESSION_ENGINE = 'django.contrib.sessions.backends.cache'

模版缓存

默认情况下Django每处理一个请求都会使用模版加载器都会去文件系统搜索模板,然后渲染这些模版。你可以通过使用cached.Loader开启模板缓存加载。这时Django只会查找并且解析你的模版一次,可以大大提升模板渲染效率。

TEMPLATES = [{

    'BACKEND''django.template.backends.django.DjangoTemplates',

    'DIRS': [BASE_DIR / 'templates'],

    'OPTIONS': {

        'loaders': [

            ('django.template.loaders.cached.Loader', [

                'django.template.loaders.filesystem.Loader',

                'django.template.loaders.app_directories.Loader',

                'path.to.custom.Loader',

            ]),

        ],

    },

}]

不建议在开发环境中(Debug=True)时开启缓存加载,因为修改模板后你不能及时看到修改后的效果。

另外模板文件中建议使用with标签缓存视图传来的数据,便于下一次时使用。对于公用的html片段,也建议使用缓存。

{% load cache %}

{% cache 500 sidebar request.user.username %}

    .. sidebar for logged in user ..

{% endcache %}

静态文件

压缩 HTML、CSS 和 JavaScript等静态文件可以节省带宽和传输时间。Django 自带的压缩工具有GzipMiddleware 中间件和 spaceless 模板 Tag。使用Python压缩静态文件会影响性能,一个更好的方法是通过 Apache、Nginx 等服务器来对输出内容进行压缩。例如Nginx服务器支持gzip压缩,同时可以通过expires选项设置静态文件的缓存时间。

#更多关于Nginx的配置:

https://pythondjango.cn/python/tools/5-nginx-configuration/



使用通用类视图开发任务管理CRUD小应用

第一步:创建tasks应用,把它加入INSTALLED_APPS

首先使用 python manage.py startapp tasks 创建一个名为tasks的app,并把它计入到settings.py的INSTALLED_APPS中去。

INSTALLED_APPS = [

    'django.contrib.admin',

    'django.contrib.auth',

    'django.contrib.contenttypes',

    'django.contrib.sessions',

    'django.contrib.messages',

    'django.contrib.staticfiles',

    'tasks',

    'atricledb',

]

然后把app下的urls路径加入到项目文件夹的urls.py里去。

from django.contrib import admin

from django.urls import path, include

 

urlpatterns = [

    path('admin/', admin.site.urls),

    path('tasks/', include('tasks.urls'))

]

 

第二步:创建Task模型及其关联表单

我们的Task模型非常简单,仅包含name和status两个字段. 我们还使用ModelForm类创建了TaskForm,我们在创建任务或更新任务时需要用到这个表单

# tasks/models.py

from django.db import models

 

class Status(models.TextChoices):

        UNSTARTED = 'u'"Not started yet"

        ONGOING = 'o'"Ongoing"

        FINISHED = 'f'"Finished"

 

class Task(models.Model):

    name = models.CharField(verbose_name='Task name'max_length=65unique=True)

    status = models.CharField(verbose_name='Task status'max_length=1choices=Status.choices)

 

    def __str__(self):

        return self.name

# tasks/forms.py

from .models import Task

from django import forms

 

class TaskForm(forms.ModelForm):

 

    class Meta:

        model = Task

        fields = "__all__"

 

第三步:编写路由URLConfs及其相关视图函数

需要创建5个urls, 对应5个基于类的视图。这是因为对于Retrieve操作,我们需要编写两个类视图,一个用于获取任务列表,一个用于获取任务详情。对于task_detail, task_update和task_delete这个三个类视图,我们还需要通过urls传递任务id或pk参数,否则它们不知道对哪个对象进行操作。

注意:在编写路由urls.py时,基于类的视图需要使用as_view()方法将其装饰成函数。

# tasks/urls.py

from django.urls import path, re_path

from . import views

 

# namespace

app_name = 'tasks'

 

urlpatterns = [

    # Create a task

    path('create/', views.TaskCreateView.as_view(), name='task_create'),

    # Retrieve task list

    path('', views.TaskListView.as_view(), name='task_list'),

    # Retrieve single task object

    re_path(r'^(?P<pk>\d+)/$', views.TaskDetailView.as_view(), name='task_detail'),

    # Update a task

    re_path(r'^(?P<pk>\d+)/update/$', views.TaskUpdateView.as_view(), name='task_update'),

    # Delete a task

    re_path(r'^(?P<pk>\d+)/delete/$', views.TaskDeleteView.as_view(), name='task_delete')

]

5个基于类的视图是本应用的核心代码,它们分别继承了Django通用类视图的ListView, DetailView,CreateView, UpdateView和DeleteView。

# tasks/views.py

from django.urls import reverse, reverse_lazy

from .models import Task

from .forms import TaskForm

from django.views.generic import ListView, DetailView, \

    CreateView, UpdateView, DeleteView

 

class TaskListView(ListView):

    model = Task

    context_object_name = 'tasks'

 

class TaskDetailView(DetailView):

    model = Task

 

class TaskCreateView(CreateView):

    model = Task

    form_class = TaskForm

    success_url = reverse_lazy('tasks:task_list')

 

class TaskUpdateView(UpdateView):

    model = Task

    form_class = TaskForm

    success_url = reverse_lazy('tasks:task_list')

 

class TaskDeleteView(DeleteView):

    model = Task

    success_url = reverse_lazy('tasks:task_list')

 

第四步:编写模板

虽然有5个urls,但只需要创建4个模板:task_list.html, task_detail.html、task_form.html 和task_confirm_delete.html, 其中task_form.html由task_create 和task_update 视图类共享。我们在模板中对实例对象进行判断,如果对象已存在则模板对于更新任务,否则是创建任务。

# tasks/templates/tasks/task_list.html

<!DOCTYPE html>

<html lang="en">

    <head>

        <meta charset="utf-8">

        <title>Task List</title>

    </head>

 

    <body>

        <h3>Task List</h3>

        {% for task in tasks %}

            <p>

                {{ forloop.counter }}.{{ task.name }}-{{ task.get_status_display }}

                (<a href="{% url 'tasks:task_update' task.id %}">Update</a> |

                <a href="{% url 'tasks:task_delete' task.id %}">Delete</a>)

            </p>

        {% endfor %}

 

        <p> <a href="{% url 'tasks:task_create' %}"> + Add A New Task</a></p>

        

    </body>

</html>

# tasks/templates/tasks/task_detail.html

<!DOCTYPE html>

 <html lang="en">

 <head>

     <meta charset="UTF-8">

     <title>Task Detail</title>

 </head>

 <body>

 <p> Task Name: {{ task.name }} | <a href="{% url 'tasks:task_update' task.id %}">Update</a> |

     <a href="{% url 'tasks:task_delete' task.id %}">Delete</a>

 </p>

 <p> Task Status: {{ task.get_status_display }} </p>

 <p> <a href="{% url 'tasks:task_list' %}">View All Tasks</a> |

     <a href="{% url 'tasks:task_create'%}">New Task</a>

 </p>

 </body>

 </html>

# tasks/templates/tasks/task_form.html

<!DOCTYPE html>

 <html lang="en">

 <head>

     <meta charset="UTF-8">

     <title>{% if object %}Edit Task {% else %} Create New Task {% endif %}</title>

 </head>

 <body>

 <h3>{% if object %}Edit Task {% else %} Create New Task {% endif %}</h3>

     <form action="" method="post" enctype="multipart/form-data">

         {% csrf_token %}

         {{ form.as_p }}

         <p><input type="submit" class="btn btn-success" value="Submit"></p>

     </form>

 </body>

 </html> 

 # tasks/templates/tasks/task_confirm_delete.html

<!DOCTYPE html>

<html lang="en">

<form method="post">{% csrf_token %}

    <p>Are you sure you want to delete "{{ task }}"?</p>

    <input type="submit" value="Confirm" />

</form>

</html>

 

第五步:运行项目,查看效果

运行如下命令,访问http://127.0.0.1:8000/tasks/就应该看到效果了。

 python manage.pymakemigrations

 python manage.pymigrate

 python manage.pyrunserver


文章转载自牛建邦,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论