微服务架构设计实战:从单体到分布式

云端小木屋
2025-12-17 08:36
阅读 1448

上周五晚上十点半,我一边在 MacBook Pro 上敲 docker-compose down,一边听着隔壁工位小张和产品经理对线:“你说微服务能三天上线?那你来写啊!”——熟悉的配方,熟悉的味道。作为组里唯一一个 DBA 出身的后端开发(没错,就是那种看不得慢查询、见不得没索引、连 JSON 字段都恨不得建个 GIN 索引的人),我其实早就想把我们那个“祖传单体”给拆了。但现实是:老板要快,测试要稳,运维要日志,而我……只想早点回家撸猫。

起因:一个快撑不住的“巨石”

我们的核心系统是用 Python 写的 Django 单体应用,跑在 PostgreSQL 上。刚入职那会儿(快两年了),代码还能勉强维护,数据库也就几十张表。但随着业务爆炸式增长——尤其是去年搞了个“区块链积分上链”功能(别问,问就是老板看了某峰会 PPT)——整个系统开始频繁抽风。

典型场景:用户兑换积分触发链上交易,同时要扣库存、发通知、记账、写审计日志……全在一个事务里。结果?数据库连接池打满,PostgreSQL 的 max_connections 被干到 95%,线上直接 502。当时看着 Grafana 面板上飙红的 CPU 和堆积如山的 WAL 日志,我真的想砸电脑。

更离谱的是,每次改个小需求,比如加个字段,都要全量回归测试。测试同学看我的眼神都带着怨念:“你这又动数据库了?上次那个外键删了没?”

拆!但怎么拆?

领导拍板:微服务化。但具体怎么搞?市面上方案一堆:Spring Cloud、Dubbo、Go-micro、甚至有人提议上 Service Mesh。但我们技术栈是 Python + PostgreSQL,团队也没人会 Java,所以 Spring Cloud 直接出局。至于区块链部分?它本身是异步上链的,其实天然适合独立部署。

经过几轮撕逼(哦不,是技术评审),我们定了三个原则:

  1. 数据库必须隔离:每个服务独占自己的 DB schema,严禁跨库 join(DBA 的底线!)
  2. 语言保持 Python:虽然性能不是最优,但团队熟悉,开发效率高
  3. 通信协议轻量:HTTP/JSON 足够,暂时不上 gRPC(除非真有性能瓶颈)

于是,我们把系统粗暴拆成四块:

  • user-service:用户管理、鉴权
  • inventory-service:库存、商品
  • reward-service:积分、兑换逻辑(含区块链对接)
  • notification-service:消息推送

技术选型:Python 微服务全家桶

既然是 Python,那框架就得挑靠谱的。Django 太重,Flask 又太裸。最后我们选了 FastAPI —— 异步支持好、自动生成 OpenAPI、类型提示友好,而且和 Pydantic 配合得天衣无缝。数据库方面,继续用 PostgreSQL,但每个服务一个独立数据库实例(AWS RDS 分开部署)。

服务发现?初期直接用 Consul,配置简单,UI 还能看。后来发现节点不多,干脆改用 Nginx + upstream 静态注册,省事。消息队列选了 RabbitMQ,因为团队有人玩过,Kafka 太重,Redis Streams 又怕丢消息(金融相关,不敢赌)。

组件 选型 理由
Web 框架 FastAPI 异步、文档自动生成、类型安全
数据库 PostgreSQL (per service) ACID 保证、JSONB 支持、DBA 熟悉
服务注册 Nginx static upstream 简单、稳定、运维友好
消息队列 RabbitMQ 可靠、有 DLX、团队有经验
区块链对接 web3.py + 异步 worker 官方库、支持 async

踩坑实录:那些让我半夜惊醒的 Bug

坑 1:分布式事务?不存在的!

最头疼的是“用户兑换积分 → 扣库存 → 上链”这个流程。单体时代一个事务搞定,现在分三个服务,咋保证一致性?

我们试过 Saga 模式,但补偿逻辑太复杂。最后妥协:最终一致性 + 幂等 + 人工兜底

具体做法:

  • reward-service 发起兑换请求,先本地记录“待处理”
  • 通过 RabbitMQ 发送 inventory_deduct 消息
  • inventory-service 扣库存成功后,回发 deduct_success
  • reward-service 收到后,调用区块链合约(异步),成功则更新状态

关键代码(简化版):

# reward-service 中的兑换逻辑
@app.post("/redeem")
async def redeem_points(user_id: int, item_id: int):
    # 1. 本地创建兑换记录(状态: pending)
    redeem_id = await db.create_redeem(user_id, item_id, status="pending")
    
    # 2. 发送扣库存消息(带 redeem_id 用于幂等)
    await rabbitmq.publish("inventory.deduct", {
        "redeem_id": redeem_id,
        "item_id": item_id,
        "user_id": user_id
    })
    
    return {"redeem_id": redeem_id}

inventory-service 收到消息后,先查是否已处理过该 redeem_id(防重复),再扣库存。如果失败,消息进死信队列,人工介入。

吐槽:产品经理说“用户不能看到失败”,结果我们搞了个“延迟到账”页面,其实是后台在重试……用户以为高科技,其实是我们没辙。

坑 2:数据库连接池爆炸

FastAPI 是异步的,但我们用的 asyncpg 连接池默认只开 10 个连接。压测时,只要并发一高,就报 too many connections for role

解决方法:动态调整连接池大小 + 限流

# database.py
from asyncpg import create_pool

DATABASE_URL = "postgresql://user:pass@host:5432/reward_db"

# 根据环境调整 pool_size
POOL_SIZE = 20 if os.getenv("ENV") == "prod" else 5

async def get_db():
    pool = await create_pool(DATABASE_URL, min_size=5, max_size=POOL_SIZE)
    async with pool.acquire() as conn:
        yield conn

同时在 Nginx 层加了 limit_req,防止单个 IP 打爆服务。

坑 3:区块链回调地狱

web3.py 的事件监听是阻塞的!我们一开始直接在 FastAPI 里开线程监听合约事件,结果主线程被卡死。后来改成 Celery + Redis 异步处理:

# blockchain_worker.py
from celery import Celery
from web3 import Web3

app = Celery('blockchain', broker='redis://localhost:6379')

@app.task
def listen_to_chain_events():
    w3 = Web3(Web3.HTTPProvider('https://mainnet.infura.io/v3/...'))
    contract = w3.eth.contract(address=..., abi=...)
    
    # 使用 filter 轮询(非最优,但稳定)
    event_filter = contract.events.Redeem.createFilter(fromBlock='latest')
    while True:
        for event in event_filter.get_new_entries():
            # 处理上链成功事件
            update_redeem_status(event['args']['redeemId'], 'completed')
        time.sleep(15)  # 避免 Infura 限流

血泪教训:别在异步 Web 框架里混用阻塞 I/O,除非你想体验“服务假死”。

效果如何?值不值得?

上线三个月,效果喜人:

  • 数据库负载下降 60%,PostgreSQL 的 active connections 从 80+ 降到 20 以下
  • 新功能上线时间从 2 周缩短到 3 天(独立部署,互不影响)
  • 区块链模块故障不再拖垮整个系统(曾经因为 Infura 限流导致全站不可用)

当然,代价也有:运维复杂度上升,日志得用 ELK 聚合,监控得配 Prometheus + Grafana。但作为 DBA 出身的人,看到每个服务都有清晰的数据边界、合理的索引、干净的慢查询日志……值了!

最后几句真心话

微服务不是银弹,尤其对小团队。如果你的系统还没到“改一行代码全公司加班”的地步,别急着拆。但一旦决定拆,请记住:

  • 数据库隔离是底线(求你们了,别跨服务 join)
  • 异步解耦能救命(消息队列是好朋友)
  • Python 能做微服务,但别硬扛高并发(实在不行就上 Go 写核心模块)

至于区块链?说实话,除了“显得高大上”,它给我们的系统带来的更多是复杂度。但既然老板要,我们就把它关进“reward-service”这个笼子里,尽量少让它出来捣乱。

现在,我终于能在周五晚上十点前关掉 MacBook,而不是对着满屏的 psycopg2.OperationalError 发呆了。这,大概就是微服务给普通程序员最实在的礼物吧。

(完)

评论 0

最热最新
暂无评论
云端小木屋Lv.1
0
影响力
0
文章
0
粉丝