1. 程式人生 > >Django之使用celery和NGINX生成靜態頁面實現效能優化

Django之使用celery和NGINX生成靜態頁面實現效能優化

效能優化原理:

當我們要給client瀏覽器返回一個頁面時,我們需要去資料庫查詢資料並將資料和基本頁面模板渲染形成頁面返回給客戶端,但如果每一個使用者訪問時都去查詢一次首頁的的資料時,當日訪問量很大時那麼無疑會給資料庫查詢帶來很大的效能問題。為了解決這個問題,我們可以給未登入使用者返回一個早就渲染好的靜態首頁(給已登入的使用者返回一個呼叫快取資料和個人資料渲染的頁面),這樣就可以提高網站的效能了。

 

使用celery生成靜態首頁

生成靜態頁面原理:

在一個為靜態首頁準備的基礎模板之上,獲取資料,使用django的loader載入基礎模板,使用render渲染頁面即可生成幾臺頁面。

安裝celery

pip install celery

為redis配置settings檔案

# diango的快取配置
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/9",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}

準備一個首頁靜態模板檔案static_base.html

{# 首頁 註冊 登入 #}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
{% load staticfiles %}
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    {# 網頁標題內容塊 #}
    <title>{% block title %}{% endblock title %}</title>
    <link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}">
    <link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
    {# 網頁頂部引入檔案塊 #}
    {% block topfiles %}{% endblock topfiles %}
</head>
<body>
{# 網頁頂部歡迎資訊塊 #}
{% block header_con %}
    <div class="header_con">
        <div class="header">
            <div class="welcome fl">歡迎來到商城!</div>
            <div class="fr">
                <div class="login_btn fl">
                    <a href="{% url 'user:login' %}">登入</a>
                    <span>|</span>
                    <a href="{% url 'user:register' %}">註冊</a>
                </div>
                <div class="user_link fl">
                    <span>|</span>
                    <a href="{% url 'user:user' %}">使用者中心</a>
                    <span>|</span>
                    <a href="cart.html">我的購物車</a>
                    <span>|</span>
                    <a href="{% url 'user:order' %}">我的訂單</a>
                </div>
            </div>
        </div>        
    </div>
{% endblock header_con %}

{# 網頁頂部搜尋框塊 #}
{% block search_bar %}
    <div class="search_bar clearfix">
        <a href="index.html" class="logo fl"><img src="{% static 'images/logo.png' %}"></a>
        <div class="search_con fl">
            <input type="text" class="input_text fl" name="" placeholder="搜尋商品">
            <input type="button" class="input_btn fr" name="" value="搜尋">
        </div>
        <div class="guest_cart fr">
            <a href="#" class="cart_name fl">我的購物車</a>
            <div class="goods_count fl" id="show_count">{{ cart_count }}</div>
        </div>
    </div>
{% endblock search_bar %}

{# 網站主體內容塊 #}
{% block body %}{% endblock body %}

    <div class="footer">
        <div class="foot_link">
            <a href="#">關於我們</a>
            <span>|</span>
            <a href="#">聯絡我們</a>
            <span>|</span>
            <a href="#">招聘人才</a>
            <span>|</span>
            <a href="#">友情連結</a>        
        </div>
        <p>CopyRight © 2016 北京商城資訊科技有限公司 All Rights Reserved</p>
        <p>電話:010-****888    京ICP備*******8號</p>
    </div>
    {# 網頁底部html元素塊 #}
    {% block bottom %}{% endblock bottom %}
    {# 網頁底部引入檔案塊 #}
    {% block bottomfiles %}{% endblock bottomfiles %}
</body>
</html>
base_index.html

在首頁靜態模板檔案的基礎上繼承生成一個首頁靜態檔案 static_index.html 方便celery獲取資料庫檔案並進行渲染

{% extends 'static_base.html' %}
{% load staticfiles %}
{% block title %}首頁{% endblock title %}
{% block topfiles %}
    <script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/jquery-ui.min.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/slide.js' %}"></script>
{% endblock topfiles %}
{% block body %}
    <div class="navbar_con">
        <div class="navbar">
            <h1 class="fl">全部商品分類</h1>
            <ul class="navlist fl">
                <li><a href="">首頁</a></li>
                <li class="interval">|</li>
                <li><a href="">手機生鮮</a></li>
                <li class="interval">|</li>
                <li><a href="">抽獎</a></li>
            </ul>
        </div>
    </div>

    <div class="center_con clearfix">
        <ul class="subnav fl">
            {% for type in types %}
                <li><a href="#model0{{ forloop.counter }}" class="{{ type.logo }}">{{ type.name }}</a></li>
            {% endfor %}
        </ul>
        <div class="slide fl">
            <ul class="slide_pics">
                {% for banner in goods_banners  %}
                    <li><a href="#"><img src="{{ banner.image.url }}" alt="幻燈片"></a></li>
                {% endfor %}
            </ul>
            <div class="prev"></div>
            <div class="next"></div>
            <ul class="points"></ul>
        </div>
        <div class="adv fl">
            {% for banner in promotion_banners %}
                <a href="{{ banner.url }}"><img src="{{ banner.image.url }}"></a>
            {% endfor %}
        </div>
    </div>

    {% for type in types %}
    <div class="list_model">
        <div class="list_title clearfix">
            <h3 class="fl" id="model0{{ forloop.counter }}">{{ type.name }}</h3>
            <div class="subtitle fl">
                <span>|</span>
                {% for banner in type.title_banners %}
                    <a href="#">{{ banner.sku.name }}</a>
                {% endfor %}
            </div>
            <a href="#" class="goods_more fr" id="fruit_more">檢視更多 ></a>
        </div>

        <div class="goods_con clearfix">
            <div class="goods_banner fl"><img src="{{ type.image.url }}"></div>
            <ul class="goods_list fl">
                {% for banner in type.image_banners %}
                <li>
                    <h4><a href="#">{{ banner.sku.name }}</a></h4>
                    <a href="#"><img src="{{ banner.sku.image.url }}"></a>
                    <div class="prize">¥ {{ banner.sku.price }}</div>
                </li>
                {% endfor %}
            </ul>
        </div>
    </div>
    {% endfor %}
{% endblock body %}
static_index.html

在專案下新建celery_tasks資料夾,在資料夾中新建tasks.py檔案,編寫tasks檔案;

from django.conf import settings
from celery import Celery
from django.template import loader

# 在任務處理者一端加這幾句
import os
# import django
# os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shoppingmall.settings")
# django.setup()

# 這幾個類要放在django環境初始化那四句的下面
from goods.models import GoodsType, IndexGoodsBanner, IndexPromotionBanner, IndexTypeGoodsBanner

# 建立一個Celery類的例項物件
app = Celery('celery_tasks.tasks', broker='redis://127.0.0.1:6379/8')


@app.task
def generate_static_index_html():
    '''產生首頁靜態頁面'''
    # 獲取商品的種類資訊
    types = GoodsType.objects.all()
    # 獲取首頁輪播商品資訊
    goods_banners = IndexGoodsBanner.objects.all().order_by('index')
    # 獲取首頁促銷活動資訊
    promotion_banners = IndexPromotionBanner.objects.all().order_by('index')
    # 獲取首頁分類商品展示資訊
    for type in types:  # GoodsType
        # 獲取type種類首頁分類商品的圖片展示資訊
        image_banners = IndexTypeGoodsBanner.objects.filter(type=type, display_type=1).order_by('index')
        # 獲取type種類首頁分類商品的文字展示資訊
        title_banners = IndexTypeGoodsBanner.objects.filter(type=type, display_type=0).order_by('index')
        # 動態給type增加屬性,分別儲存首頁分類商品的圖片展示資訊和文字展示資訊
        type.image_banners = image_banners
        type.title_banners = title_banners

    # 組織模板上下文
    context = {
                'types': types,
                'goods_banners': goods_banners,
                'promotion_banners': promotion_banners
            }

    # 使用模板
    # 1.載入模板檔案,返回模板物件
    temp = loader.get_template('static_index.html')
    # 2.模板渲染
    static_index_html = temp.render(context)

    # 生成首頁對應靜態檔案
    save_path = os.path.join(settings.BASE_DIR, 'static/index.html')
    with open(save_path, 'w', encoding='utf-8') as f:
        f.write(static_index_html)
tasks.py

開啟redis服務

E:\>cd E:\YifChanSoft\Database\Redis\RedisSoft\Redis-x64-3.2.100

E:\YifChanSoft\Database\Redis\RedisSoft\Redis-x64-3.2.100>redis-server --service-install redis.windows-service.conf --loglevel verbose

E:\YifChanSoft\Database\Redis\RedisSoft\Redis-x64-3.2.100>redis-cli
127.0.0.1:6379> select 8
OK
127.0.0.1:6379[8]> keys *
1) "_kombu.binding.celery"
2) "_kombu.binding.celery.pidbox"
127.0.0.1:6379[8]>

開啟redis服務截圖

將專案程式碼拷貝一份放在某處,進入該處,啟動tasks的worker模式,
注意,用作worker的程式碼的tasks檔案中應該有提前啟動django的初始化的程式碼,不然worker沒法呼叫conf資訊;

即應該有以下內容

# 在任務處理者一端加這幾句
import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shoppingmall.settings")
django.setup()

為了解決celery4.x在win10上執行的錯誤,安裝eventlet

pip install eventlet

進入複製用來做celery工作者的專案程式碼所在處

開啟worker模式

celery -A celery_tasks.tasks worker -l info -P eventlet

開啟worker模式截圖

如果有就刪除celery程式碼檔案中static中的index.html檔案;
主動呼叫 generate_static_index_html.delay() 即可驗證生成index.html;

from celery_tasks.tasks import generate_static_index_html
generate_static_index_html.delay()

驗證截圖

 

可以看到在專案下的static資料夾下生成了index.html;

開啟專案在瀏覽器中輸入 http://127.0.0.1:8888/static/index.html/ 即可看到生成的靜態首頁;因為資料庫中還沒有資料,所以頁面比較空。

 

NGINX的安裝

參考教程:https://blog.csdn.net/weixin_40151334/article/details/80681173 

1.下載nginx:http://nginx.org/en/download.html 

2.解壓縮nginx包

下載好後在放入合適的目錄,解壓縮後如下

3.使用cmd命令,進入nginx所在解壓縮目錄,使用如下命令進行安裝nginx;

start nginx.exe

安裝截圖

安裝完成後,我們可以在 工作管理員中看到nginx任務,如圖

至此,nginx就算安裝完成了。

 

nginx命令

start nginx.exe  # 開啟nginx
nginx -s reload  # 重新啟動
nginx -s stop  # 停止nginx
nginx -s quit  # 退出nginx

 

使用NGINX提供靜態首頁

修改nginx配置

找到nginx的配置檔案,如下圖所示,為了方便以後其他的專案使用,我們拷貝一份原始檔重新命名為nginx_origin.conf

用編輯器開啟 nginx.conf 檔案,修改配置檔案中內容如下:

location /static {
    alias E:/Pycharm/Pycharm_save/cp15/18Django_fresh2/step206/shoppingmall206/static/;
}

location / {
    # root   html;
    root   E:/Pycharm/Pycharm_save/cp15/18Django_fresh2/step206/shoppingmall206/static/;
    index  index.html index.htm;
}

配置截圖

注意,其中的地址應該是你使用celery的專案所在的絕對路徑地址,並且地址之間應該使用斜槓/而不是反斜槓\,否則會報錯。

 

修改好配置儲存後,我們使用一下命令進行nginx的重啟

nginx -s reload

然後,我們開啟瀏覽器輸入一下兩個連結之一就可以看到專案主頁面了。

http://127.0.0.1/  # 注意,後面必須有一個/,否則會進入nginx預設介面
http://127.0.0.1/static/index.html

專案主頁面截圖

 

nginx的cmd命令截圖,其中的報錯都是因為使用的是win10目錄自帶的反斜槓

在Django網站和celery可以理解是並列的關係,在他們之前,其實還有一個nginx伺服器負責排程;
一般是當用戶直接訪問127.0.0.1時,我們通過nginx排程去celery的nginx中返回靜態頁面;
而當用戶訪問127.0.0.1/index時,我們返回呼叫Django網站的IndexView;

在網站上線時我們會使用nginx對它們進行配置。

 

後臺資料修改時重新生成靜態頁面

原理

在資料庫的資料改變時,會呼叫admin.ModelAdmin下的sava_model和delete_model方法用來更新資料,而我們需要當資料改變後重新生成靜態頁面;

因此,我們可以自定義一個類繼承admin.ModelAdmin,重寫更新和刪除資料的方法,呼叫父類的更新刪除方法後,呼叫celery中的方法重新生成靜態首頁;

 

實現

我們要配置當某個表的資料改變時重新生成靜態頁面,就要給該表定義一個 xxxModelAdmin 類,繼承自admin.ModelAdmin並重寫其中的方法,並且在admin中註冊時該表應該同時繼承xxxModelAdmin 類;

因為有很多表都需要如此配置,且類中的程式碼都相同,所以我們可以抽出一個 BaseModelAdmin 類,編寫更新後重新呼叫生成靜態頁面的程式碼,然後讓各個需要修改的表繼承該類即可。

在首頁對應的應用中的admin.py檔案中編寫如下程式碼

from django.contrib import admin
# from django.core.cache import cache
from goods.models import GoodsType, GoodsSKU, Goods, GoodsImage, IndexGoodsBanner, IndexTypeGoodsBanner, IndexPromotionBanner
from celery_tasks.tasks import generate_static_index_html


class BaseModelAdmin(admin.ModelAdmin):
    """當後臺資料庫資料改動時使celery重新生成靜態首頁頁面"""
    def save_model(self, request, obj, form, change):
        """當更新或者新增資料時呼叫"""
        super().save_model(request, obj, form, change)
        # 發出任務,讓celery worker重新生成靜態首頁
        generate_static_index_html.delay()

        # 清除首頁的快取資料
        # cache.delete("index_page_data")

    def delete_model(self, request, obj):
        """當刪除資料時呼叫"""
        super().delete_model(request, obj)
        generate_static_index_html.delay()

        # 清除首頁的快取資料
        # cache.delete("index_page_data")


class GoodsTypeAdmin(BaseModelAdmin):
    pass


class IndexGoodsBannerAdmin(BaseModelAdmin):
    pass


class IndexTypeGoodsBannerAdmin(BaseModelAdmin):
    pass


class IndexPromotionBannerAdmin(BaseModelAdmin):
    pass


admin.site.register(GoodsType, GoodsTypeAdmin)
admin.site.register(GoodsSKU)
admin.site.register(Goods)
admin.site.register(GoodsImage)
admin.site.register(IndexGoodsBanner, IndexGoodsBannerAdmin)
admin.site.register(IndexTypeGoodsBanner, IndexTypeGoodsBannerAdmin)
admin.site.register(IndexPromotionBanner, IndexPromotionBannerAdmin)
admin.py

至此,當我們在admin後臺更新資料時就會重新生成靜態首頁了,大家可以自行嘗試一下~

&n