拆解分布式系统中常见问题及解决方案

张开发
2026/4/4 17:21:38 15 分钟阅读
拆解分布式系统中常见问题及解决方案
为什么FastAPI在分布式里容易“翻车”FastAPI的异步特性让它像一台高性能跑车但分布式系统不是单车道——它更像一个复杂的城市交通网。你的服务要和别人通信、共享资源、保证数据不打架。最常见的三大痛点网络不可靠服务A调用服务B可能超时、断连甚至B处理完了但A没收到响应。资源竞争多个实例同时操作同一笔订单搞出负数库存。数据一致性跨库操作要么都成功要么都失败但分布式事务太重了。接下来咱们一个一个解决建议收藏后反复看。☎️ 问题一服务调用像“打电话占线”想象你给餐厅打电话订位服务A服务员接起来后去查记录服务B处理中但这时电话突然断了——你可能会重打但服务员那边可能已经帮你预留了位置。这就是重复执行的隐患。解决方案超时控制 重试 幂等性在FastAPI里我习惯用httpx.AsyncClient并且必须显式设置超时。千万别用默认值否则生产环境一个慢请求就能拖垮整个链路。# client.py import httpx async def call_service_b(order_id: str): async with httpx.AsyncClient(timeout10.0) as client: # 总超时10秒 try: resp await client.post( http://service-b/process, json{order_id: order_id}, headers{Idempotency-Key: order_id} # 幂等键 ) resp.raise_for_status() return resp.json() except httpx.TimeoutException: # 这里记录日志触发补偿或告警但不要直接重试 print(f调用超时但可能已处理需查状态 order_id{order_id}) # 最佳实践查询下游状态决定是否重试这里的关键是Idempotency-Key头下游服务必须根据这个key保证同一个请求只处理一次。自己实现时可以用Redis记录已处理的key。⚠️重点警告重试一定要配合幂等否则就是灾难。我见过因为重试导致重复扣款的案例最后用唯一索引业务状态机才解决。 问题二抢钥匙——分布式锁多个服务实例同时操作同一份数据就像几个人抢同一间厕所不加锁就会“撞车”。以最常见的库存扣减为例# 错误示范不加锁 async def deduct_stock(product_id: str, quantity: int): product await db.products.find_one({_id: product_id}) if product.stock quantity: product.stock - quantity await db.products.save(product) # 两个并发可能同时读到stock10都减到9实际只减了1正确姿势用Redis实现分布式锁推荐Redlock算法但简单场景用单点锁过期时间即可。# 使用 aioredis 实现锁 import aioredis import asyncio redis aioredis.from_url(redis://localhost) async def acquire_lock(lock_key: str, timeout: int 10): # SET NX 且设置过期时间防止死锁 locked await redis.set(lock_key, locked, nxTrue, extimeout) return locked async def release_lock(lock_key: str): await redis.delete(lock_key) async def deduct_stock_with_lock(product_id: str, quantity: int): lock_key flock:product:{product_id} if not await acquire_lock(lock_key): raise Exception(系统繁忙请稍后重试) try: # 业务逻辑 product await db.products.find_one({_id: product_id}) if product.stock quantity: product.stock - quantity await db.products.save(product) return True return False finally: await release_lock(lock_key) # 一定要释放这里有个坑锁的过期时间要大于业务最大执行时间否则业务没做完锁就自动释放了别的请求又进来了。我的经验是设置30秒并配合看门狗Watch Dog机制续期但简单场景可以预估时间设长一点。 问题三数据一致性——先扣钱还是先发货分布式事务的经典场景创建订单 - 扣库存 - 扣余额。要是用强一致性就得用2PC两阶段提交但性能差、复杂度高。现实往往采用最终一致性通过消息队列保证各个操作要么都成功要么都回滚。在FastAPI里我常用fastapi-consumer或直接用Celery处理。核心思想本地事务 消息表 重试。# 伪代码创建订单时先写数据库再发消息 async def create_order(order_data): async with db.transaction(): # 假设支持事务 # 1. 插入订单表 await db.orders.insert(order_data) # 2. 插入本地消息表 await db.messages.insert({topic: order_created, data: order_data, status: pending}) # 3. 发送到消息队列如果失败有定时任务扫描消息表重发 await send_to_kafka(order_created, order_data)消费者处理扣库存时必须保证幂等用消息ID去重。万一扣库存失败可以记录失败然后人工介入或自动回滚订单发送补偿消息。 这里有个小技巧用消息表做“发件箱模式”避免分布式事务还能保证消息不丢。

更多文章