随着网站的内容的增多和用户访问量的增多,无可避免的是网站加载会越来越慢,受限于带宽和服务器同一时间的请求次数的限制,我们往往需要在此时对我们的网站进行代码优化和服务器配置的优化。
此文已Django的电商网站为例(处理高并发问题)
技术点:首页静态化,详情页面静态化,定时任务,异步任务
所谓的静态化技术就是 将查询好的数据填充到模板中,然后将生成的html写入到指定的文件中
静态处理后又给网站带来了哪些好处?
- 静态页面相对于动态页面更容易被搜索引擎收录。
- 访问静态页面不需要经过程序处理,因此可以提高运行速度。
- 减轻服务器负担。
- HTML页面不会受Asp相关漏洞的影响。
分析需求:
1.首页频繁被访问 ,经常需要更新,发生变化,页面只有一个(用定时任务处理,定时刷新)
2.详情页面页面多,只有在数据发生变化时才需要更新页面(需要生成的静态化页面多,用celery处理)
前期配置:
需要在Django的setting.py文件中添加模板路径
修改成:
'DIRS': [os.path.join(BASE_DIR, 'templates')],
添加文件保存路径
DEFAULT_FILE_STORAGE = 'utils.fastdfs.fdfsstorage.FastDFSStorage'
在项目中创建templates文件夹
注意创建的路径,如果路径创建错误,会一直报Templates DoesNotExist的错误
此时可以找一个test.py
import os
from mall.settings import BASE_DIR
url = [os.path.join(BASE_DIR, 'templates')]
print(url)
输出一下路径看路径是否正确
在templates文件夹中创建home.html,index.html和detail.html并添加对应的前端代码
获取到前端和详情页面的数据,保证能正常访问
首页:(分类数据和首页数据)
静态化:
1.获取到模板
2.将数据渲染到模板中
3.需要将html数据写入指定的文件中
template = loader.get_template('index.html')
html_data = template.render(context)
#将文件写入
file_path = os.path.join(settings.GENERATED_STATIC_HTML_FILES_DIR,'index.html')
with open(file_path,'w') as f:
f.write(html_data)
添加静态化首页的手动脚本test_script.py文件
#!/usr/bin/env python
import sys
sys.path.insert(0, '../')
sys.path.insert(0, '../apps')
import os
if not os.getenv('DJANGO_SETTINGS_MODULE'):
os.environ['DJANGO_SETTINGS_MODULE'] = 'mall.settings'
# 让django进行初始化设置
import django
django.setup()
from contents.crons import generate_static_index_html
if __name__ == '__main__':
generate_static_index_html()
给脚本文件添加可执行权限: chmod +x test_script.py
但是像网站通常都会常常需要更新像推荐商品等信息,我们不能每次在定点让人生成静态文件
此时可以利用定时任务
安装:pip install django-crontab
添加应用
INSTALLED_APPS = [
...
'django_crontab', # 定时任务
...
]
设置任务的定时时间
在配置文件中设置定时执行的时间
每个定时任务分为三部分定义:
- 任务时间
基本格式 :
* * * * *
分 时 日 月 周 命令
M: 分钟(0-59)。每分钟用*或者 */1表示
H:小时(0-23)。(0表示0点)
D:天(1-31)。
m: 月(1-12)。
d: 一星期内的天(0~6,0为星期天)。
在setting.py中添加
# 定时任务
CRONJOBS = [
# 每5分钟执行一次生成主页静态文件
('*/1 * * * *', 'contents.crons.generate_static_index_html', '>> /home/python/Desktop/meiduo/mall/logs/crontab.log')
]
执行脚本文件:./test_script.py
此时首页静态化页面创建成功
在/home/python/Desktop/meiduo/mall/logs/crontab.log文件中可以看到
Wed Aug 29 22:17:01 2018:generate_static_index
Wed Aug 29 22:18:02 2018:generate_static_index
详情页面
1.详情页面需求时项目上线先静态化生成全部的页面
2.然后在后台管理系统每次更改数据的时候重新生成静态化页面
在celery_tasks中新建html/tasks.py任务
import os
from celery_tasks.main import app
from utils.goods import get_categories
from django.template import loader
from django.conf import settings
from goods.models import Goods,SKU
@app.task(name='generate_static_sku_detail_html')
def generate_static_sku_detail_html(sku_id):
# 获取分类数据
categories = get_categories()
# 获取当前商品数据
sku = SKU.objects.get(id=sku_id)
sku.images = sku.skuimage_set.all()
# 获取面包屑数据
goods = sku.goods
goods.channel = goods.category1.goodschannel_set.all()[0]
# 获取商品规格项ids
sku_key = []
sku_specs = sku.skuspecification_set.order_by('spec_id')
for sku_spec in sku_specs:
sku_key.append(sku_spec.option.id)
# 构建商品规格
# 获取所有商品,
skus = goods.sku_set.all()
# 构建不同规格参数(选项)的sku字典
# spec_sku_map = {
# (规格1参数id, 规格2参数id, 规格3参数id, ...): sku_id,
# (规格1参数id, 规格2参数id, 规格3参数id, ...): sku_id,
# ...
# }
spec_sku_map = {}
for s in skus:
s_peces = sku.skuspecification_set.order_by('spec_id')
# 用于记录 规格参数id
key = []
for spec in s_peces:
key.append(spec.option.id)
# 添加数据
spec_sku_map[tuple(key)] = s.id
# 获取当前商品的规格信息
# specs = [
# {
# 'name': '屏幕尺寸',
# 'options': [
# {'value': '13.3寸', 'sku_id': xxx},
# {'value': '15.4寸', 'sku_id': xxx},
# ]
# },
# {
# 'name': '颜色',
# 'options': [
# {'value': '银色', 'sku_id': xxx},
# {'value': '黑色', 'sku_id': xxx}
# ]
# },
# ...
# ]
specs = goods.goodsspecification_set.order_by('id')
# 若当前sku的规格信息不完整,则不再继续
if len(sku_key) < len(specs):
return
# 针对于商品数据进行遍历
for index, spec in enumerate(specs):
# 复制当前sku的规格键
key = sku_key[:]
# 该规格的选项
options = spec.specificationoption_set.all()
for option in options:
# 在规格参数sku字典中查询符合当前规则的sku
key[index] = option.id
option.sku_id = spec_sku_map.get(tuple(key))
spec.options = options
# 组织上下文
context = {
'categories': categories,
'goods': goods,
'specs': specs,
'sku': sku
}
template = loader.get_template('detail.html')
html_text = template.render(context)
file_path = os.path.join(settings.GENERATED_STATIC_HTML_FILES_DIR, 'goods/' + str(sku_id) + '.html')
with open(file_path, 'w') as f:
f.write(html_text)
重新执行celery
celery -A celery_tasks.main worker -l info
在front文件夹中,创建一个 goods文件夹
1.需要创建test2_script.py脚本文件
import sys
sys.path.insert(0, '../')
sys.path.insert(0, '../mall/apps')
import os
if not os.getenv('DJANGO_SETTINGS_MODULE'):
os.environ['DJANGO_SETTINGS_MODULE'] = 'mall.settings'
import django
django.setup()
from django.template import loader
from django.conf import settings
from utils.goods import get_categories
from goods.models import SKU
def generate_static_sku_detail_html(sku_id):
"""
生成静态商品详情页面
"""
# 商品分类菜单
categories = get_categories()
# 获取当前sku的信息
sku = SKU.objects.get(id=sku_id)
sku.images = sku.skuimage_set.all()
# 面包屑导航信息中的频道
goods = sku.goods
goods.channel = goods.category1.goodschannel_set.all()[0]
# 构建当前商品的规格键
sku_specs = sku.skuspecification_set.order_by('spec_id')
sku_key = []
for spec in sku_specs:
sku_key.append(spec.option.id)
# 获取当前商品的所有SKU
skus = goods.sku_set.all()
# 构建不同规格参数(选项)的sku字典
# spec_sku_map = {
# (规格1参数id, 规格2参数id, 规格3参数id, ...): sku_id,
# (规格1参数id, 规格2参数id, 规格3参数id, ...): sku_id,
# ...
# }
spec_sku_map = {}
for s in skus:
# 获取sku的规格参数
s_specs = s.skuspecification_set.order_by('spec_id')
# 用于形成规格参数-sku字典的键
key = []
for spec in s_specs:
key.append(spec.option.id)
# 向规格参数-sku字典添加记录
spec_sku_map[tuple(key)] = s.id
# 获取当前商品的规格信息
specs = goods.goodsspecification_set.order_by('id')
# 若当前sku的规格信息不完整,则不再继续
if len(sku_key) < len(specs):
return
for index, spec in enumerate(specs):
# 复制当前sku的规格键
key = sku_key[:]
# 该规格的选项
options = spec.specificationoption_set.all()
for option in options:
# 在规格参数sku字典中查找符合当前规格的sku
key[index] = option.id
option.sku_id = spec_sku_map.get(tuple(key))
spec.options = options
# 渲染模板,生成静态html文件
context = {
'categories': categories,
'goods': goods,
'specs': specs,
'sku': sku
}
template = loader.get_template('detail.html')
html_text = template.render(context)
file_path = os.path.join(settings.GENERATED_STATIC_HTML_FILES_DIR, 'goods/' + str(sku_id) + '.html')
with open(file_path, 'w') as f:
f.write(html_text)
if __name__ == '__main__':
skus = SKU.objects.all()
for sku in skus:
print(sku.id)
generate_static_sku_detail_html(sku.id)
给文件添加可执行权限: chmod +x test2_script.py
执行脚本文件:./test2_script.py
2.修改数据时调用异步任务需要重写save_model方法
例如:
from . import models
class SKUAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.save()
from celery_tasks.html.tasks import generate_static_sku_detail_html
generate_static_sku_detail_html.delay(obj.id)
全部添加成功后,在admin管理页面中修改数据后,就会调用异步任务重新生成静态化文件了