Djangoプロジェクトのトップページ静的化とキャッシュ戦略

ページの静的化

静的化が必要な理由

トップページはサイトで最も頻繁にアクセスされるページであり、ユーザーがログインしているかどうかに関わらずアクセスできます。各アクセス時にデータベースからデータをクエリする必要があり、ほとんどのアクセスで表示される内容は同じです(バックエンド管理者がトップページのデータ情報を変更しない限り)。

そのため、トップページを静的なページとして個別に作成し(データ情報を含む)、未ログインのユーザーが直接このページにアクセスできるようにすることで、サーバーの負荷を軽減できます。

また、このページは静的に書き込まれているため、バックエンドのデータが変更されてもページ情報は元のままです。この問題を解決し、静的ページのデータを最新の状態に保つために、バックエンド管理者がトップページのデータ情報を変更するたびに、新しい静的ページを再生成します。

静的ページを生成する方法

管理者がトップページに必要なデータを更新するたびに、静的ページを生成するメソッドを呼び出すことができます。生成プロセスに時間がかかる場合、管理者のフロントエンドでの長時間の待機を引き起こす可能性があるため、Celeryを使用して非同期でページを生成できます。

Celeryの設定は「DJANGO-天天生鲜プロジェクト0から1-002-ユーザーモジュール-登録」のCelery利用と同じであるため、その基盤となるtasks.pyファイルに新しいタスクを追加するだけです。

トップページデータの取得

トップページのデータ取得は複数の場所で使用されるため、メソッドとしてカプセル化し、前節のショッピングカート数量取得と同様にutils/utils.pyに配置します:

from products.models import *

def get_homepage_data():
    '''トップページデータ情報を取得'''
    # 商品カテゴリを取得
    product_categories = ProductCategory.objects.all()
    # スライドショー商品を取得
    featured_products = FeaturedProductBanner.objects.all().order_by('display_order')
    # プロモーション情報を取得
    promotion_banners = PromotionBanner.objects.all().order_by('display_order')

    # カテゴリ商品表示情報を取得
    for category in product_categories:
        # このカテゴリの下にある商品のタイトル情報を取得し、並べ替え
        title_banners = CategoryProductBanner.objects.filter(
            category=category, banner_type='title').order_by('display_order')
        # このカテゴリの下にある商品の画像情報を取得し、並べ替え
        image_banners = CategoryProductBanner.objects.filter(
            category=category, banner_type='image').order_by('display_order')
        # 動的にcategoryに属性を追加し、トップページカテゴリ商品のテキスト情報と画像情報をそれぞれ保存
        category.title_banners = title_banners
        category.image_banners = image_banners

    # コンテキストを構成
    context = {
        'product_categories': product_categories,
        'featured_products': featured_products,
        'promotion_banners': promotion_banners,
    }
    return context

Celeryタスクの作成

データを取得した後、products/home.htmlテンプレートファイルを使用し、テンプレートオブジェクトのrenderメソッドを使用して取得したデータをテンプレートファイルに挿入し、HTMLコンテンツを生成します。その後、with openを使用してHTMLコンテンツをstaticディレクトリのindex.htmlファイルに書き込みます。ここでは、まず空のファイルを作成する必要があります。

from utils.helpers import get_homepage_data
from django.template import loader

@app.task()
def generate_static_homepage():
    # 表示する情報を取得
    context = get_homepage_data()
    # テンプレートを使用
    # テンプレートファイルをロードし、テンプレートオブジェクトを返す
    template = loader.get_template('products/home.html')
    # テンプレートをレンダリング
    static_homepage = template.render(context)

    # HTMLファイルを生成
    save_path = os.path.join(settings.BASE_DIR, 'static/index.html')
    with open(save_path, 'w') as f:
        f.write(static_homepage)

adminモデル管理クラスの作成とCeleryタスクの呼び出し

バックエンド管理者がトップページのデータ情報を変更したときに静的ファイルを作成するCeleryタスクを呼び出す必要があるため、adminモデル管理クラスを使用し、関連モデルクラスのsaveおよびdeleteメソッドをオーバーライドします。

products/admin.pyファイルを編集:

from django.contrib import admin
from products.models import *
from utils.tasks import generate_static_homepage
from django.core.cache import cache
# モデルを登録

class BaseModelAdmin(admin.ModelAdmin):
    '''モデル管理サイト'''
    def save_model(self, request, obj, form, change):
        # 親クラスのメソッドを継承
        super().save_model(request, obj, form, change)
        # Celeryを使用して静的トップページファイルを再生成
        generate_static_homepage.delay()
        # キャッシュをクリア
        cache.delete('homepage_data')

    def delete_model(self, request, obj):
        # 親クラスのメソッドを継承
        super().delete_model(request, obj)
        # Celeryを使用して静的トップページファイルを再生成
        generate_static_homepage.delay()
        # キャッシュをクリア
        cache.delete('homepage_data')

admin.site.register(Product, BaseModelAdmin)
admin.site.register(ProductSPU, BaseModelAdmin)
admin.site.register(ProductCategory, BaseModelAdmin)
admin.site.register(ProductImage, BaseModelAdmin)
admin.site.register(FeaturedProductBanner, BaseModelAdmin)
admin.site.register(CategoryProductBanner, BaseModelAdmin)
admin.site.register(PromotionBanner, BaseModelAdmin)

Nginx経由での静的ページへのアクセス

上記で生成された静的ページは、DjangoサーバーのIP:8000/static/index.htmlのURL経由でのみアクセスできますが、ユーザーがIPアドレスを直接入力しただけで静的ページにアクセスできるようにするには、Nginxの助けが必要です。Nginx設定ファイル(/usr/local/nginx/conf/nginx.conf)で、次のように設定を変更します:

server {
        listen       80;
        server_name  localhost;
        #charset koi8-r;
        #access_log  logs/host.access.log  main;
        location /static {
            alias /home/user/projects/freshmarket/static/;
        }
        location / {
            #root   html;
            root /home/user/projects/freshmarket/static/;
            index index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

ブラウザでIPアドレスを入力し、ポート番号を入力しない場合、デフォルトで80ポートにアクセスします。locationはURLマッチャーとして機能し、上から下にマッチします。ip/staticに一致する場合、/home/user/projects/freshmarket/static/ディレクトリ下のindex.htmlファイルが表示されます。IPアドレスの後に入力がない場合も、/home/user/projects/freshmarket/static/ディレクトリ下のindex%ファイルが表示されます。

ブラウザでhttp://192.168.1.100/static/またはhttp://192.168.1.100にアクセスすると、生成された静的ページが表示されます。

静的化されたトップページと通常の動的トップページのどちらにアクセスするか

静的化されたトップページにアクセスする場合、アクセスするアドレスはhttp://192.168.1.100で、デフォルトで80ポートであり、アクセスするサーバーはNginxサーバーです。

動的化されたトップページにアクセスする場合、アクセスするアドレスはhttp://192.168.1.100:8000で、8000ポートであり、アクセスするサーバーはDjangoサーバーです。

しかし、本番環境にデプロイした後、ユーザーに公開するアドレスは最終的に1つだけであり、上記の2つのアドレスをユーザーに選択させるのではなく、ユーザーと(Nginx、Django)サーバーの間に別のNginxサーバーを設置し、このNginxサーバーを通じてユーザーにブラウジングアドレス(例:http://192.168.1.101)を公開し、サーバーがユーザーが最終的にアクセスするサーバーを判断できるようにします(例:ユーザーがhttp://192.168.1.101を直接入力した場合、中間のNginxは最終的にNginxが指す静的トップページにアクセスさせ、ユーザーがhttp://192.168.1.101/indexを入力した場合、中間のNginxは最終的にDjangoサーバーの動的トップページにアクセスさせます)。

トップページデータ情報のキャッシュ

静的ページのトップページではなく、通常の動的トップページにアクセスする場合でも、各アクセス時にデータベースを再クエリする必要があります。実際には、クエリされたデータはほとんど同じであり、バックエンド管理者がバックエンドデータを変更しない限り、この問題を回避するために、Djangoのキャッシュメカニズムを使用して、最初にクエリされたデータをDjangoサーバーのキャッシュ(ブラウザのキャッシュではないことに注意)に保存し、後続のページアクセス時に、まずキャッシュでデータが存在するかを確認し、存在する場合は直接データを取得し、存在しない場合はデータを再クエリします。

Djangoキャッシュの設定

キャッシュはデータベースまたはファイルシステムに存在できます。一般的に、メモリ型データベースに存在させます。以前にログインを設定したとき(DJANGO-天天生鲜プロジェクト0から1-003-ユーザーモジュール-ログイン)、Redisデータベースにキャッシュを保存する設定をすでに完了しています。

CACHES = {
  "default": {
    "BACKEND": "django_redis.cache.RedisCache",
    "LOCATION": "redis://127.0.0.1:6379/1",
    "OPTIONS": {
      "CLIENT_CLASS": "django_redis.client.DefaultClient",
    }
  }
}

Djangoキャッシュの使用:cache.get(key)、cache.set(key, data, timeout)

トップページのview.pyで、データベース情報をクエリする前に、まずcache.getを使用してキャッシュから必要なデータが存在するかを確認します。存在しない場合は、いくつかの共通データベース情報をクエリした後、これらの共通情報をcache.setを使用してキャッシュに保存します。

from django.core.cache import cache

class HomeView(View):
    '''トップページビュー'''
    template_name = 'products/home.html'
    def get(self, request):
        '''トップページを表示'''
        # キャッシュからデータを取得
        context = cache.get('homepage_data')
        if not context:
            # データベース情報を取得
            context = get_homepage_data()
            # キャッシュを設定、3600秒で期限切れ
            print('キャッシュを設定')
            cache.set('homepage_data', context, 3600)

        # ショッピングカート数量を取得、ショッピングカートの保存形式:cart_userid : {'productid': quantity}
        cart_count = get_cart_quantity(request)
        context['cart_count'] = cart_count
        return render(request, self.template_name, context)

キャッシュの削除:cache.delete(key)

同様に、バックエンド管理者がトップページのデータベース情報を変更したときに、元のキャッシュを削除し、削除後に再度トップページにアクセスしたときに新しいキャッシュ情報を設定する必要があります。

from django.core.cache import cache

class BaseModelAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        '''テーブルのデータを新規作成または更新する際に呼び出される'''
        super().save_model(request, obj, form, change)

        # タスクを発行し、Celeryワーカーにトップページの静的HTMLを再生成させる
        from celery_tasks.tasks import regenerate_homepage_html
        regenerate_homepage_html.delay()

        # トップページのキャッシュデータをクリア
        cache.delete('homepage_data')

    def delete_model(self, request, obj):
        '''テーブルのデータを削除する際に呼び出される'''
        super().delete_model(request, obj)
        # タスクを発行し、Celeryワーカーにトップページの静的HTMLを再生成させる
        from celery_tasks.tasks import regenerate_homepage_html
        regenerate_homepage_html.delay()

        # トップページのキャッシュデータをクリア
        cache.delete('homepage_data')

タグ: Django キャッシュ 静的化 Celery nginx

5月20日 09:57 投稿