海北藏族自治州网站建设_网站建设公司_Vue_seo优化
2026/1/14 15:08:18 网站建设 项目流程

测试环境#

本次测试的项目部署在腾讯云2C2G的小水管服务器上,感觉服务器性能严重拖累了应用性能哈哈🤣

而且我还发现一个事情,腾讯云似乎偷偷摸摸在高峰期(如下午)把服务器性能降低,下午和凌晨测试的结果有很大差异。

本次参与测试的wsgi/asgi服务器有 daphne, granian, gunicorn, uwsgi, uvicorn, hypercorn 基本涵盖了 python 生态的大部分服务器了~

本次使用 wrk 作为性能测试功能,所有接口都使用-t4 -c200 -d30s的测试参数。

性能测试结论#

RPS 排名#

排名serverreq/sec接口
🥇 1uWSGI + WSGI1206WSGI
🥈 2Gunicorn + WSGI740WSGI
🥉 3Granian + ASGI220ASGI
4Daphne + ASGI190ASGI
5Uvicorn + ASGI181ASGI
6Hypercorn + ASGI168ASGI

结论:

  • WSGI 整体远快于 ASGI

    这是预期结果,Django 的内部原生就是 WSGI,同步路由不需要额外的 async 转换。

  • uWSGI 再次证明自己是 WSGI 性能之王

    uWSGI 的 C 实现、成熟的 worker 管理、内存利用优化都让它在纯 WSGI 场景无敌。

内存占用对比#

排名server内存
🥇 1uWSGI58M(极低)
2Granian170M
3Daphne175M
4Hypercorn300M
5Gunicorn+WSGI430M
6Uvicorn500M(最高)

结论

  • uWSGI 不仅最快,还最省内存
  • Uvicorn 内存最高(Python 单 worker overhead 明显)
  • ASGI 服务器普遍内存偏高是正常现象

性能测试数据#

以下是详细的性能测试数据。

daphne + asgi#

测试时内存峰值占用175M左右

$ wrk -t4 -c200 -d30s http://127.0.0.1:9875/api/django-starter/monitoring/health Running 30s test @ http://127.0.0.1:9875/api/django-starter/monitoring/health 4 threads and 200 connections Thread Stats Avg Stdev Max +/- Stdev Latency 1.03s 134.59ms 1.99s 89.71% Req/Sec 69.17 64.53 313.00 80.49% 5737 requests in 30.04s, 2.14MB read Socket errors: connect 0, read 0, write 0, timeout 4 Requests/sec: 190.99 Transfer/sec: 72.93KB

granian + asgi#

测试时内存峰值占用170M左右

$ curl http://127.0.0wrk -t4 -c200 -d30s875/api/django-starter/monitoring/health Running 30s test @ http://127.0.0.1:9875/api/django-starter/monitoring/health 4 threads and 200 connections Thread Stats Avg Stdev Max +/- Stdev Latency 886.48ms 79.27ms 1.26s 85.40% Req/Sec 86.07 112.58 490.00 84.05% 6637 requests in 30.04s, 2.72MB read Requests/sec: 220.93 Transfer/sec: 92.56KB

gunicorn + wsgi#

测试时内存峰值占用430M左右

$ wrk -t4 -c200 -d30s http://127.0.0.1:9875/api/django-starter/monitoring/health Running 30s test @ http://127.0.0.1:9875/api/django-starter/monitoring/health 4 threads and 200 connections Thread Stats Avg Stdev Max +/- Stdev Latency 268.67ms 31.82ms 425.23ms 86.13% Req/Sec 187.47 119.54 494.00 56.66% 22244 requests in 30.03s, 9.52MB read Requests/sec: 740.60 Transfer/sec: 324.74KB

uwsgi + wsgi#

测试时内存峰值占用58M左右

$ wrk -t4 -c200 -d30s http://127.0.0.1:9875/api/django-starter/monitoring/health Running 30s test @ http://127.0.0.1:9875/api/django-starter/monitoring/health 4 threads and 200 connections Thread Stats Avg Stdev Max +/- Stdev Latency 199.52ms 277.85ms 1.97s 87.56% Req/Sec 302.89 88.84 575.00 67.33% 36210 requests in 30.02s, 12.95MB read Socket errors: connect 0, read 36216, write 0, timeout 96 Requests/sec: 1206.08 Transfer/sec: 441.68KB

uvicorn + asgi#

测试时内存峰值占用500M左右

$ wrk -t4 -c200 -d30s http://127.0.0.1:9875/api/django-starter/monitoring/health Running 30s test @ http://127.0.0.1:9875/api/django-starter/monitoring/health 4 threads and 200 connections Thread Stats Avg Stdev Max +/- Stdev Latency 1.00s 537.23ms 2.00s 59.81% Req/Sec 62.04 66.05 460.00 83.03% 5440 requests in 30.04s, 2.23MB read Socket errors: connect 0, read 0, write 0, timeout 369 Requests/sec: 181.07 Transfer/sec: 76.05KB

hypercorn + asgi#

测试时内存峰值占用300M左右

$ wrk -t4 -c200 -d30s http://127.0.0.1:9875/api/django-starter/monitoring/health Running 30s test @ http://127.0.0.1:9875/api/django-starter/monitoring/health 4 threads and 200 connections Thread Stats Avg Stdev Max +/- Stdev Latency 734.64ms 423.05ms 2.00s 51.70% Req/Sec 55.53 52.50 360.00 83.49% 5064 requests in 30.06s, 2.09MB read Socket errors: connect 0, read 0, write 0, timeout 1124 Requests/sec: 168.45 Transfer/sec: 71.24KB

更细化的性能测试#

访问一个有数据库查询的接口 http://127.0.0.1:9878/api/demo/movie/movie

直接 uwsgi

$ wrk -t4 -c200 -d30s http://127.0.0.1:9875/api/demo/movie/movie Running 30s test @ http://127.0.0.1:9875/api/demo/movie/movie 4 threads and 200 connections Thread Stats Avg Stdev Max +/- Stdev Latency 501.93ms 260.82ms 1.99s 94.20% Req/Sec 61.02 39.11 191.00 62.02% 6726 requests in 30.04s, 15.84MB read Socket errors: connect 0, read 6758, write 0, timeout 45 Requests/sec: 223.89 Transfer/sec: 539.82KB

经过 caddy 反代

$ wrk -t4 -c200 -d30s http://127.0.0.1:9878/api/demo/movie/movie Running 30s test @ http://127.0.0.1:9878/api/demo/movie/movie 4 threads and 200 connections Thread Stats Avg Stdev Max +/- Stdev Latency 562.46ms 166.56ms 1.65s 88.77% Req/Sec 52.66 21.07 150.00 65.63% 6299 requests in 30.04s, 15.06MB read Socket errors: connect 0, read 0, write 0, timeout 208 Non-2xx or 3xx responses: 40 Requests/sec: 209.69 Transfer/sec: 513.30KB

代码#

来介绍下代码的部分。

这次拿来进行性能测试的主要是健康检查和一个简单的数据库访问接口。所有接口都是用 django-ninja 开发的。

数据库访问接口#

涉及到数据库的就是这个接口

查询数据返回+分页功能,非常简单

@router.get('/movie', response=List[MovieOut], url_name='demo/movie/list') @paginate def list_items(request): qs = Movie.objects.all() return qs

健康检查接口#

引入这些package

import asyncio from ninja import Router from django.db import connections from django.db.utils import OperationalError from redis import Redis from redis.exceptions import RedisError from redis import asyncio as aioredis import os import time import platform import subprocess import anyio

原版的接口是纯同步的,还要调用一个系统进程去获取 uptime,这个操作严重拖慢了整个接口的速度,我这次重构成同步和异步两种接口。

首先是最简单的版本,这里面就啥也没有,单纯返回 JSON。

@router.get('health') def simple_health_check(request): """健康检查端点,用于容器健康检查和监控""" response_data = { 'status': 'healthy', 'status_code': 200, } return response_data

异步接口#

先来异步接口。

其实我很少用到 python 的异步功能,可能这也和 python 对异步的支持比较差有关系。

先写两个异步方法,用来检查数据库和Redis连接。

async def check_db_async(): """数据库检查(同步 ORM → 放线程池)""" try: def _check(): for conn in connections.all(): conn.cursor() return True return await anyio.to_thread.run_sync(_check) except OperationalError: return False async def check_redis_async(): """Redis 异步检查(注意:确保关闭客户端以释放连接池)""" try: redis_client = aioredis.Redis( host="redis", port=6379, socket_connect_timeout=1, decode_responses=True, ) try: ok = await redis_client.ping() return bool(ok) finally: # 关闭客户端和连接池,避免连接泄露(在短生命周期的容器/请求里很重要) try: await redis_client.close() except Exception: pass try: await redis_client.connection_pool.disconnect() except Exception: pass except Exception: return False

Django ORM 不支持异步,所以这里用 anyio 这个包来简化一下操作,把同步操作包装一下假装成异步运行。实际上用 asyncio 这个库也能做,不过 anyio 方便一点。

在用 aioredis 时,这里有个坑,我一开始安装了 aioredis 这个包,结果发现和原有的 redis 包冲突了。

查看文档才发现from redis import asyncio as aioredis就完事儿了,redis 包里已经自带了异步支持。

接着实现异步接口

@router.get('health/async') async def health_check_async(request): """Async 模式健康检查(ASGI 优化版)""" # 使用 asyncio.gather 并发运行 DB 和 Redis 检查 db_ok, redis_ok = await asyncio.gather( check_db_async(), check_redis_async(), ) status = "healthy" if db_ok and redis_ok else "unhealthy" status_code = 200 if status == "healthy" else 503 return { "status": status, "status_code": status_code, "checks": { "database": "ok" if db_ok else "error", "redis": "ok" if redis_ok else "error", }, "system": get_system_info(), }

同步接口#

同步的就简单了

@router.get('health/sync') def health_check_sync(request): """同步版本的健康检查接口""" # --- 检查数据库 & Redis --- db_ok = check_db_sync() redis_ok = check_redis_sync() # --- 系统信息 --- system_info = { "timestamp": time.time(), "uptime": get_uptime(), "hostname": os.environ.get("HOSTNAME", ""), "environment": os.environ.get("ENVIRONMENT", "development"), "os": platform.system(), } # --- 状态码 --- status = "healthy" if db_ok and redis_ok else "unhealthy" status_code = 200 if status == "healthy" else 503 # --- 返回数据 --- return { "status": status, "status_code": status_code, "checks": { "database": "ok" if db_ok else "error", "redis": "ok" if redis_ok else "error", }, "system": system_info, }

小结#

传统应用无脑选 uwsgi 就对了。

需要异步的可以把异步那部分切割出来用 granian 或者 daphne 跑。

当然这也增加了复杂度,但这是对性能优化的最佳权衡。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询