Django【性能提升篇】


數據庫部分

一、查詢優化

二、持久化數據庫連接

  django1.6以后已經內置了數據庫持久化連接,很多人使用PostgreSQL作為它們的線上數據庫系統,而當我們連接PostgreSQL有時會顯得很慢,這里我們可以進行優化。 

沒有持久化連接,每一個網站的請求都會與數據庫建立一個連接。如果數據庫不在本地,盡管網速很快,這也將花費20-75ms.

  設置持久化連接,僅需要添加CONN_MAX_AGE參數到你的數據庫設置中:

DATABASES = {
    ‘default’: {
        ‘ENGINE’: ‘django.db.backends.postgresql_psycopg2’,
        ‘NAME’: ‘whoohoodb’,
        ‘CONN_MAX_AGE’: 600,
    }
}
View Code

  通過這樣的設置,我們設置的持久化連接每次都將存活10分鍾 
  這有助於減少內存泄漏或導致一種片狀連接的問題。 
  你可設置更長的時間,但是我不要設置超過1個小時,因為這樣獲得的效果不會太好。你可以從django的幫助文檔中獲取詳細信息 django持久化連接

三、select_related() 和 prefetch_related()

相比於修改數據庫存儲,這里只要需要簡單的設置select_related()和prefetch_related(),在使用ORM的情況下,他能夠減少sql查詢的數量。 
這里有一個BlogPost模型,它有一個用戶外鍵,獲得了一個listview

queryset = BlogPost.objects.active

那么在模版中是這樣使用:

<ul>
{% for post in object_list %}
  <li>{{ post.title }} - {{ post.user.email }}</li>
{% endfor %}
</ul>

這里滿足了預期的效果,但是每個post都會去查詢auth_user表。為了解決這個問題,可以讓djangode的ORM在前面就連接上auth_user 表,這樣object.user就是一個可以直接用的對象了,這樣BlogPost.objects.active().count()就會變成一個簡單的查詢

修改如下:

queryset = BlogPost.objects.select_related().active()

prefetch_related的機理是相同的


當感到疑惑時,開啟django調試工具,然后分析每次請求的查詢次數和時間,如果每次查詢都要用上5~10次,那么這些就是可以優化的線索

四、利用標准數據庫優化技術

傳統數據庫優化技術博大精深,不同的數據庫有不同的優化技巧,但重心還是有規則的。在這里算是題外話,挑兩點通用的說說:
  索引,給關鍵的字段添加索引,性能能更上一層樓,如給表的關聯字段,搜索頻率高的字段加上索引等。Django建立實體的時候,支持給字段添加索引,具體參考Django.db.models.Field.db_index。按照經驗,Django建立實體之前應該早想好表的結構,盡量想到后面的擴展性,避免后面的表的結構變得面目全非。
  使用適當字段類型,本來varchar就搞定的字段,就別要text類型,小細節別不關緊要,后頭數據量一上去,愈來愈多的數據,小字段很可能是大問題。

五、了解Django的QuerySets

  了解Django的QuerySets對象,對優化簡單程序有至關重要的作用。QuerySets是有緩存的,一旦取出來,它就會在內存里呆上一段時間,盡量重用它。

# 了解緩存屬性:
>>> entry = Entry.objects.get(id=1)
>>> entry.blog   # 博客實體第一次取出,是要訪問數據庫的
>>> entry.blog   # 第二次再用,那它就是緩存里的實體了,不再訪問數據庫
>>> entry = Entry.objects.get(id=1)
>>> entry.authors.all()   # 第一次all函數會查詢數據庫
>>> entry.authors.all()   # 第二次all函數還會查詢數據庫
  • all,count exists是調用函數(需要連接數據庫處理結果的),注意在模板template里的代碼,模板里不允許括號,但如果使用此類的調用函數,一樣去連接數據庫的,能用緩存的數據就別連接到數據庫去處理結果。還要注意的是,自定義的實體屬性,如果調用函數的,記得自己加上緩存策略。
  • 利用好模板的with標簽:

       模板中多次使用的變量,要用with標簽,把它看成變量的緩存行為吧。

  • 使用QuerySets的iterator():   

  通常QuerySets先調用iterator再緩存起來,當獲取大量的實體列表而僅使用一次時,緩存行為會耗費寶貴的內存,這時iterator()能幫到你,iterator()只調用iterator而省 去了緩存步驟,顯著減少內存占用率,具體參考相關文檔。

六、數據庫的工作就交給數據庫本身計算,別用Python處理  

  • 使用 filter and exclude 過濾不需要的記錄,這兩個是最常用語句,相當是SQL的where
  • 同一實體里使用F()表達式過濾其他字段
  • 使用annotate對數據庫做聚合運算

      不要用python語言對以上類型數據過濾篩選,同樣的結果,python處理復雜度要高,而且效率不高, 白白浪費內存

  • 使用QuerySet.extra() extra雖然擴展性不太好,但功能很強大,如果實體里需要需要增加額外屬性,不得已時,通過extra來實現,也是個好辦法
  • 使用原生的SQL語句 如果發現Django的ORM已經實現不了你的需求,而extra也無濟於事的時候,那就用原生SQL語句

七、如果需要就一次性取出你所需要的數據  

  單一動作(如:同一個頁面)需要多次連接數據庫時,最好一次性取出所有需要的數據,減少連接數據庫次數。

  此類需求推薦使用QuerySet.select_related() (主動連表)和 prefetch_related()(被動連表)

  相反,別取出你不需要的東西,模版templates里往往只需要實體的某幾個字段而不是全部,這時QuerySet.values() 和 values_list(),對你有用,它們只取你需要的字段,返回字典dict和列表list類型的東西,在模版里夠用即可,這可減少內存損耗,提高性能

  同樣QuerySet.defer()only()對提高性能也有很大的幫助,一個實體里可能有不少的字段,有些字段包含很多元數據,比如博客的正文,很多字符組成,Django獲取實體時(取出實體過程中會進行一些python類型轉換工作),我們可以延遲大量元數據字段的處理,只處理需要的關鍵字段,這時QuerySet.defer()就派上用場了,在函數里傳入需要延時處理的字段即可;而only()和defer()是相反功能

  使用QuerySet.count()代替len(queryset),雖然這兩個處理得出的結果是一樣的,但前者性能優秀很多。同理判斷記錄存在時,QuerySet.exists()比if queryset實在強得太多了

八、懂減少數據庫的連接數

  使用 QuerySet.update() 和 delete(),這兩個函數是能批處理多條記錄的,適當使用它們事半功倍;如果可以,別一條條數據去update delete處理。

  對於一次性取出來的關聯記錄,獲取外鍵的時候,直接取關聯表的屬性,而不是取關聯屬性,如:

entry.blog.id
優於
entry.blog__id


# 善於使用批量插入記錄,如:
Entry.objects.bulk_create([
    Entry(headline="Python 3.0 Released"),
    Entry(headline="Python 3.1 Planned")
])
優於
Entry.objects.create(headline="Python 3.0 Released")
Entry.objects.create(headline="Python 3.1 Planned")
# 前者只連接一次數據庫,而后者連接兩次


# 還有相似的動作需要注意的,如:多對多的關系,
my_band.members.add(me, my_friend)
優於
my_band.members.add(me)
my_band.members.add(my_friend)

  

 

 

 

 

 

 

 

 

 

 

 

六、讀寫分離

1.增加主從數據庫

DATABASES = {
      'default': {
          'ENGINE': 'django.db.backends.mysql',
          'NAME': 'dailyfresh',
          'HOST': '192.168.243.193', # MySQL數據庫地址(主)
          'PORT': '3306',
          'USER': 'root',
          'PASSWORD': 'mysql',
      },
      'slave': {
              'ENGINE': 'django.db.backends.mysql',
              'NAME': 'dailyfresh',
              'HOST': '192.168.243.189', # MySQL數據庫地址(從)
              'PORT': '3306',
              'USER': 'root',
              'PASSWORD': 'mysql',
          }
  }
View Code

2.編輯路由分發的類 :db_router.py

class MasterSlaveDBRouter(object):
      """讀寫分離路由"""

      def db_for_read(self, model, **hints):
          """"""
          return 'slave'

      def db_for_write(self, model, **hints):
          """"""
          return 'default'

      def allow_relation(self, obj1, obj2, **hints):
          """是否允許關聯查詢"""
          return True
View Code

3.讀寫分離引導settings.py

# 配置讀寫分離
DATABASE_ROUTERS = ['utils.db_router.MasterSlaveDBRouter']
View Code

 

三、redis集群

頁面渲染部分

 一、模板加載

  默認django使用兩個標准的模版加載器

TEMPLATE_LOADERS = (
    ‘django.template.loaders.filesystem.Loader’,
    ‘django.template.loaders.app_directories.Loader’,
)
View Code

  每一個請求,這些模版加載器都會去文件系統搜索,然后解析這些模版。 
  這里可以感覺到,它是不是可以處理的更快了? 
  你可以開啟緩存加載,因此django只會查找並且解析你的模版一次 
配置如下:

TEMPLATE_LOADERS = (
    (‘django.template.loaders.cached.Loader’, (
        ‘django.template.loaders.filesystem.Loader’,
        ‘django.template.loaders.app_directories.Loader’,
    )),
)
settings

但是,不要在開發環境中開啟緩存加載,這樣會很煩人的,因為每次模版做了修改之后你都需要重啟服務才能看到修改后的效果

 二、頁面靜態化

  • 對於主頁,信息豐富,需要多次查詢數據庫才能得到全部數據。
  • 如果用戶每次訪問主頁,都做多次數據庫查詢,性能差。
  • 優化:將主頁存儲成靜態頁面,用戶訪問時,響應靜態頁面
  • 實現:把render()返回的html數據存儲起來
  • 以上是wed服務器優化方案之一,把頁面靜態化,減少服務器處理動態數據的壓力

 1、實現思路

  • 后台站點在發布主頁內容時,Django使用異步任務生成主頁靜態頁面
  • 需要使用celery服務器執行異步任務
  • 需要使用nginx服務器提供靜態頁面訪問服務
  • 需要注冊模型類到站點,並創建模型類管理類,在模型類管理類中調用celery異步任務

  

2、celery生成靜態html頁面

import os
os.environ["DJANGO_SETTINGS_MODULE"] = "dailyfresh.settings"
# 放到celery服務器上時將注釋打開
#import django
#django.setup()

from celery import Celery
from django.core.mail import send_mail
from django.conf import settings
from goods.models import GoodsCategory,Goods,IndexGoodsBanner,IndexPromotionBanner,IndexCategoryGoodsBanner
from django.template import loader

# 創建celery應用對象
app = Celery('celery_tasks.tasks', broker='redis://192.168.243.193/4')

@app.task
def send_active_email(to_email, user_name, token):
    """發送激活郵件"""

    subject = "天天生鮮用戶激活"  # 標題
    body = ""  # 文本郵件體
    sender = settings.EMAIL_FROM  # 發件人
    receiver = [to_email]  # 接收人
    html_body = '<h1>尊敬的用戶 %s, 感謝您注冊天天生鮮!</h1>' \
                '<br/><p>請點擊此鏈接激活您的帳號<a href="http://127.0.0.1:8000/users/active/%s">' \
                'http://127.0.0.1:8000/users/active/%s</a></p>' %(user_name, token, token)
    send_mail(subject, body, sender, receiver, html_message=html_body)


@app.task
def generate_static_index_html():
    """生成靜態的html頁面"""

    # 查詢商品分類信息
    categorys = GoodsCategory.objects.all()

    # 查詢圖片輪播信息:按照index進行排序
    banners = IndexGoodsBanner.objects.all().order_by('index')

    # 查詢活動信息
    promotion_banners = IndexPromotionBanner.objects.all().order_by('index')

    # 查詢分類商品信息
    for category in categorys:
        title_banners = IndexCategoryGoodsBanner.objects.filter(category=category, display_type=0).order_by('index')
        category.title_banners = title_banners

        image_banners = IndexCategoryGoodsBanner.objects.filter(category=category, display_type=1).order_by('index')
        category.image_banners = image_banners

    # 查詢購物車信息
    cart_num = 0

    # 構造上下文
    context = {
        'categorys': categorys,
        'banners': banners,
        'promotion_banners': promotion_banners,
        'cart_num': cart_num
    }

    # 加載模板
    template = loader.get_template('static_index.html')
    html_data = template.render(context)

    # 保存成html文件:放到靜態文件中
    file_path = os.path.join(settings.STATICFILES_DIRS[0], 'index.html')
    with open(file_path, 'w') as file:
        file.write(html_data)
定義異步任務

3、測試celery生成靜態文件

  • 將項目拷貝到celery服務器中
  • 開啟celery:celery -A celery_tasks.tasks worker -l info
  • 終端 python manage.py shell 測試異步任務調度

 4、配置nginx訪問靜態頁面

  • 為了讓celery服務器中的靜態文件能夠被高效訪問,需要給celery配置nginx服務器
  • nginx服務器提供靜態數據效率高

 5、配置參數

  • 進入到:/usr/local/nginx/conf
  • 在nginx.conf文件中,配置新的server選項,將主頁的訪問引導到nginx服務器
  • 在nginx.conf文件中,具體配置html頁面中靜態文件image、css、js訪問路徑
  • 重啟nginx:sudo /usr/local/nginx/sbin/nginx -s reload

          

             

6、測試nginx服務

   瀏覽器中輸入nginx服務器地址,默認端口號80,查看能否加載到主頁靜態頁面

 

 

 

 

 

 

 

緩存部分

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM