疯狂派对go中文版
123.12 MB · 2025-11-15
此文涉及 MySQL、Kafka、Redis 组件和用户、资金、订单模块,整体从 Java 迁移到 Python 所需的知识点。
Java 的语法很臃肿,如果会 Java 再看其他语言简直降维打击,学起来没什么难度,顶多是底层的一些设计、架构不一样。
Python 是动态语言,比如
# 传统 Python
def add(a, b):
return a + b
add(1, 2) # 结果是 3
add("a", "b") # 结果是 "ab"
没有类型校验,没有运行前编译,看起来非常不安全。于是有了 Pydantic。
def add_with_hints(a: int, b: int) -> int:
return a + b
括号里面是参数,-> int 是返回值,用了 Type Hints,相当于一种提示,让 Pydantic 读取到后进行校验和转换。
列举一个接口的例子,比如 Java
// 1. DTO 类 (POJO)
public class CreateUserDTO {
// 2. Validation (javax.validation)
@NotNull
@Size(min = 3, max = 50)
private String username;
@Min(18)
private Integer age;
// Getters and Setters... (由 Jackson 用于序列化)
}
// 3. Controller (Jackson + Validation)
@PostMapping("/users")
public ResponseEntity<?> createUser(@Valid @RequestBody CreateUserDTO userDTO) {
// 到了这里,userDTO 已经被 Jackson 解析,并被 Validation 校验过了
// ...
}
而 Python + FastAPI + Pydantic 是
from pydantic import BaseModel, Field
from typing import Optional # 对应 Java 的 Optional 或 null
# 1. Pydantic 模型 (这一个东西 = Java 的 DTO + Validation + Jackson 注解)
class CreateUserDTO(BaseModel):
# 对应 @NotNull 和 @Size
username: str = Field(..., min_length=3, max_length=50)
# 对应 @Min(18) 和 可选 (Integer 而非 int)
age: Optional[int] = Field(None, ge=18) # ge = Greater than or Equal
# 注意:
# 1. ... (三个点) 表示这个字段是必填的
# 2. Field(None, ...) 表示这个字段是可选的 (默认值为 None)
# 2. FastAPI 路由
@app.post("/users")
async def create_user(user_dto: CreateUserDTO):
# 到了这里,Pydantic 已经自动完成了:
# 1. 读取 JSON 请求体 (像 Jackson)
# 2. 按照类型提示 (str, int) 校验和转换数据
# 3. 运行校验规则 (min_length, ge) (像 javax.validation)
# 4. 如果校验失败,自动返回一个 422 错误的 JSON 响应
# 你可以直接使用这个对象
# user_dto.username
return user_dto
Python 里面的 Optional 和 Java 里面是同一个意思,写法不一样,Python 里面把八大基本类型的包装类也用 Optional 表示。
Python 里没有 Lombok,直接.属性就行。
这是 Java 和 Python 最大的不同,Java 是多线程,在 Spring Web 中一个请求就是一个线程(非 WebFlux),Tomcat 里面配置最大线程数就这个作用。线程内是阻塞的,比如查数据库线程就停那了,等待数据完成了继续执行。
Python 用的 asyncio 单线程事件循环。查数据库的时候,线程去执行别的,直到数据拿到了再接着执行。(具体流程后面详细分解)
比如这个接口逻辑
查用户
查商品
写订单
写订单详情
Java 是
// 线程 1
public Order createOrder(...) {
User user = userRepo.findById(...); // (线程 1 阻塞,等待 5ms)
Product product = productRepo.findById(...); // (线程 1 阻塞,等待 5ms)
Order order = new Order(...);
orderRepo.save(order); // (线程 1 阻塞,等待 10ms)
OrderDetail detail = new OrderDetail(...);
detailRepo.save(detail); // (线程 1 阻塞,等待 10ms)
return order; // 总耗时:5+5+10+10 = 30ms (线程 1 被独占 30ms)
}
Python 是
# 单个事件循环线程
async def create_order(...):
# 'await':我把控制权交回事件循环
user = await user_repo.get_by_id(...) # (I/O 开始,任务暂停)
# ... 某个未来的时刻,事件循环恢复了我的执行 ...
product = await product_repo.get_by_id(...) # (I/O 开始,任务再次暂停)
# ... 再次恢复 ...
order = Order(...)
await order_repo.save(order) # (I/O 开始,任务再次暂停)
# ... 再次恢复 ...
detail = OrderDetail(...)
await detail_repo.save(detail) # (I/O 开始,任务再次暂停)
# ... 再次恢复 ...
return order # 总耗时:还是 30ms (I/O 总时间)
这导致的第一重大变化是 ThreadLocal 没了,之前用户登录的 token 可以用 AOP 一路带到线程里,在 Python 里面用 FastAPI 的 Depends 解决,后面想说。
第二个是 async 必须全程到底,一个方法用了,所有上下游调用都用,外部的 MySQL、Kafka 这种组件也要用 async 的版本。
主要是 Python 的 RAM 内存管理用的 GLC(Global Interpreter Lock),不像 Java 有复杂的 GC 垃圾回收机制,GLC 在用到对象的引用计数 + 1,没用到 -1,为 0 则清理,为了防止多线程 RAM 内存出问题,有了 GLC。
asyncio 就是用单线程多进程(协程)的方式,提高并发。
打开 Win 的任务管理器,最外层的 Micosoft Edge 就一个进程,括号里的 18 就是 18 的线程,协程一种特殊的线程,可以很快速的在不同任务之间切换,asyncio 里面这么叫。
其实 CPU 的速度是远超 IO 的,老早之前单核 CPU 就有时间切片模拟成多核,服务器大都是 IO 密集型,卡在查 MySQL、Kafka、Redis 等,这样其实更高效。
每个线程的启动都要消耗几 M 的 RAM,线程上下文切换也比较耗资源,进程之间效率高得多。
线上真实的多核处理器服务器,会用进程管理器 Gunicorn 根据服务器核心数启动多个 Python 进程,由 Gunicorn 将请求分发。
asyncio 底层有两个组件,Ready Queue 和 Waiting Map。Ready Queue 是可以立即执行的队列,Waiting Map 是异步等待唤醒的任务。
以为一个请求流程为例
async def create_order(...):
# 你的代码从这里开始执行
user_data = await request.json() # 假设这是第一个 await (I/O)
...
这些代码都先当做任务放到 Ready Queue,直到遇到第一个 await 则暂停,把任务移动到 Waiting Map,然后从 Ready Queue 取下一个任务执行。直到所有的 Ready Queue 都执行完成。
一个是 Queue 一个是 Map,Queue 存的是任务,Map 中存的是任务 + 回调,这样数据回调的时候知道去执行哪个任务。
await 交给操作系统系统 OS 处理,在日常开发中比如付款场景等支付宝回调,在这里类似。
await 告诉操作系统帮我查下 MySQL,有结果了通知我。但不同的操作系统通知方式不一样,Mac UNIX 的 kqueue、Linux 的 epoll 属于Reactor 的就绪通知,Win 的 IOPC 属于Proactor 的回调。
他俩区别是系统 OS 层的,涉及网卡、数据流那些,Proactor 帮忙把数据流复制了一份,Reactor 则自己去 read() write(),代码层面没有区别,asyncio 已经帮忙封装好了,根据操作系统自动调用。
重要的是理解 Java 多线程和 Python 多进程的区别,比做成工厂,Java 是有 1000 个工人的工厂,一人一个任务,但是任务很轻,工人在工作空档有大把时间摸鱼;Python 是工厂里面只有一头核动力驴,房贷车贷彩礼贷加 8 个儿子,别人摸鱼的时候,他直接干下一个任务。
Spring 的两大核心 IOC 和 AOP 在 Python + FastAPI 中也有对应。
先说 AOP,对应 Python 的装饰器 Decorators 和 FastAPI 的中间件 Middleware。
例如 Java 的 @Around
@Aspect
@Component
public class LoggingAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("Enter: {}.{}()", ...);
Object result = joinPoint.proceed(); // 执行原方法
log.info("Exit: {}.{}()", ...);
return result;
}
}
对应的 Python 是
import functools
# 1. 这是一个“装饰器”,它接受一个函数(func)作为参数
def log_around(func):
# 2. 它返回一个新的“包装函数”(wrapper)
@functools.wraps(func) # 这是一个好习惯,用来保留原函数元信息
async def async_wrapper(*args, **kwargs):
print(f"Enter: {func.__name__}()")
result = await func(*args, **kwargs) # 3. 在内部执行原函数
print(f"Exit: {func.__name__}()")
return result
# (如果原函数是同步的,你需要一个同步的 wrapper)
def sync_wrapper(*args, **kwargs):
print(f"Enter: {func.__name__}()")
result = func(*args, **kwargs)
print(f"Exit: {func.__name__}()")
return result
# 智能返回异步或同步 wrapper
if asyncio.iscoroutinefunction(func):
return async_wrapper
else:
return sync_wrapper
Python 中其他地方的使用
# 4. "贴"上装饰器
@log_around
async def get_order_by_id(order_id: int):
print("... 正在执行核心业务: 查询订单 ...")
order = await order_repo.get(order_id)
return order
Python 的装饰器相当于 Spring 的 @Around,但它也有个缺点,就是不能指定范围,没有 execution 那样的东西,所以每个方法都要手动写个注解,特点是 Python 语言级别的,只要是函数就能用。
装饰器的作用就是把函数本身当成参数,放到装饰器里面执行,可以对入参、返回值等进行一系列操作。
FastAPI 也有类似的功能叫中间件,好处是可以拦截所有的 Request,缺点是只能在 Request 环境中使用,如果不是请求来的,比如系统本身触发的定时任务,就不能被执行。
列举两个常见的例子,全局日志,Spring 写法是
public class LoggingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
long startTime = System.currentTimeMillis();
logger.info("Request: " + request.getMethod() + " " + request.getRequestURI());
chain.doFilter(request, response); // <--- 在这里执行路由
long processTime = System.currentTimeMillis() - startTime;
response.addHeader("X-Process-Time", String.valueOf(processTime) + "ms");
logger.info("Request finished in " + processTime + "ms");
}
}
FastAPI 中间件写法是
import time
from fastapi import FastAPI, Request
import asyncio # 只是为了模拟耗时
app = FastAPI()
# 1. 使用 @app.middleware("http") 注册一个中间件
@app.middleware("http")
async def add_process_time_and_log_middleware(request: Request, call_next):
# request: Request 对象,等同于 HttpServletRequest
# call_next: 一个函数,等同于 FilterChain chain
# --- 这是 chain.doFilter() '之前' 的代码 ---
start_time = time.time()
print(f"--- [Middleware IN] 收到请求: {request.method} {request.url.path} ---")
# --- 这是执行 chain.doFilter() ---
# await call_next(request) 会去调用下一个中间件,或者最终的路由函数
# response 等同于 HttpServletResponse
response = await call_next(request)
# --- 这是 chain.doFilter() '之后' 的代码 ---
process_time = time.time() - start_time
# 往响应头里加东西
response.headers["X-Process-Time"] = f"{process_time:.4f}s"
print(f"--- [Middleware OUT] 请求处理完毕,耗时: {process_time:.4f}s ---")
return response
# --- 你的路由 ---
@app.get("/")
async def root():
print("... (路由函数) 正在处理核心业务 ...")
await asyncio.sleep(0.5) # 模拟 0.5 秒的 I/O 操作
return {"message": "Hello World"}
再来个全局异常捕获,Spring 写法是
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleAllExceptions(Exception ex) {
// ...
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
FastAPI 中间件写法是
from fastapi.responses import JSONResponse
@app.middleware("http")
async def global_exception_handler_middleware(request: Request, call_next):
try:
# 尝试执行路由 (chain.doFilter)
response = await call_next(request)
return response
except Exception as e:
# 如果 call_next() 内部 (即你的路由函数) 抛出了任何异常...
# 就在这里捕获!
print(f"--- [Middleware ERROR] 捕获到全局异常: {e} ---")
# 不要让异常崩溃,而是返回一个标准化的 JSON 错误
return JSONResponse(
status_code=500,
content={
"error_type": type(e).__name__,
"message": f"服务器内部发生未捕获错误: {e}",
},
)
# --- 一个会崩溃的路由 ---
@app.get("/crash")
async def crash_me():
print("... (路由函数) 准备执行 1 / 0 ...")
# 这会抛出一个 ZeroDivisionError
a = 1 / 0
return {"message": "你永远看不到我"}
总结一下,Java 没有 AOP,都是 Spring 提供的,还提供了 @Around @ControllerAdvice 自带的各种 Filter 封装好的工具,所以必须在 Spring 环境内才能使用。Python 本身就有装饰器,可以把函数本身当做参数处理。也可以用 FastAPI 的中间件,和 Spring 类似。
接下来对比 Spring 的 DI 和 FastAPI 的 DI。
Spring 有一个大的 IOC 容器 ApplicationContext 启动时批量扫描所有指定目录和直接的 bean,注入进来。
而 FastAPI 没有这个池子,用Depends +yield 改变代码执行顺序。表达的意思是在 Depends 的时候,先执行 Depends 里方法的代码,直到 yield 之前,返回 yield 让之前的执行完,接着执行 yield 之后的。
例如这是例子,备注是执行的顺序
async def get_db_session():
# 第 2 步: 开始执行。
session = await AsyncDatabaseSession()
print("--- 上层面包: Session 创建 ---")
try:
# 第 3 步: 'yield'
# 1. 把 session '扔' 出去
# 2. 在这里 '冻结' (PAUSE)
yield session
except Exception:
# (如果第 4 步出错,会跳到这里)
await session.rollback()
print("--- 事务回滚 ---")
else:
# (如果第 4 步没出错,会跳到这里)
await session.commit()
print("--- 事务提交 ---")
finally:
# 第 6 步: '解冻' (RESUME),继续执行
await session.close()
print("--- 下层面包: Session 关闭 ---")
@app.post("/orders")
async def create_order(
# 第 1 步: FastAPI 看到这个依赖
db: AsyncSession = Depends(get_db_session)
):
# 第 4 步: 拿到了 '扔' 出来的 session
# 开始执行 '馅料'
print("... 正在执行业务逻辑 ...")
db.add(Order(...))
# (如果这里抛出异常...)
# 第 5 步: '馅料' 执行完毕
return ...
让我做个最终的总结,属于 Python 的是 asyncio 和装饰器,装饰器可以把函数作为入参,像 AOP 一样观察和修改。
asyncio 是高性能的核心,通过 Ready Queue 和 Waiting Map 把进程转为协程,为 IO 密集型任务提效。
FastAPI 是基于 asyncio 简历的高性能 Web 框架,用中间件等同 Spring AOP,也有自己的 Depends + yield 做 DI。