惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tenable Blog
Engineering at Meta
Engineering at Meta
The Register - Security
The Register - Security
N
Netflix TechBlog - Medium
D
Docker
Vercel News
Vercel News
云风的 BLOG
云风的 BLOG
月光博客
月光博客
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
D
DataBreaches.Net
IT之家
IT之家
V
V2EX
人人都是产品经理
人人都是产品经理
F
Fortinet All Blogs
J
Java Code Geeks
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 叶小钗
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
雷峰网
雷峰网
博客园 - Franky
Hugging Face - Blog
Hugging Face - Blog
有赞技术团队
有赞技术团队
aimingoo的专栏
aimingoo的专栏
MongoDB | Blog
MongoDB | Blog
P
Privacy International News Feed
F
Full Disclosure
P
Proofpoint News Feed
B
Blog RSS Feed
T
Tor Project blog
T
The Blog of Author Tim Ferriss
B
Blog
Webroot Blog
Webroot Blog
腾讯CDC
T
Troy Hunt's Blog
T
Tailwind CSS Blog
H
Heimdal Security Blog
AWS News Blog
AWS News Blog
G
Google Developers Blog
Spread Privacy
Spread Privacy
NISL@THU
NISL@THU
A
About on SuperTechFans
SecWiki News
SecWiki News
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
I
InfoQ
M
MIT News - Artificial intelligence
大猫的无限游戏
大猫的无限游戏
美团技术团队
L
LangChain Blog

陈少文的网站

巨变与机遇的未来十年 Kubernetes 平台管理软件压力测试方案 使用镜像部署 Hexo 静态页面 终于等到你 - GitHub 镜像仓库服务(ghcr.io) 一起来学 Go --(6)Interface 一起来学 Go --(5)Goroutine 和 Channel 什么是函数式编程 如何在 Kubernetes 集群集成 Kata 柯里化与偏函数 使用 PyGithub 自动创建 Label 软件产品是团队能力的输出 Helm 2 、Helm 3 比较 IoT 变现 Kubernetes 中的 DNS 服务 国内的 Helm 镜像源 Harbor 使用自签证书支持 Https 访问 DevOps 工具链之 Prow 如何使用 kfctl 安装 Kubeflow VS Code 无法下载 Go 插件的工具包 工程师更应具有服务精神 你不知道的 Docker 使用技巧 使用 Docker 运行 Tensorflow 论中国 什么是左移 如何清空 Git 仓库全部历史记录 一禅小和尚 有风吹过厨房 时间的玫瑰 如何在 CentOS 安装 GPU 驱动 开发 Tips(19) 使用 Velero 备份 Kubernetes 集群 Kubernetes Cheat Sheet 开发 Tips(18) 如何构建一个 Java 工程 开发 Tips(17) KubeSpray 安装 Kubernetes 报错 ip in ansible_all_ipv4_addresses 基于 Kubernetes 和 Jenkins 搭建自动化测试系统 在 Kubernetes 上动态创建 Jenkins Slave 使用 Jenkins 进行服务拨测 开发 Tips(16) Kubernetes 签发 Ingress 证书及日常故障运维 Kubernetes 中 Deployment 的基本操作 Kubernetes 中的证书 如何使用 KubeBuilder 开发一个 Operator Kubernetes 1.6.0 安装问题汇总 镜像管理工具 -- Harbor 开发 Tips(15) Docker 如何拉取镜像 开发 Tips(14) 使用 Helm 安装 harbor 开发 Tips(13) 使用 S2I 构建云原生应用 在 Kubernetes 中使用 emptyDir、hostPath、localVolume 开发 Tips(12) 开发 Tips(11) 代码质量分析工具 SonarQube 使用 Kubeadm 安装 Kubernetes 集群 一起来学 Go --(4)常用函数 Kubernetes 中的 Ceph Kubernetes 之 Volumes Kubernetes 之 Labels、Selectors 开发 Tips(10) 开源正在重构商业模式 Kubernetes 之网络 Kubernetes 之 API 使用 Helm 和 Operator 快速部署 Prometheus Kubernetes 复杂有状态应用管理框架 -- Operator Kubernetes 的包管理器 -- Helm 一起来学 Go --(3)Go Modules 如何一步一步地优化博客方案 kubectl 实用指南 Kubernetes 中的基本概念 搭建远程 Kubernetes 开发环境 大公司和小公司的 ToB 思路 开发 Tips(9) Go 入门指南 一起来学 Go --(2)数据与逻辑结构 如何预防 Web 富文本中的 XSS 攻击 django-xss-cleaner 云工作时代 一起来学 Go --(1)背景与特点 SaaS 开发团队的不同阶段 你不知道的 Git 使用技巧 输出既服务 微服务设计 继续奔跑 开发 Tips(8) 从账户安全到二次验证 Django 性能之数据库查询优化 Django 性能之分库分表 敏捷开发之研发流程 打造一致性的团队 开发 Tips(7) Pytest 进阶学习之 Mock PaaS 部署之 buildpack Go 开发配置 领域输出才是 PaaS 的核心竞争力 Pytest 入门学习 开发 Tips(6) 如何使用 Jenkins、Docker、GitLab 搭建 Django 自动化部署流程
Django 全栈优化指南
微信公众号 · 2018-11-15 · via 陈少文的网站

由于数据量剧增,系统响应很慢。对应用系统进行了一系列的优化工作,系统响应时间得到了数量级级别的优化效果。总体看,在压缩文件、加快网络访问方面的优化,对前端性能有显著提升效果。在存储过程、缓存、逻辑代码方面的优化,对后端性能提升有显著效果。本文整理了优化思路和方法。

1. 梳理链路

在优化之前,梳理整个链路尤为重要。

优化是一个系统工程,不能经过简单地增减就取得很好的效果。同时,并不是我们故意将系统设计得慢,而是设计系统的前提条件发生了变化,需要审视整个系统。优化就是要找出这些变化,并让系统适应这种变化。

下图,是对整个系统链路的梳理:一个接入层,一个逻辑层,一个存储层。

2. 项目背景和优化思路

这是一个商城 + 社区论坛的 Web 应用,还有小程序端。不仅要支撑商品的发布、更新、交易、日常运营,还需要支撑用户社区问答、关注点赞等社交功能。由于某些历史原因,系统的数据表多达 142 张。

一句话描述,就是大而杂。同时,开发人员只有一个前端,一个后端。

项目采用前后端分离的模式进行开发,优化时,也是顺着这个思路。前后端分别,先借助一定的分析工具,找到链路上耗时的节点,再进行优化。

下面是一些已经采取或即将采取的优化措施。

3. 前端

3.1 根据 PageSpeed Insights 分析优化

PageSpeed Insights 是由 Google 开发的网站分析和优化工具。有网页版,也有 Google Chrome 插件版

安装插件版之后,通过 F12 打开开发者工具,点击 【ANALYZE】。将会看到 PageSpeed 对页面的打分和修改意见。

点击每一项意见,会显示更详细的内容。PageSpeed Insight 的分析包含以下几个方面:

  • 优化缓存——让你应用的数据和逻辑完全避免使用网络
  • 减少回应时间——减少一连串请求-响应周期的数量
  • 减小请求大小——减少上传大小
  • 减小有效负荷大小——减小响应、下载和缓存页面的大小
  • 优化浏览器渲染——改善浏览器的页面布局

3.2 CDN 托管静态文件

CDN 技术主要优势:

  • 访问速度更快
    用户连接的是距离最近的 CDN 服务器,来获得内容,在访问速度上会得到显著提升。

  • 可用性
    在用户流量过高、间歇性高峰和潜在服务器故障等高压力情况下,CDN 依然能保障用户能获取到内容。

  • 安全性
    CDN 服务能够有效缓解各种攻击行为。

将前端静态文件托管到 CDN ,是对前端优化很重要的一个环节。当然,这种改造需要部署系统支持。

3.3 根据页面拆分前端静态文件

拆分前端打包的静态文件,是为了用户访问应用时,按需加载。而不必在首次加载页面时,请求全部文件。前端使用的是 webpack 3.6.0,在 webpackConfig 的 plugins 属性中新增如下内容即可:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
(new webpack.optimize.ModuleConcatenationPlugin(),
  new webpack.optimize.CommonsChunkPlugin({
    name: "vendors",
    minChunks(module) {
      return (
        module.resource &&
        /\.js$/.test(module.resource) &&
        module.resource.indexOf(path.join(__dirname, "../node_modules")) === 0
      );
    },
  }),
  new webpack.optimize.CommonsChunkPlugin({
    name: "manifest",
    minChunks: Infinity,
  }),
  new webpack.optimize.CommonsChunkPlugin({
    name: "app",
    async: "vendor-async",
    children: true,
    minChunks: 3,
  }));

3.4 减少不必要的接口调用

由于前端开发人员不稳定,交接频繁,实现逻辑上存在不一致。同时,前端代码质量参差不齐,代码实现上不易于扩展和维护。主要存在以下情况:

  • 在公共模块中已经调用的接口,路由子页面中重复调用
  • 只属于某一个路由子页面的接口,每个页面都在调用
  • 页面中同一接口,调用多次

这部分主要需要前端开发 Review 代码,在代码逻辑层进行优化。

4. 后端

本节分为两个部分,前面介绍几种性能分析工具,在分析数据的基础上,后半节主要介绍如何进行优化。

4.1 django-debug-toolbar 分析时间消耗

django-debug-toolbar 是一个不错的 Django 性能检测工具。django-debug-toolbar 主要提供以下几个方面的性能检测:

  • 执行了多少条 SQL 语句
  • 有多少时间花费在数据库上
  • 执行了什么特殊的查询操作,每次查询花费多长时间
  • 这些查询是有什么代码生成的
  • 渲染页面都用到了哪些模板
  • 冷/热缓存是如果影响性能的
  1. 安装
1
pip install django-debug-toolbar
  1. 配置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
INSTALLED_APPS = [
    # ...
    'django.contrib.staticfiles',
    # ...
    'debug_toolbar',
]
MIDDLEWARE = [
    # ...
    'debug_toolbar.middleware.DebugToolbarMiddleware',
    # ...
]

url.py

1
2
3
4
5
6
7
8
from django.conf import settings
from django.conf.urls import include, url

if settings.DEBUG:
    import debug_toolbar
    urlpatterns = [
        url(r'^__debug__/', include(debug_toolbar.urls)),
    ] + urlpatterns

配置成功之后,就可以看到相关性能的检查数据:

4.2 django-debug-panel 分析 Ajax

django-debug-panel 在 django-debug-toolbar 的基础上,提供了更好地对单页面应用和 Ajax 请求的支持。

  1. 安装
1
pip install django-debug-panel
  1. 配置

settings.py

1
2
3
4
INSTALLED_APPS = (
    # ...
    'debug_panel',
)

使用 panel 的中间件,替换 toolbar 的中间件。

middlewares.py

1
2
3
4
5
6
MIDDLEWARE_CLASSES = (
    ...
    # 'debug_toolbar.middleware.DebugToolbarMiddleware',
    'debug_panel.middleware.DebugPanelMiddleware',
    ...
)

安装 Chrome 扩展:Django Debug Panel

配置成功之后,就可以看到相关性能的检查数据:

4.3 django-debug-toolbar-requests 分析 requests 请求

通常,我们会使用 requests 库,请求一些第三方的接口。这些第三方接口的性能也需要被关注。django-debug-toolbar-requests 在 django-debug-toolbar 的基础上,提供了对 requests 请求的支持。

  1. 安装
1
pip install django-debug-toolbar-requests
  1. 配置
1
2
3
4
INSTALLED_APPS =
    # ...
    'requests_toolbar'

DEBUG_TOOLBAR_PANELS 中添加 RequestsDebugPanel

1
2
3
4
DEBUG_TOOLBAR_PANELS = (
    # ...
    'requests_toolbar.panels.RequestsDebugPanel',
)

配置成功之后,就可以看到相关性能的检查数据:

4.4 Django 数据库优化

  • 给搜索频率高的字段加索引

在 Django 中定义 Model 时,可以通过 db_index=True 给字段添加索引:

1
2
3
class Country(models.Model):
    name = models.CharField(unique=True, db_index=True, max_length=50)
    short_name = models.CharField(unique=True, max_length=5)

给关键的字段添加索引,性能会得到很大提升,特别是数据量大、查询操作多时。可以参考这篇文档

但是索引并不是越多越好,索引可以极大的提高数据的查询速度,但是会降低插入、删除、更新表的速度。把握合适的度很重要。

  • 合理利用 QuerySets 缓存

Django 的 QuerySets 有缓存,一旦取出来,会在内存呆上一段时间。看下面的这个例子:

1
2
3
entry = Entry.objects.get(id=1)
entry.blog   # Blog object is retrieved at this point
entry.blog   # cached version, no DB access
1
2
3
entry = Entry.objects.get(id=1)
entry.authors.all()   # query performed
entry.authors.all()   # query performed again

all、count、exists 等函数调用才需要连接数据库,但是属性访问不需要连接数据库。如果是自定义的属性,需要使用 cached_property,添加缓存策略,可以参考这篇文档

  • 利用 iterator() 获取对象,避免 QuerySets 内存消耗
1
2
3
allbooks = Book.objects.filter(author = 'chenshaowen')
for book in allbooks.iterator():
    do_something(book)

QuerySet 会在缓存其结果,以便在重复计算时不会导致额外的查询。而 iterator() 将直接读取结果,不在 QuerySet 级别执行任何缓存。对于返回大量只需要访问一次的对象的 QuerySet,这可以带来更好的性能,显著减少内存使用。

  • 尽量批量操作

批量操作能有效减少数据库连接的次数。

批量插入数据

1
2
3
4
product_list_to_insert = list()
for x in range(10):
    product_list_to_insert.append(Product(name='product name ' + str(x), price=x))
Product.objects.bulk_create(product_list_to_insert)

批量更新数据

1
Product.objects.filter(name__contains='name').update(name='new name')

批量删除数据

1
Product.objects.filter(name__contains='name query').delete()

多对多关系

1
my_band.members.add(me, my_friend)

4.5 合理利用缓存

缓存是优化性能的利器。缓存的思路是利用空间交换时间,避免重复计算。

在 Django 中常用的缓存方式有:

  • 开发调试缓存
  • 内存缓存
  • 文件缓存
  • 数据库缓存
  • Memcache缓存(使用python-memcached模块)
  • Memcache缓存(使用pylibmc模块)

根据不同粒度,又可以将缓存划分得更细:

  • 整站缓存

只需要添加中间件即可:

1
2
3
4
5
    ... += [
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',
    ]
  • 视图层缓存
1
2
3
4
5
from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def my_view(request):
    pass
  • 模板片段缓存
1
2
3
4
{% load cache %}
{% cache 500 sidebar %}
    .. sidebar ..
{% endcache %}
  • 定制缓存

Django 提供的 django.core.cache.caches 允许用户,自行设置、维护缓存。

1
2
3
4
5
>>> from django.core.cache import caches
>>> cache1 = caches['myalias']
>>> cache2 = caches['myalias']
>>> cache1 is cache2
True
  • 线程中的缓存

除了 Django 内置的缓存机制,利用 Python 的动态特性,在线程级别还可以进行更细粒度的缓存:

1
2
3
4
5
6
7
8
9
def get_value(self,cache_key,default=None):
    value=getattr(local,cache_key,None)
    if value:
        return value
    value=cache.get(cache_key,default=None)
    if value is None:
        return default
    setattr(local,cache_key,value)
    return value

将数据缓存在当前线程 local 中,这种优化方式对在一个请求内多次访问相同缓存的场景有比较好的效果。

实际上这是在使用内存缓存。

4.4 代码逻辑优化

  • 正则表达式记得编译
  • 排序尽量使用 .sort()

    • O(nlogn)
    • 使用 key 比 cmp 效率更高
  • 使用列表迭代表达式

1
2
3
4
5
6
def function1(l):
    result = []
    for i in l:
        if i % 2 == 0:
            result.append(i)
return result

使用列表推导比上面快了 36%

1
2
def function2(l):
    return [i for i in l if i % 2 == 0]
  • 减少函数调用,尽量访问局部变量
1
2
3
4
5
6
7
8
def add_two(i):
    return i + 2

def function1(l):
    result = []
    for item in l:
      result.append(chr(add_two(item)))
    return result

减少函数调用之后比上面快了 40%

1
2
3
4
5
6
def function2(l):
    result = []
    lchr = chr
    for item in l:
        result.append(lchr(item + 1))
    return result
  • 使用 set 判断元素是否存在
1
2
3
4
l = range(10000)

def function1():
    return 9000 in l

使用 set 之后比上面快了 70000%

1
2
3
4
s = set(range(10000))

def function2(item):
    return item in s