适合谁?

你已经在用 FastAPI / Starlette / Django(WSGI)写业务,却觉得部署总是要额外加 Gunicorn + Nginx、镜像太大、启动慢。
你想在 Docker‑scratch、K8s、Serverless 或 边缘设备(IoT、Raspberry Pi)里只放一个二进制文件。
你想要 原生 HTTP/2 + TLS,而不想再跑一层反向代理。
这篇文章从 概念 → 安装 → 基础用法 → 高级特性 → 性能对比 → 实战部署 全面剖析 Granian,帮助你在几分钟内把它写进自己的项目。## 目录

  1. 什么是 Granian?
  2. 为什么用 Granian 而不是 “Uvicorn + Gunicorn”
  3. 一步到位的安装(pip)
  4. 最小示例:FastAPI 与 Granian
  5. 核心概念:进程、线程、热重载、运行时
  6. TLS / HTTP/2 / WebSocket / WSGI(一次搞定)
  7. 监控、日志、Prometheus 指标
  8. 容器化部署(Docker / Kubernetes / systemd)
  9. 性能基准——Granian vs. Uvicorn / Gunicorn
  10. 调试 & 常见坑(更新版)
  11. 未来路线图 & 贡献方式

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 sinotify + 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.xPyPI 上提供 两类发行版

  1. 纯 Python Wheel(仅 granian 包本身)
  2. 平台特定的二进制 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 → 主进程 forkexec 新的二进制(避免 fork‑后‑Python‑state) 每个 worker 拥有独立的 Python 解释器,避免 GIL 争抢,CPU 核心可以真正并行
线程池 --threads M → Tokio worker threads(默认 num_cpus() 阻塞 CPU‑bound 任务(如 Pandas、Numba)能在这些线程里运行,不会阻塞 async 事件循环
热重载 --reloadinotify(Linux)/FSEvents(macOS)监控文件变化,exec 替换整个进程 重启时间 < 100 ms,适合本地开发、CI 测试
运行时 Tokio (Rust) 负责底层 epoll/kqueue 调度;在 Python 侧使用 asyncio(兼容 await 低延迟、零拷贝 I/O,天然支持 HTTP/2TLS,并且对 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 = 24(让阻塞任务跑到线程池)
极限高并发(几万连接) 适当增大 线程池--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)收集,随后通过 /metricsgranian_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 poolcachingedge 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 mountdocker 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_threadgranian.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 本地二进制 正在实现 tokioIOCP 适配,预计 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 已可用

如何参与?

  1. Issue / Discussion:在 GitHub 项目 https://github.com/mitigr/granian 中打开 Discussion 询问需求。
  2. 提交 PR:如果你要改进文档或 bugfix,只需要 fork、修改后 pull request
  3. 贡献二进制:对于 ARM64(树莓派、AWS Graviton)用户,推荐在本地编译二进制(cargo build --release --target aarch64-unknown-linux-gnu),并通过 GitHub Releases 上传,让社区受益。
  4. 编写示例:我们欢迎 Docker, K8s, systemdGitHub 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 服务 🚀

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐