第一章:The Big Picture

作者开篇就提到大家总说 Django 性能不行,但是实际上 有很多高性能的站点是使用 Django 开发的。

Django’s scaling success stories are almost too numerous to list at this point. It backs Disqus, Instagram, and Pinterest. Want some more proof? Instagram was able to sustain over 30 million users on Django with only 3 engineers (2 of which had no back-end development experience). Disqus serves over 8 billion page views per month. Those are some huge numbers. These teams have proven Django most certainly does scale. Our experience here at Lincoln Loop backs it up. We’ve built big Django sites capable of spending the day on the Reddit homepage without breaking a sweat.

在作者的公司,他们开发高性能 Django 站点的准则就是 simplicity :

  • Using as few moving parts as possible to make it all work. “Moving parts” may be servers, services or third-party software.
  • Choosing proven and dependable moving parts instead of the new hotness.
  • Using a proven and dependable architecture instead of blazing your own trail.
  • Deflecting traffic away from complex parts and toward fast, scalable, and simple parts .

Simple systems are easier to scale, easier to understand, and easier to develop.

构建高性能 Web 应用通常需要关注一下几点:

  • 数据库。关系型数据库通常是整个技术栈中最慢最复杂的部分,一个办法是改用 NoSQL 数据库,不过 大多数情况下都可以通过缓存解决。
  • 模板。我们可以用一个更快的模板引擎替换 Django 自带的模板引擎,不过即便是这样模板仍旧是 整个技术栈中第二慢的部分。我们仍然可以通过缓存解决这个问题。
  • Python。Python 在通常情况下已经足够快了。我们可以使用 Web 加速器(比如:Varnish)缓存服务器响应, 在请求进入到 Python 那一层之前就返回相应。

这章作者一直在强调缓存,"CACHE ALL THE THINGS"。无论你怎么优化你的技术栈,没有比缓存更快的优化方案。 说到缓存可能大家可能会顾虑缓存过期的问题,作者说了现在先别关心这个问题,之后会给出解决方案。

作者提到一般的网站都保护这几层:负载均衡器,Web 加速器,APP 服务器,缓存,数据库。

顺便提了一个 HTTPS 的负载均衡配置方法:客户端与负载均衡器之间使用 HTTPS,负载均衡器与后端服务之间使用 HTTP。这样既保证了安全又可以减少 HTTPS 对性能的影响。

第二章 The Build

小技巧

  • 本地开发环境应该尽可能的与线上环境一致:相同的数据库,相同的操作系统以及相同的软件版本。。。
  • 组织 settings 文件,创建一个基础的配置文件 settings/base.py ,然后再为开发,测试,部署分别创建一个配置文件,一些重要的配置信息可以通过环境变量获取

    • settings/base.py
    • settings/dev.py
    • settings/deploy.py

这里作者有提到一个小技巧,那就是环境变量的值都是字符串,那么如果将值转换为布尔值,元组甚至字典呢?答案就是可以使用 ast 模块:

    >>> ast.literal_eval('True')
     True

    >>> ast.literal_eval('1, 2, 3')
     (1, 2, 3)

    >>> ast.literal_eval('{"foo": "bar"}')
     {'foo': 'bar'}

小心第三方 APP

在决定使用某个第三方 APP 之前,先回答下面几个问题:

  • 它真的符合你的需求吗?还是只是有点相近?
  • 它是个健康的项目吗?

    • 维护者有一个好的追踪记录吗?
    • 文档写的好吗?
    • 测试覆盖率够吗?
    • 社区怎样(贡献者,pull requests 等等)?
    • 还在处于活跃开发吗?
    • 有很多旧的 issues 和 pull requests 吗?
  • 性能咋样?

    • 它会产生多少数据库查询?
    • 易于缓存吗?
  • 跟你项目的其他部分有冲突吗?
  • 它的授权协议跟你的项目兼容吗?

不再维护以及不稳定的第三方应用很快就会成为你的项目的负债。 可以尝试阅读源代码,然后从中找出你的项目需要的代码然后应用到项目中。

找出性能问题

可以使用下面这些工具

  • Django Debug Toolbar
  • django-debug-panel
  • django-devserver

观察页面性能:

执行了多少条 SQL 语句?
有多少时间花费在数据库上?
执行了什么特殊的查询操作,每次查询花费多长时间?
这些查询是有什么代码生成的?
渲染页面都用到了哪些模板?
冷/热缓存是如果影响性能的?(提示:可以使用 settings 来切换缓存)

哪里需要优化

数据库优化

  • 减少查询次数

    • 使用 select_related, prefetch_related, (提示:prefetch_related 要放在查询的最后,不然会没有效果。)
  • 减少查询时间

    • 不要忘记加索引(索引也是有代价的,每次对数据库进行写操作都需要更新索引)
    • 某些情况下 join 查询性能很差,在这种情况下两条查询语句比一条 join 耗时更少。
  • 限制结果数

    • 留意 .all() 只取需要的结果数, queryset[:20]
  • count 查询很慢。可以的话,不要使用 count。比如使用 .exists() 代替 count 进行判断记录是否存在。
  • generic 外键。generic 外键是个很 cool 的功能,但是它会生成一些特别复杂的查询,所以可能的话,不要使用它。如果你一定要用的话,记得这是个需要缓存的地方。
  • 优化 model 方法。如果某个 model 方法在一个请求内会多次被调用,可以使用 cache_property 缓存方法解决(缓存只在该请求内有效)
from django.utils.functional import cached_property

class TheModel(models.Model):

    @cached_property
    def expensive(self):
       # ...
       return result
  • 结果太大了,包含了不需要的字段。使用 defer, only, values, values_list 限制结果大小:
posts = Post.objects.all().defer('body')
posts = Post.objects.all().only('title')
posts = Post.objects.all().values('id')
posts = Post.objects.all().values_list('id')
posts = Post.objects.all().values_list('id', flat=True)
  • 缓存查询结果。这里提到两个库: Johnny Cache 和 Cache Machine 这两个库的原理都是在 ORM 和数据库中间加了一个缓存层,将 ORM 生成的 SQL 作为 key 来缓存查询结果。
  • 只读 replicas。对那些读远远大于写的站点,可以考虑从 只读 replicas 中读取数据,实现读写分离。减少主库的负担优化性能。
  • raw 查询。如果感觉 ORM 有点慢话,可以考虑使用 raw 方法执行原生的 SQL 语句。
  • 反范式。这种方法有个问题就是每次更新的时候都需要同时更新其他表中相关的冗余字段。
  • 使用其他数据存储方式。比如: Postgres, redis, mongodb,使用 Elasticsearch 进行全文检索等。 需要注意的是,在生产环境下新增一个服务并无法没有代价的。作为开发者我们可以不太在意这个,但是系统需要 支持,配置,监控,备份等。新增服务的时候要考虑到这些代价以及你的系统管理员的意见。
  • sharding。99.9% 的网站都不需要用到数据库的 sharding 功能,所以只有在你确信你遇到了那 0.1% 的时候 再使用 sharding 功能。

模板优化

应该缓存模板中一切可以缓存的东西。

  • 俄罗斯套娃式缓存。在一个模板里缓存嵌套缓存,就像俄罗斯套娃一样,一层套一层。比如:
{% cache MIDDLE_TTL "post_list" request.GET.page %}
    {% include "inc/post/header.html" %}
    <div class="post-list">
    {% for post in post_list %}
        {% cache LONG_TTL "post_teaser_" post.id post.last_modified %}
            {% include "inc/post/teaser.html" %}
        {% endcache %}
    {% endfor %}
    </div>
{% endcache %}
  • 自定义一个支持通过 url 参数刷新模板缓存的 cache 标签,这样就可以随时刷新缓存了

随后处理耗时的任务

可以把耗时的,不需要同步知道结果的任务放到类似 celery 的任务队列中异步执行, 从而减少请求——响应的处理时间。下面这些任务可以考虑放到 celery 中:

  • 第三方 API 的调用
  • 发邮件
  • 非常复杂的计算(视频处理,大量的数字处理等)

对于使用 celery 作者提到了一下小提示:

  • 不要将 model 实例作为任务的参数,可以改用传主键的方式。因为在这期间 model 的数据可能已经发生改变了, 还有就是那个 model 实例可能不支持序列化。
  • 保持任务小,不要再一个任务中执行太多的工作。把一个任务分割成多个任务,一方面可以使用多核或多 worker 的 方式加速任务执行,另一方面,单个任务可以很快的执行完方便安全快速的重启 worker,因为一个 worker 重启时 会等待正在执行的问题完成,保持任务小巧的话,可以加快部署时间。
  • 可以考虑使用 celery 的 beat 功能去自习定时任务。

前段优化

  • 压缩 css 和 javascript(min, gzip, 版本化静态文件)。(个人建议:版本化文件应该类似这样 foo-xxyy.js 而不是这样 foo.js?v=xxyy ,主要是方便使用 CDN,防止出现缓存未过期的情况。)
  • 压缩图片。
  • 使用 CDN 服务静态文件。

文件上传

可以考虑使用分布式文件系统或者云存储来存储上传的文件。使用云存储的时候要考虑备用方案,万一服务不可用怎么办。

测试

好的测试用例是健康代码的强有力的基石。测试应该覆盖到你代码中最复杂,最重要,最容易出问题的地方。

自动化测试以及持续集成

一个持续集成系统可以让开发者在开发进度的早期就发现问题,通过持续集成系统来执行 自动化测试以及检查你的代码的健康度。作者提到了他们的检查点:

  • 单元测试
  • 代码覆盖率
  • PEP8/linting
  • 使用 Selenium 进行功能测试
  • 所以 Jmeter 进行性能测试

第三章:The Deployment

先决条件

操作系统

作者推荐使用 Ubuntu。同时作者给出了选择其他操作系统时,需要考虑的事情:

  • 能够很容易的就使用 Python 2.7+ 。有些操作系统要安装 2.7+ 版本的 Python 非常的麻烦,如果是这样的话你就要慎重考虑了。
  • 有长久的安全更新支持。

配置管理

  • Chef, Puppet, Ansible, Salt 都是比较好的配置管理工具
  • Fabric 不是配置管理工具,如果你把它当作配置管理工具的话,会有你头疼的时候。你可以在 Fabric 的基础上 构建你自己的配置管理工具。

进程管理

  • 系统默认的工具:upstart, systemd
  • 第三方软件:deamontools, supervisord, circus

更新代码

更新代码一般需要进行下面几步:

  1. 从版本控制服务器上拉取最新的代码
  2. 更新依赖
  3. 合并数据库更改(migrate)
  4. 收集,压缩,推送静态文件到 CDN
  5. reload WSGI 服务器
  6. 重启后台 workers

推荐写个脚本自动执行这些操作,这样不容易出错。如果要更新多台服务器的话,可以使用远程执行 框架来做这种事情,比如: Salt, Fabric。

有一点特别要注意的是,一旦服务上线就应该尽可能的使用平滑 reload 的方式来代替重启进程的暴力方式。

多个远程环境

至少要有两个环境吧:打包/开发环境,生产环境。 同时多个环境尽量保证一致性,尤其是多个生产环境之间(设置,软件,系统,等等)。 不过有些东西还行要区分的,比如:

第三方服务配置。比如,你肯定不希望在开发环境下触发支付操作或发送文件到生产环境下的 CDN 上。
获取数据的问题。经常看到某些人在开发环境下使用线上数据库的副本,但是这里有几个问题要考虑:
你的开发环境更生产环境一样安全吗?监控松散的开发机器是黑帽黑客经常会攻击的目标。
有可能会从你的应用中发送邮件或通知吗?从你的开发环境下向你的用户发送数千封邮件不仅是非常 尴尬的事情,同时也会影响你们的商业。

避免单点故障

要经常备份,确保你知道所有存储的数据(数据库,用户上传的数据,等等)都有备份。这样出现故障的时候 其他其他机器的时候丢失的数据会少一点。

高可用是一个可以考虑的方法,如果对你的商业来说他是非常重要的话。 HA 可以保证在出现服务挂掉 的情况下能够无缝自动切换到备用服务或者说不用手动切换。不过需要注意的是通常构建高可用 的花费比服务当掉的花费还高。

在考虑架构的时候要考虑到如何解决单点故障的问题。举个例子,当使用第三方的 Amazon EC2 的时候, 你是否能够接受某些区域的设备宕机,整个地区呢?如果服务商 Amazone 当了怎么办? 越早考虑这些问题就能在灾难实际发生的时候更好的应对。

服务器布局

  • 负载均衡器: 可以使用云服务商提供的负载均衡服务或者使用 nginx, Haproxy。对于负载均衡器网络 带宽是个非常重要的注意点。
  • Web 加速器:网络带宽和内存是值得关注的点
  • 应用:CPU 和内存值得关注。
  • 后台运行的 workers:后台运行的任务通常都是 CPU 密集型的任务并且运行在独立的服务器上。一个服务器上可以运行 多个 workers。
  • 缓存:你的缓存服务器需要更多的内存。另一个需要注意就是网络带宽,它可能会在内存之前成为瓶颈。如果网络开始 拥堵的时候, Django 可以配置多个缓存服务器。
  • 数据库:足够的内存非常重要,最用是足够把你的数据都装载在内存里。 如果你预期会有 64GB 的数据,那么至少要有 64GB 的内存。 磁盘速度也非常重要。购买你能够负担得起的最快的磁盘。 如果你用的是虚拟机的话,你需要留意你其实是在跟你的邻居共享一个物理磁盘,常规的实践是尽可能的买最大的虚拟机。

优化技术栈

优化数据库

优化 uWSGI

优化 Django

CACHES

如果你使用 Memcached,使用 pylibmc 这个库会有更高的性能。 redis 的话可以使用 django-redis 。

缓存过期是第一个需要面临的问题,一个缓存 key 过期或者被刷新都有可能摧毁你的数据库。 幸运的是有个简单包可以解决这个问题: django-newcache

还有一个问题就是如果缓存服务器宕机了会到期用户收到 “500 Server Error”的响应。 将使用一个缓存服务器改为三个可以降低出错的几率。 你需要考虑当缓存服务器宕机的时候,你是需要你的网站也跟着宕机,还是希望你的应用继续 运行良好只是把它当作缓存未命中来处理?作者创建了一个 django-ft-cache 包用来 解决这个问题,它会将任何的 memcached 操作用 try/except 包裹,捕获这里的异常, 这样缓存服务器当掉时请求依然可以被正确处理。

SESSION_ENGINE

把 session 保存在数据库中非常影响性能,一个好的办法是保存在缓存中。如果用 redis 的话 可以使用 cache backend, 用 memcached 的话 cached_db backend 也还行。 另一个办法就是使用 signed_cookies backend, 让客户端存储 session 数据。

DATABASES

可以通过 DATABASES 设置增加 CNN_MAX_AG key 选项来定义重用数据库连接。 比如 300, 这将告诉 Django 保持打开和重用数据库连接 5 分钟。

LOGGING

不要定义 LOGGING 将日志保存到文件中,而是应该输出到 STDERR ,让进程管理器来处理日志。

MIDDLEWARE_CLASSES

不要轻易自定义中间件,因为中间件会在每个请求中执行。所以确保你知道每个中间件都做了什么, 以及尽可能的不要在中间件中执行数据库查询操作。

常规安全问题

需要注意 clickjacking 和 XSS(Cross Site Scripting)。最简单的方法是使用 django-secure 项目来检查安全问题。 另一个安全问题是,确保你的 admin 后台处于保护中,如果你把它开放出去了,确保使用了非常 复杂的管理员密码。最好是把它变成一个内网服务,让它不能从外部互联网访问。

配置你的服务器

安全

  • 调整 SSH 配置:禁用 root 登录,禁用密码登录,更改默认端口。
  • 应用安全补丁:关注一下系统安全问题,及时更新 zer-day 补丁(比如,Hearbleed)
  • 使用私有网络:大多数云服务器都提供私有网络服务,只允许访问你帐号下的服务。在私有网络 中访问你的服务器可以加大被人攻击你的难度。
  • 保护内网服务:内网服务包括控制台,开发服务器,持续集成系统。它们会成为你安全网络的一道暗门。 将它们用 VPN 或认证代理锁起来,如果你没有使用 VPN 的话,确保传输数据以及登录时总是使用 SSL/HTTPS。 锁住你的开发环境可以确保 google 不会抓取它从而伤害你的 SEO)
  • 防火墙:只允许指定端口和 IP 访问你的服务器。硬件防火墙很棒,软件防火墙比如 iptables 也不错。
  • 不要在 root 下运行:不在 root 下运行可以防止某些人运用 RCE(remote code execution)获取你服务器的 root 访问权限。使用标准用户登录服务器,只在必要时使用 sudo。
  • 保护你的第三方服务的账户:使用强壮的密码,尽可能的开启两步验证。

备份

对于数据库,有一个运行的 replica 可以很方便的执行全备份。推荐在半夜执行全备份。 进行备份的时候有几个问题需要问一下你自己:

  • 如果有人黑进了你的服务器,他们能够删掉或破坏你的备份吗? 基于这个原因拉取到备份服务器比推送要更好。
  • 如果有人拿到了你的备份会有多糟糕?加密备份文件并且确保黑客没法在同一个服务器上找到解密的方法(注:比如可以使用公钥进行加密,同时服务器上不要存有私钥,这样就不会被黑客在服务器上找到解密的方法)。
  • 你有测试过备份是否可用吗?定期测试你的备份,验证它们是否真的有效。

监控

没有监控的话,线上网站就会成为一个大黑盒。你没法知道实际情况是怎样的,也就没法进行性能优化了。

instrumentation

对于应用,你需要知道下面这个问题:

  • 我的系统中最慢的部分是什么?
  • Django 处理响应的平均耗时是多少?
  • 哪个视图是最慢的?或者花费最多的时间?
  • 哪个数据库查询是最慢的?或者花费最多的时间?
  • 这些数据一段时间内是如何变化的?

NewRelic 是个探测这些问题的比较好的服务,它可以很方便的安装。 然而, NewRelic 是闭源的,专有的系统,同时也非常的贵。有一些开源产品可以替代它。 比如:Graphite

服务器资源

关于服务器资源需要监控如下数据:

  • 平均负载
  • CPU 负载
  • 物理内存使用情况
  • 磁盘相关数据
  • 网络 I/O

告警

下列情况需要发送告警:

超过 X% 的请求出现错误
服务器宕机
服务器资源占用过高:负载,虚拟内存,磁盘等等
服务未响应

日志

需要收集如下日志:

  • 从你的负载均衡器都应用的 Apache 风格的日志
  • 任何应用内的日志
  • 相关服务的 syslog 日志
  • 数据库慢查询日志,以及在不会导致数据库连接的 I/O 问题(尤其是磁盘或网络)的前提下收集所有数据库查询的日志。

错误汇报

在生成环境下 Django 默认会在出现异常时给网站管理员发送异常邮件。这个功能有时也会导致一些问题:

  • Email 不利于追踪错误
  • 如果你的错误发生在一个高访问的页面的话,你实际上是在 DoS 你的邮件服务器, 可能导致被加入黑名单,或者你的邮件服务商会关掉你的服务(他们不想在几秒内发送超过 10K 的邮件)。

幸运的是,更好的错误汇报方式已经存在了,开源的 Sentry 项目是个非常好的解决方案。 Sentry 并不会发送 10K 的邮件,它之后在第一次出现错误时邮件通知你,之后收集并在 Web 页面上 暂时其他时候的错误用于分析问题。

还有一个关于错误的主题就是确保有一个漂亮的不引来应用服务的 500.html 文件,并且已经在服务器上配置好了出错是使用 这个页面。

第四章:预备

使用 Jmeter 进行负载测试

这一部分主要讲解了 Jmeter 的各种是否方法及配置。

启动计划

启动的时候有些东西需要考虑

  • 使用负载均衡器在新的和旧的之间分流
  • 使用“dark launch”,这样用户就不会感觉到他们访问的是新的服务器
  • 使用代理功能分一部分流量到新的服务器
  • 使用特性标志来发布一个新的特性。

发送流量到一个新的没有缓存的服务器可能会导致临时的高负载 从而在缓存热和起来前击垮你的服务器。预热你的缓存可以解决这个问题。一个比较简单的办法是使用一个脚本在有真实请求前去抓取你的网站上的热门 URLs。

不要在一天的最后时间段或周五的时候升启动,除非你想让你的 整个团队在晚上或周末加班。 你应该在大家都在并且有几个小时或几天的时间来处理 启动过程中出现的问题时候启动,同时也要确保你的成员有时间休息。 如果你的网站访问量比较高的话,尝试在访问量比较低的时间段进行升级操作。

启动前的检查事项

Django 配置项

  • DEBUGTEMPLATE_DEBUG 都设为 False
  • SECRET_KEY 是个非常大的随机字符串并且保密
  • ALLOWED_HOSTS 包含了访问者可能会使用的有效域名
  • TEMPLATE_LOADERS: Cached template loader 已启用
  • SESSION_ENGINE 比默认设置更快
  • CACHES: 使用 Memcached 或 Redis 后端
  • MEDIA_ROOTMEDIA_URL 接受并显示文件上传
  • 管理员账户被限制并且有一个强壮的密码

部署

  • 通过点击各种页面和链接的方式来确认网站是否按预期的结果 工作(没有挂掉的图片和链接)
  • Django 日志是否正常工作
  • 监控平台是否接收到数据。确保你能看到整个技术栈中各级 的错误信息。
  • 错误被汇报并且触发了通知
  • 第三方服务能够接收到数据(支付,分析等等)
  • 从你的应用服务和 Celery workers 中发出邮件的功能 能够正常运行
  • 自定义的错误页面(400,500)已经在各个级别中被设置(负载均衡器,web 加速器,Django)
  • Django admin 没法通过 /admin/ 公开访问
  • SSL 证书有效并且设置是安全的。
  • Django-secure 的 manage.py checksecure 运行起来没有错误输出

基础设施

  • 服务器和服务是安全的,已经锁好了大门
  • 有个简单,正式的程序用来部署新的代码
  • 你有一个可以在需要的时候快速水平扩展服务的计划
  • DNS TTL 可以被修改为 5 分钟或更短的时间在需要更改的时候

第五章 The Launch

监控整个 Launch

服务器资源

可以使用如下工具参考服务器资源使用情况

  • htop
  • varnishstat
  • varnishhist
  • varnishtop
  • varnishlop
  • uwsgitop
  • celery inspect
  • celery events
  • flower
  • memcache-top
  • pg_top
  • pg_stat_statements
  • pt-query-digest
  • mytop

当灾难来临的时候

应用服务器过载

最简单的办法就是通过增加服务器的方式进行水平扩展。 不过你需要注意这将导致你的数据库承受更大的压力, 可能会把数据库搞挂。

当你通过增加服务器的方式把负载降低到可以接受的级别后, 你就需要使用你的低级别的工具来查看为什么会出现负载过高的 情况。你的 web 加速器端缓存命中率过低是一个需要考虑的因素。

数据库服务器过载

如果你的网站是 读多写少的话,可以通过增加 replica 的方式来简单解决响应时间问题。 同时也看看是否在数据库优化时遗漏某些可以优化的项。

应用和数据库服务器过载

你可以从自底向上优化你的数据库,减轻数据库的压力可以让你的应用拥有更高的性能。 你也可以通过优化你的 web 加速器从而减轻应用服务器的压力,进而减轻数据库服务器的压力。

前方的路

恭喜你的网站已经启动了!现在你需要确保它能够持续稳定的运行。在这个战争中你需要与下列事物做斗争:

  • 你的用户(流量增长)
  • 你的软件(一点点腐烂)
  • 你(错误的决定)

第一个没啥好惊讶的,最后两个可能会让你惊讶同时也是让你网站宕机的一些因素。

流量增长

正常情况下你的网站不应该在技术栈的任何层次占用 100% 的资源,一旦出现超过 70%(CPU,内存,磁盘等等) 的资源占用,那就说明某些地方需要优化了或者增加更多的资源。当流量突增的时候如果你有额外的资源的话就可以很好 的应对。

有些时候的流量激增可能是商业上进行了某些吸引用户的活动,确保开发者知道这些商业活动,以便应对激增的流量。 一个好的主意是像刚启动时那样,大家在桌子上一起讨论分享这些商业信息。

一点点腐烂

由于高性能网站使用了很多不同的服务,所以需要保持这些服务打上了最新的安全补丁。 “如果没出现问题,就不要去修复它”是个非常危险的准则。

跳过几个小版本是没什么问题的,但是不要落下太远,不然的话到时就会失去升级动力。 定期小步升级你的依赖(你的操作系统,主要的服务以及 Python 库)。

作者的小贴士:

当 review 升级的时候,我们通常不会使用最新的版本,一般最新版本也意味着有新的 bug ,我想 你不希望当小白鼠吧。给新版本几个月的成熟期。 升级到第一个稳定的版本或者你已经确保这个版本没什么大的问题了。

错误的决定

意外的刷新了缓存

在流量特别高的时候重启缓存或 web 加速器可能会击垮你的网站。这个问题叫做 dog-piling/cache stampede 所以尽量使用 reload 的方式来更新服务。对于使用 memcached/redis 的缓存可以通过给不同的缓存定义不同的 KEY_PREFIX 的方式来实现逐个清理缓存(通过 VERSION)而不是一下子删除所有缓存的功能。 如果一定要重启的话,进行选择在流量低的时候重启服务。

数据库锁表

数据库锁会临时阻塞主写操作,如果这个操作特别长的话就会成为一个问题。两个比较常见的长时间锁表的场景是: schema migration 和备份。

在开发的时候, South 和内置的 migration 可以很方便的应用 Model 更改, 但是在生成环境下, migrate 操作可能会导致长时间的锁表现象,这个对你的用户来说就是个糟糕的消息了。 对于 ADD COLUMN 操作,如果你使用的是 MySQL 数据库的话你需要留意一下这个,但是 PostgreSQL 很少会出现这个问题。

无论你使用哪种数据库,migrations 都需要被 review 并且使用最近的线上数据的副本数据测试过后才能在生产环境使用。

备份是另一个会导致长时间锁住数据库的操作。最好是在一个只读的 replica 上进行备份操作。

大量的缓存过期

跟刷新缓存差不多糟糕的事情就是有大量的缓存过期,这将导致数据库压力倍增。 如果你不是很确定的话,应该选择在流量比较低的时候进行部署操作,避免击垮你的站点。

昂贵的 admin 视图

构建一个未优化的 admin 视图会导致上千的数据库查询。 如果你正在使用一个查询缓存比如 johnny-cache, 每一次在 admin 中保存都将 导致与所在表相关的缓存都将失效。所以 admin 视图也需要像普通视图那样进行优化。

昂贵的后台任务

未优化,数据库依赖的任务会导致非预期的负载负担。所有应该像优化视图一样优化你的后台任务。

逐渐恶化

随着新功能的增加,网站的性能也在接受不同的考验,你需要随时关注性能问题。

每次发布新版本的时候都关注一下性能的变化,如果响应时间变长了或者负载变高了, 立马去解决它。这样就不会出现在几个月的提交过去后再去查找问题所在的情况。

complexity creep

如果你按照前面所说的步骤做了的话,你已经做的非常好了。 随着你的网站的成长你可能会遇到各种各样的新问题,你可以很容易的就构建自己 独有的解决方法。 构建你自己的工具是件很有乐趣的事情,但是 not invented here 对于长时间运行 的站点是非常危险的。你最好去学习如何让 Varnish 更高效而不是丢弃它转而使用你自己的工具。 这个决定衍生的代价是非常巨大的:

  • 训练新的开发者需要更高的花费。你可以找到一名拥有使用一个比如 Varnish 这种服务经验的开发者, 但是如果使用你自定义的方案的话,你需要训练每一位在门外徘徊的开发者。
  • 开发低级别的基础设施工具将会导致开发时间远离了你的核心产品。使用一个支持良好的开源服务, 你的团队的开发者的效率会很高效。
  • 写你自己的软件代码不是一锤子买卖。所有的软件都需要不断的开发,测试,文档,等等。

最后的想法

现在你可以在看看那个老问题“Django doesn't scale”,你是怎么认为的呢? 如果只用 manage.py runserver,使用 SQLite 在一个非常小的云服务器上跑 当然不会很快。

让我们回到 2012 年,当 Instagram 只有3个的 Django 团队却支撑起了 1千4百多万的用户的时候, 他们在博客上是 这么说的 :

我们选择一个系统的核心宗旨是:

  • 保持非常简单
  • 不要重复造轮子
  • 可能的话,使用久经考验的成熟的技术

我们非常同意。因此,在你继续你的 Django 旅程的时候,不要忘了你在这里学到的东西。 简单是指导原则。

注:本文转自黄黄的博客,并修改了文中大量错误。