FastAPI真香!一个前端仔的后端初体验
上周五晚上十点半,我盯着MacBook上那个卡成PPT的Node.js本地服务,突然意识到一件事:我这个天天研究Lottie动画、CSS Houdini的前端工程师,竟然要开始写后端了。
事情是这样的:公司最近在搞一个新产品原型,涉及区块链资产查询和交易记录展示。产品经理拍着胸脯说“就几个接口,很简单”,结果后端大哥被临时抽去支援另一个Java微服务项目,deadline还剩三天。领导看我平时GitHub上Python小脚本写得挺溜,直接甩过来一句:“你先顶上,用FastAPI搭个MVP。”
得,又到了程序员被迫全栈的时刻。
为什么选FastAPI?因为我懒啊
说实话,我本来想用Flask的——毕竟大学毕设就是用它糊的。但翻了翻文档发现FastAPI这玩意儿简直为我这种既要又要还要的人量身定制:
- 自动生成Swagger文档:再也不用跟产品经理解释“接口还没写完所以文档没有”了
- Pydantic数据校验:前端传参格式不对?直接422错误,省得我写一堆if判断
- 异步支持:虽然这次用不上,但听说性能吊打Flask(后面测试确实如此)
最打动我的是它的类型提示——作为一个被TypeScript惯坏的前端,看到Python也能享受def get_user(user_id: int) -> User这种安全感,当场就决定试试。
从Hello World到区块链接口
先搞个最简示例压压惊:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def hello():
return {"message": "前端仔的后端初体验"}
跑起来后访问/docs,自动出来的交互式文档让我惊呼“卧槽”——这比我们组Java Spring Boot项目里手写的YAPI文档好用十倍!
但现实很快打脸。当我尝试接入区块链节点查询交易时,遇到了第一个坑:同步阻塞。
# 初版代码(别学!)
import requests
@app.get("/tx/{tx_hash}")
def get_transaction(tx_hash: str):
# 这里会阻塞整个事件循环!
resp = requests.get(f"https://eth-mainnet.g.alchemy.com/v2/xxx/{tx_hash}")
return resp.json()
压力测试时QPS刚过50,Uvicorn进程CPU就飙到100%。想起之前面试某大厂时被问“如何提高Python服务并发”,当时支支吾吾答不上来,现在终于懂了——得用异步!
换成httpx后世界清净了:
import httpx
@app.get("/tx/{tx_hash}")
async def get_transaction(tx_hash: str):
async with httpx.AsyncClient() as client:
resp = await client.get(f"https://eth-mainnet.g.alchemy.com/v2/xxx/{tx_hash}")
return resp.json()
QPS直接干到800+(本地测试数据),老板路过我工位时都多看了两眼屏幕。
数据库设计踩坑实录
产品需求里有个“用户资产历史记录”功能。我一开始偷懒用SQLite存本地,结果测试同学一导入10万条数据,查询慢得像在加载IE6。
赶紧切PostgreSQL,但建表时又犯了新手错误:
-- 错误示范:没加索引
CREATE TABLE asset_history (
user_id VARCHAR(42),
token_address VARCHAR(42),
balance DECIMAL,
timestamp TIMESTAMP
);
当用户查自己30天内的ETH变动时,EXPLAIN ANALYZE显示全表扫描,耗时1.2秒。运维同事路过冷笑:“你这接口上线怕不是要被DDoS?”
赶紧补复合索引:
CREATE INDEX idx_user_token_time
ON asset_history (user_id, token_address, timestamp DESC);
查询时间降到23ms,这才有点生产环境的样子。
性能优化三板斧
在准备跳槽刷LeetCode间隙,我顺手对服务做了些优化:
连接池管理
用databases库替代原生psycopg2,避免每次请求新建DB连接:from databases import Database database = Database("postgresql://user:pwd@localhost/db")缓存穿透防护
区块链交易查询加Redis缓存,空结果也缓存5秒防刷:@app.get("/tx/{tx_hash}") async def get_transaction(tx_hash: str): cache_key = f"tx:{tx_hash}" if cached := await redis.get(cache_key): return json.loads(cached) # ...查询逻辑... await redis.setex(cache_key, 300, json.dumps(result)) return resultGunicorn + Uvicorn组合拳
本地开发用uvicorn main:app,生产环境上Gunicorn管理多worker:gunicorn -k uvicorn.workers.UvicornWorker --workers 4 main:app
最终压测结果(MacBook Pro M1):
| 配置 | QPS | 平均延迟 |
|---|---|---|
| 单worker同步 | 48 | 2083ms |
| 单worker异步 | 812 | 123ms |
| 4 worker异步 | 2940 | 34ms |
这提升幅度,够我在周会上吹一周了。
和Java项目的对比感悟
我们主站是Spring Boot全家桶,每次改个接口都要等3分钟启动。而FastAPI改完代码保存即生效(配合--reload),调试效率高到飞起。
但不得不说,Java生态在分布式事务、服务治理这些重型场景还是稳如老狗。FastAPI更适合做以下场景:
- 快速验证产品想法(MVP阶段)
- 中小型内部工具
- 高I/O低计算的API网关
如果是搞区块链底层或者高频交易系统,该上Java还是得上——虽然我每次看到CompletableFuture.supplyAsync().thenApplyAsync()就想砸键盘。
给前端转后端的朋友几点建议
别迷信“简单”
FastAPI虽然上手快,但生产环境要考虑日志监控、熔断降级、配置管理,这些和前端工程化一样复杂数据库不是JSON存储
我差点把所有区块链数据存成JSON字段,被DBA按在地上摩擦异步不是银弹
CPU密集型任务(比如解析大段ABI)还是要扔给Celery
最后说个搞笑的事:昨天产品问我“能不能加个WebSocket实时推送交易状态”,我正愁没机会用Starlette的WebSocket呢!结果他下一句是“不过下周可能改成轮询...”。果然,互联网没有银弹,只有不断变化的需求。
如果你也在用Mac写代码、边刷题边焦虑跳槽,不妨试试FastAPI——至少比调CSS居中简单多了(手动狗头)。

评论 0