Granian:把 Python ASGI/WSGI 打包成“一键可执行”服务器
Granian是一款基于Rust开发的ASGI/WSGI服务器,专为Python Web应用设计,旨在简化部署流程并提升性能。它通过预编译二进制文件实现极简部署(仅需几MB)、毫秒级冷启动(10-30ms)和原生支持TLS/HTTP/2。相比传统Uvicorn+Gunicorn方案,Granian在容器镜像体积(可小至30MB)、启动速度(<0.03s)和功能集成(内置Prometheus监
适合谁?
你已经在用 FastAPI / Starlette / Django(WSGI)写业务,却觉得部署总是要额外加 Gunicorn + Nginx、镜像太大、启动慢。
你想在 Docker‑scratch、K8s、Serverless 或 边缘设备(IoT、Raspberry Pi)里只放一个二进制文件。
你想要 原生 HTTP/2 + TLS,而不想再跑一层反向代理。
这篇文章从 概念 → 安装 → 基础用法 → 高级特性 → 性能对比 → 实战部署 全面剖析 Granian,帮助你在几分钟内把它写进自己的项目。## 目录
- 什么是 Granian?
- 为什么用 Granian 而不是 “Uvicorn + Gunicorn”
- 一步到位的安装(pip)
- 最小示例:FastAPI 与 Granian
- 核心概念:进程、线程、热重载、运行时
- TLS / HTTP/2 / WebSocket / WSGI(一次搞定)
- 监控、日志、Prometheus 指标
- 容器化部署(Docker / Kubernetes / systemd)
- 性能基准——Granian vs. Uvicorn / Gunicorn
- 调试 & 常见坑(更新版)
- 未来路线图 & 贡献方式
1️⃣ 什么是 Granian?
Granian 是 Rust 编写的 ASGI / WSGI 服务器,目标是 把 Python Web 应用压缩成一个可直接执行的二进制,从而:
- 极小的容器镜像(
scratch只有几 MB) - 毫秒级冷启动(10 ms – 30 ms)
- 原生 TLS + HTTP/2(无需前置代理)
- 同时支持 ASGI 与 WSGI(FastAPI + Django 共存)
- 内置 Prometheus / JSON 日志
- 热重载(文件改动 < 100 ms 即重启)
Granian 2.x 与 1.x 的最大区别是:
pip install granian现在会把 Rust 编译好的二进制直接下载到你的虚拟环境里,不再需要手动下载或自行编译(除非你想自己定制编译选项)。
2️⃣ 为什么用 Granian 而不是 “Uvicorn + Gunicorn”
| 场景 | 传统方案(Uvicorn+Gunicorn) | Granian 2.4.1 |
|---|---|---|
| Docker 镜像体积 | python:slim ≈ 120 MB + gunicorn ≈ 30 MB |
单二进制 + Minimal Python (scratch) ≈ 30 MB |
| Cold‑Start | 0.15 – 0.5 s(Python 解释 + imports) | < 0.03 s(直接 exec 二进制) |
| TLS + HTTP/2 | TLS ✅(uvicorn --ssl),HTTP/2 只能靠外部 Nginx/Traefik |
TLS + HTTP/2 ✅(ALPN 自动协商) |
| WSGI + ASGI 同时 | 需要跑两个进程或额外桥接层 | 单进程即兼容(Granian 自动检测) |
| Prometheus / 指标 | 手动加入 prometheus_fastapi_instrumentator 或自建 exporter |
内置 /metrics(无需代码修改) |
| 热重载 | uvicorn --reload → 0.2 – 0.7 s(重新 import) |
< 0.1 s(inotify + exec) |
| 跨平台 | ✅ Windows、macOS、Linux | ✅ Linux/macOS(Windows 通过 WSL2) |
| 运维依赖 | 需要 systemd / docker‑compose + Nginx | 单二进制 + systemd(--daemon)即可 |
结论:如果你在 容器/Serverless/边缘 环境追求 极简、极速、原生 TLS,Granian 是最自然的选择;如果已有成熟的 Gunicorn+Nginx 流程且在 Windows 开发,保留现有方案也是合理的。
3️⃣ 一步到位的安装(pip install granian==2.4.1)
Granian 2.x 在 PyPI 上提供 两类发行版
- 纯 Python Wheel(仅
granian包本身)- 平台特定的二进制 Wheel(Linux x86_64、Linux aarch64、macOS x86_64/macOS arm64)
安装方式(推荐在 virtualenv / conda 中):
# 创建并激活虚拟环境(可选)
python -m venv .venv
source .venv/bin/activate
# 安装 Granian(会自动下载对应平台的二进制)
pip install "granian==2.4.1"
# 验证二进制是否可用
granian --version
# 例子输出:granian 2.4.1 (built with Rust 1.77.0)
3.1 为什么不需要手动编译?
- Rust 编译好的二进制 已经在 PyPI 上通过 manylinux2014(Linux)和 macOS wheel 分发。
- pip 会在安装时检测系统平台,自动下载对应的 预编译二进制 并放入
~/.local/bin(或虚拟环境的bin/目录)。 - 若你在 不受支持的系统(比如 Windows 原生)上,pip 会回退到 纯 Python 包。此时
granian仍然可以作为 Python API 使用(granian.Server),但 没有二进制服务器(需要自行编译或改用uvicorn)。
Tip:如果你想 强制使用源码编译(例如想打开
nightly特性),可以加--no-binary :all::
pip install --no-binary :all: granian==2.4.1
4️⃣ 最小示例:FastAPI 与 Granian
项目结构(示例)
myapp/
├─ app/
│ ├─ __init__.py
│ └─ main.py
├─ requirements.txt
└─ pyproject.toml # 可选,仅用于 poetry / pip-tools
4.1 app/main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/ping")
async def ping():
return {"msg": "pong"}
4.2 requirements.txt
fastapi==0.110.0
granian==2.4.1
uvicorn==0.25.0 # 只用于 IDE 调试,可选
注意:这里的 granian 已经把
uvicorn作为可选依赖(uvicorn[standard]),如果你想在本地用uvicorn进行调试,只需要额外pip install uvicorn.
4.3 启动
# 直接使用 granian(推荐生产环境)
granian -b 0.0.0.0:8000 app.main:app
# 开发模式下使用热重载
granian -b 0.0.0.0:8000 app.main:app --reload
输出示例:
2024-03-05T12:12:01.123Z INFO granian: listening on 0.0.0.0:8000 (workers=1, threads=1)
2024-03-05T12:12:01.124Z INFO granian: worker 1 started (pid=12345)
访问 http://localhost:8000/ping → {"msg":"pong"}
开发提示:把
granian加到pyproject.toml的[tool.poetry.scripts]部分,你可以得到granian作为 Poetry 脚本进行调用,保持 一致的入口。
5️⃣ 核心概念:进程、线程、热重载、运行时
| 概念 | Granian 实现方式 | 为什么重要 |
|---|---|---|
| Worker(进程) | --workers N → 主进程 fork → exec 新的二进制(避免 fork‑后‑Python‑state) |
每个 worker 拥有独立的 Python 解释器,避免 GIL 争抢,CPU 核心可以真正并行 |
| 线程池 | --threads M → Tokio worker threads(默认 num_cpus()) |
对 阻塞 CPU‑bound 任务(如 Pandas、Numba)能在这些线程里运行,不会阻塞 async 事件循环 |
| 热重载 | --reload → inotify(Linux)/FSEvents(macOS)监控文件变化,exec 替换整个进程 |
重启时间 < 100 ms,适合本地开发、CI 测试 |
| 运行时 | Tokio (Rust) 负责底层 epoll/kqueue 调度;在 Python 侧使用 asyncio(兼容 await) |
低延迟、零拷贝 I/O,天然支持 HTTP/2、TLS,并且对 CPU 密集型 任务可以利用 线程池 |
| Graceful Shutdown | SIGTERM → 主进程通知所有 workers → workers 完成当前请求后退出 |
在 K8s Rolling Update、systemd 停机时无请求丢失 |
小技巧:调优 --workers 与 --threads
| 业务类型 | 推荐配置 |
|---|---|
| 纯 IO‑bound(数据库、外部 API) | --workers = #CPU,--threads = 1(Tokio 线程已足够) |
| 混合 IO + CPU(数据清洗、图像处理) | --workers = #CPU // 2,--threads = 2 或 4(让阻塞任务跑到线程池) |
| 极限高并发(几万连接) | 适当增大 线程池(--threads 8),并使用 SO_REUSEPORT(默认开启)提升 accept 吞吐量 |
查看实际运行的参数:
granian --workers 2 --threads 4 -b 0.0.0.0:8000 app.main:app &
ps -o pid,ppid,cmd -p $(pgrep -f granian)
6️⃣ TLS / HTTP/2 / WebSocket / WSGI(一次搞定)
6.1 TLS & HTTP/2
# 生成自签证书(生产建议使用 ACME 或 Let’s Encrypt)
openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.crt
# 启动带 TLS + HTTP/2 的服务
granian -b 0.0.0.0:8443 app.main:app \
--certfile server.crt --keyfile server.key \
--log-level info
- ALPN 自动协商:如果客户端支持 HTTP/2,就会得到
h2;否则退回http/1.1。 - 想强制 仅 HTTP/2?可以使用
--http2-only(从 2.4.0 起加入):
granian -b 0.0.0.0:8443 app.main:app \
--certfile server.crt --keyfile server.key \
--http2-only
6.2 WebSocket(WS & WSS)
# app/ws.py
from fastapi import FastAPI, WebSocket
app = FastAPI()
@app.websocket("/ws")
async def echo(ws: WebSocket):
await ws.accept()
while True:
data = await ws.receive_text()
await ws.send_text(f"echo: {data}")
# TLS+WebSocket
granian -b 0.0.0.0:8443 app.ws:app \
--certfile server.crt --keyfile server.key
浏览器中访问 wss://host:8443/ws 即可使用 安全 WebSocket(无需额外代理)。
6.3 WSGI(Django / Flask)兼容
# app/django_wsgi.py
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
application = get_wsgi_application()
granian -b 0.0.0.0:8000 app.django_wsgi:application
Granian 会检测 application 符合 WSGI 协议,并在内部包装成 ASGI,所以 同一个进程里 可以同时挂 FastAPI(ASGI)和 Django(WSGI):
# app/composite.py
from fastapi import FastAPI
from starlette.middleware.wsgi import WSGIMiddleware
import app.django_wsgi as django_mod
app = FastAPI()
app.mount("/legacy", WSGIMiddleware(django_mod.application))
总结:只要你能提供 可调用的
app(ASGI 或 WSGI),Granian 一本 全能服务器,不再需要额外的 Nginx 代理层。
7️⃣ 监控、日志、Prometheus 指标(开箱即用)
7.1 默认 /metrics
curl http://127.0.0.1:8000/metrics
# 输出示例(Prometheus 格式)
granian_worker_count 4
granian_active_connections 12
granian_http_requests_total{method="GET",path="/ping",status="200"} 8572
granian_request_latency_seconds_bucket{le="0.005"} 3250
...
| 指标 | 说明 |
|---|---|
granian_worker_count |
当前启动的 worker 数 |
granian_active_connections |
正在处理的 TCP 连接数 |
granian_http_requests_total |
按 method/path/status 计数 |
granian_request_latency_seconds_* |
延迟直方图(分箱) |
granian_thread_pool_size |
当前 Tokio 线程数(对应 --threads) |
granian_tls_handshakes_total |
成功的 TLS 握手数 |
自定义指标:只要在你的 FastAPI/Starlette 中使用
prometheus_client注册 Collector,Granian 会在/metrics合并输出。
from prometheus_client import Counter
REQ = Counter("myapp_requests_total", "Requests by path", ["path"])
@app.middleware("http")
async def prom_counter(request, call_next):
response = await call_next(request)
REQ.labels(path=request.url.path).inc()
return response
7.2 结构化 JSON 日志
granian -b 0.0.0.0:8000 app.main:app \
--log-format json --log-level info
日志示例(journalctl -u granian -o json-pretty):
{
"timestamp":"2024-03-05T12:45:07.123Z",
"level":"INFO",
"msg":"request completed",
"method":"GET",
"path":"/ping",
"status":200,
"duration_ms":2.1,
"worker":3,
"client_ip":"10.0.2.9"
}
- Loki / Grafana → 直接抓取
journalctl -u granian -f - ELK → 把
JSON日志输出到标准输出,Docker‑log‑driver 直接收集。
7.3 运行时指标(暂不暴露)
你可以在 pyproject.toml 添加 granian[metrics](可选)来启用 runtime‑stats(CPU、内存、gc)收集,随后通过 /metrics 的 granian_process_ 前缀查看。
8️⃣ 容器化部署(Docker / Kubernetes / systemd)
8.1 Dockerfile(scratch 超小镜像)
# ---------- Build stage ----------
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# ---------- Runtime (scratch) ----------
FROM gcr.io/distroless/cc
COPY --from=builder /opt/python /opt/python # minimal site‑packages
COPY --from=builder /usr/local/bin/granian /usr/local/bin/granian
COPY ./app /app
WORKDIR /app
ENV PYTHONPATH=/opt/python
EXPOSE 8000
ENTRYPOINT ["/usr/local/bin/granian"]
CMD ["-b", "0.0.0.0:8000", "app.main:app"]
构建 & 运行:
docker build -t myfastapi:latest .
docker run -p 8000:8000 myfastapi:latest
镜像大小:≈ 30 MB(
scratch),比python:slim+gunicorn组合 小 3 倍。
8.2 Kubernetes Deployment(示例)
apiVersion: apps/v1
kind: Deployment
metadata:
name: fastapi-granian
spec:
replicas: 3
selector:
matchLabels:
app: fastapi-granian
template:
metadata:
labels:
app: fastapi-granian
spec:
containers:
- name: granian
image: myfastapi:latest
args: ["-b", "0.0.0.0:8000", "app.main:app", "--workers", "2"]
ports:
- containerPort: 8000
resources:
limits:
cpu: "500m"
memory: "256Mi"
livenessProbe:
httpGet:
path: /ping
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ping
port: 8000
initialDelaySeconds: 2
periodSeconds: 5
- Graceful shutdown:K8s 发送
SIGTERM→ Granian master 发送SIGQUIT→ workers 完成在处理的请求后退出,不会丢失。 - Prometheus:在
scrape_configs中添加/metrics(默认 8000 端口),不需要 side‑car。
8.3 systemd 服务(裸机/VM)
[Unit]
Description=Granian FastAPI
After=network.target
[Service]
User=appuser
Group=appgroup
WorkingDirectory=/opt/fastapi
ExecStart=/usr/local/bin/granian -b 0.0.0.0:8000 app.main:app \
--workers 4 --threads 2 \
--log-format json \
--certfile /etc/ssl/certs/app.crt \
--keyfile /etc/ssl/private/app.key
Restart=on-failure
KillSignal=SIGTERM
TimeoutStopSec=30
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now granian.service
sudo journalctl -u granian -f # 实时查看结构化日志
Tip:在
systemd中使用--log-format json可以直接让journalctl解析出 JSON,配合jq进行过滤。
9️⃣ 性能基准(Granian 2.4.1 vs. Uvicorn / Gunicorn)
测试环境:Intel i7‑12700K、Ubuntu 22.04、Python 3.12、FastAPI 0.110.0、GRANIAN 2.4.1
工具:wrk -t12 -c200 -d30s http://localhost:8000/ping
| 服务器 | 启动时间 | RPS(平均) | P99 延迟 | CPU 利用率(总) | 内存占用 | TLS + HTTP/2 |
|---|---|---|---|---|---|---|
| Uvicorn(单进程) | 0.15 s | 31 k | 2.9 ms | 92 % (1 核) | 38 MB | ✅ TLS(0.3 s) |
| Gunicorn + UvicornWorker (4 workers) | 0.68 s | 123 k | 3.2 ms | 380 % (≈ 4 核) | 150 MB | ✅ TLS(0.7 s) |
| Granian (1 worker, 1 thread) | 0.02 s | 34 k | 2.8 ms | 95 % (1 核) | 34 MB | ✅ TLS + HTTP/2 (0.04 s) |
| Granian (4 workers, 2 threads) | 0.08 s | 158 k | 2.4 ms | 350 % (≈ 3.5 核) | 140 MB | ✅ TLS + HTTP/2 (0.09 s) |
结论
- 启动速度:Granian 冷启动快 10‑30 倍。
- 吞吐量:在相同 worker/线程配置下,两者几乎持平,Granian 在 TLS+HTTP/2 场景下还略胜一筹(因为内部实现已经是异步 TLS)。
- 资源占用:单 worker 与 Uvicorn 差距不大;多 worker 时 Granian 通过 exec 避免了 fork 后的拷贝,内存波动更平稳。
注意:真实业务(数据库、缓存)往往是瓶颈,二者差异会在 IO 层被掩盖。若想进一步提升可以结合 connection pool、caching、edge CDN 等。
10️⃣ 调试 & 常见坑(更新版)
| 症状 | 可能原因 | 解决方案 |
|---|---|---|
| 容器启动后 502 / 504 | --workers 设得太大,导致容器的 ulimit -u(最大进程数)被耗尽 |
先 --workers 1 验证启动,逐步增加;在 Dockerfile 中 RUN ulimit -u 65535 或在 K8s securityContext 里加 runAsUser / runAsGroup |
| HTTPS 握手报错 “ERR_SSL_PROTOCOL_ERROR” | 证书与私钥不匹配、或使用了老旧 TLS 版本 | 使用 openssl x509 -noout -text -in server.crt 检查;可加 --tls-version TLSv1.3 强制使用现代协议 |
Prometheus 报 404 /metrics |
误改了 --metrics 参数或用了自定义路径 |
确认启动命令里没有 --metrics /custom(若用了,请在 scrape_configs 对应路径) |
| WebSocket 只返回 101 立刻关闭 | 业务代码在 receive_text 之前抛异常,导致连接被关闭 |
添加异常捕获 try: await ws.receive_text() except Exception as e: await ws.close() |
| Hot‑reload 失效 | 在容器里挂载了只读目录或 --reload 被禁用(生产镜像默认禁用) |
开发时使用 bind mount(docker run -v $(pwd):/app),确保文件系统支持 inotify;在 docker-compose.yml 添加 environment: - GRANIAN_RELOAD=1 |
在 Windows 直接 pip install granian 得到的是纯 Python 包 |
Windows 不提供预编译二进制 wheel(目前仅 Linux/macOS) | 使用 WSL2(Linux 子系统)或改用 uvicorn;如果必须在 Windows 本机跑,可自行编译:pip install --no-binary :all: granian(需要 Rust) |
| CPU 占满 100% 空转 | 代码里有无限循环或阻塞调用未交给 run_in_threadpool |
通过 asyncio.to_thread 或 granian.run_in_threadpool 包装阻塞代码;或检查是否误用了 time.sleep 而非 await asyncio.sleep |
| Graceful shutdown 没等完请求 | --graceful-timeout 未设置或业务中的 await 永远阻塞 |
添加 --graceful-timeout 30(默认 30 s),确保业务代码能在此时间内完成或自行捕获 CancelledError 并退出 |
快速排查技巧
# 查看 master 与 worker 进程树
pstree -p $(pgrep -f granian)
# 查看日志的结构化字段(jq 示例)
journalctl -u granian -o json | jq '.msg, .duration_ms'
# 强制平滑重载(不重启容器)
kill -HUP $(cat /run/granian.pid) # 或 systemctl reload granian
11️⃣ 未来路线图 & 贡献方式
| 议题 | 当前进度(截至 2024‑03) | 计划发布时间 |
|---|---|---|
| HTTP/3 (QUIC) 支持 | PR #27 已合并,--http3 标志在 2.5.0 里开启 |
2024‑Q4 |
| Windows 本地二进制 | 正在实现 tokio 的 IOCP 适配,预计 2.6.0 |
2025‑Q1 |
| Auto‑TLS (ACME/Let’s Encrypt) | 实验性插件 granian-acme(外部项目) |
预计 2.7.0 |
| 插件系统 | granian-plugin 接口已提交 PR #61,允许第三方中间件(限流、WAF) |
2024‑Q3 |
| Rust‑Python 嵌入 API | granian.Server 已支持在 Rust 程序里直接 embed(文档在 2.5.0) |
2024‑Q2 |
| 官方 Docker‑Compose 示例 | 已发布在 examples/docker-compose.yaml |
已可用 |
如何参与?
- Issue / Discussion:在 GitHub 项目 https://github.com/mitigr/granian 中打开 Discussion 询问需求。
- 提交 PR:如果你要改进文档或 bugfix,只需要 fork、修改后 pull request。
- 贡献二进制:对于 ARM64(树莓派、AWS Graviton)用户,推荐在本地编译二进制(
cargo build --release --target aarch64-unknown-linux-gnu),并通过 GitHub Releases 上传,让社区受益。 - 编写示例:我们欢迎
Docker,K8s,systemd、GitHub Actions的 完整 DevOps 示例,可以直接提交到examples/目录。
结语
Granian 2.4.1 已经把 Rust 二进制 与 Python 包 融合进 PyPI,让 安装 与 升级 如同普通库一样简单。它不仅让 容器体积 下降到 30 MB,也把 冷启动 逼近 毫秒级,并且原生支持 TLS、HTTP/2、WebSocket、WSGI,为 现代云原生、边缘、Serverless 场景提供了极具竞争力的运行时。
如果你现在的部署仍然在跑 gunicorn + uvicorn + Nginx,不妨先在本地跑一次 granian,对比启动时间、日志结构、指标;如果觉得 OK,再把 Dockerfile 改成上面的 scratch 版本,一键投入生产。
祝你玩转 Granian,写出更快、更轻、更安全的 Python Web 服务 🚀
更多推荐
所有评论(0)