django 性能调优手记

最近在做一项课程作业,是基于 django 框架的一个抢票系统。项目的特点是逻辑比较简单、并发要求高。要求支持 1000 个用户并发访问,响应时间小于 5 秒,错误率 0。

    1. 首先我们知道,python manage.py runserver 是肯定不行的,测试表明支持并发数小于 10。
    2. 使用 uWSGI + nginx。基本配置下,支持并发数达到 120。
    3. 调大系统 net.core.somaxconn 值。
    4. 调整 uWSGI 配置参数,此时支持并发数达到 380。
        • 以 IPC socket 取代 TCP socket,节约协议栈开支:socket chmod-socket,对应修改 nginx 配置文件中 uwsgi_pass
        • socket 队列大小:listen
        • 进程数:processes,大致和 CPU 核心数匹配
        • 线程数:enable-threads threads
        • 关闭日志:disable-logging
        • Python 解释器优化级别:optimize

      至此,我们的 nginx 配置大致是

      server {
          listen 80;
          server_name wx.nullspace.cn;
          location ~ ^/(wechat|api|admin|accounts){
              include uwsgi_params;
              uwsgi_pass unix:///tmp/wechatticket.sock;
          }
          location / {
              root /home/justin/WeChatTicket-1611/static/;
          }
      }

      uWSGI 配置大致是

      [uwsgi]
      chdir=/home/justin/WeChatTicket-1611/
      module=WeChatTicket.wsgi:application
      master=True
      max-requests=65535
      socket=/tmp/wechatticket.sock
      chmod-socket=666
      optimize=2
      processes=2
      threads=2
      listen=65535
      disable-logging=True
      enable-threads=True
    5. JMeter 显示并发数更高时会出现很多错误,尽管响应时间是比较低的。查看 nginx 的错误日志发现全部是 socket() failed (24: Too many open files)。后来又出现了worker_connections are not enough while connecting to upstream,为此魔改了一批参数:
      1. nginx 配置 worker_rlimit_nofile
      2. nginx 配置 worker_connections
      3. 修改 /etc/security/limits.conf
      4. 编辑 /etc/sysctl.conf 修改 fs.file-max 值
    6.  优化数据库锁操作
      • 检查余量、减库存的操作是需要加锁的,代码片段原先是这样的:
        # 减库存
        with transaction.atomic():
            activity = Activity.objects.select_for_update().get(id=actId)
            remainTickets = activity.remain_tickets
            if remainTickets <= 0:
                return self.reply_text("没票了,你来晚啦 :(")
            activity.remain_tickets = remainTickets - 1
            activity.save()

        reply_text 是阻塞的 I/O 操作,将其挪到 transaction.atomic() 作用域外:

        # 减库存
        with transaction.atomic():
            activity = Activity.objects.select_for_update().get(id=actId)
            remainTickets = activity.remain_tickets
            if remainTickets > 0:
                activity.remain_tickets = remainTickets - 1
                activity.save()
        if remainTickets <= 0:
            return self.reply_text("没票了,你来晚啦 :(")

        由此可以减少数据库锁的持续时长。测试表明:此处仅稍稍调整了语句的顺序,便使平均响应时间下降了 20%。

      • 顺便指出,如果去掉锁,可以再下降 40% ( ̄(工) ̄)
      • 可考虑使用 django.db.models.F 表达式代替数据库存取和锁
    7. 优化数据库索引
      • 给“票”表增加了一个字段的索引,去掉了一个字段的索引
    8. 避免 SELECT *,减少数据库查询字段数
      • django.db.models.QuerySet.only 方法

 

文中的测试数据是在一台单核 CPU、 768MB 内存、运行 Ubuntu 16.04 系统的 VPS 上,利用 JMeter 取得的。

2 thoughts on “django 性能调优手记”

发表评论

电子邮件地址不会被公开。 必填项已用*标注