聊聊后端架构这些年走过的弯路

优雅_探险家
2026-07-19 21:54
阅读 766

昨晚哄完老二睡觉,已经快十一点了。我轻手轻脚地关上儿童房的门,溜回书房,打开电脑,给自己倒了杯凉透的咖啡。老婆在卧室喊了一句"别熬太晚",我嘴上应着"马上睡",手已经诚实地打开了VS Code。

这就是我,一个两个娃的奶爸程序员,白天在新公司搬砖,晚上等娃睡了才能有点自己的时间。入职这家公司刚满两个月,还在疯狂熟悉业务的阶段。最近项目组在搞架构升级,领导让我牵头调研从单体到云原生的演进方案,正好趁这个机会把思路理一理。

说实话,刚接到这个任务的时候我是有点懵的。上一家公司虽然也做微服务,但说白了就是把一个大单体拆成了几个小单体,本质上还是"换汤不换药"。这次新公司的业务体量完全不一样,日活百万级,高峰期QPS能飙到两三万,原来的架构确实扛不住了。

那个让我决定写这篇文章的夜晚

上周五晚上,线上出了一个P1级事故。

当时我正在给老二冲奶粉,手机突然疯狂震动,钉钉告警群消息刷屏。运维兄弟在群里@all,说核心交易服务挂了,用户下单全部超时。我一手抱着娃,一手掏出笔记本连VPN,看了一眼监控面板——好家伙,CPU直接干到100%,内存也快到极限了。

排查了半个多小时,定位到问题:一个批量导出报表的接口,因为数据量暴增,直接把数据库连接池打满了,连带着其他接口全部阻塞。当时真的想砸电脑,这种"一个接口拖垮整个系统"的事情,在单体架构里简直太常见了。

第二天复盘会上,CTO拍板:架构必须升级,不能再这么裸奔了。于是这个烫手山芋就落到了我手上。

单体架构:成也萧何败也萧何

咱们先聊聊单体架构。说实话,对于初创团队来说,单体架构真的是 yyds。

想当年我刚入行的时候,一个Spring Boot项目打天下,从用户模块到订单模块再到支付模块,全塞在一个工程里。部署也简单,打个jar包往服务器上一扔,java -jar 一跑,齐活。那时候觉得写代码真快乐,改一行代码全量生效,调试也方便,断点一打,整个世界都是你的。

但问题也来得很快。

痛点 具体表现 影响
代码耦合 改个用户模块的bug,不小心把订单模块搞挂了 上线如履薄冰
扩展困难 订单模块压力大,但没法单独扩容 资源浪费严重
技术栈锁定 想引入新技术?对不起,整个项目都得改 技术债务越积越多
部署风险 一个小改动要全量发布 发布频率越来越低
团队协作 十几个人改同一个代码库,冲突不断 每天都在解冲突

我记得最离谱的一次,我们团队8个人,一个迭代下来,光合并代码冲突就花了两天。产品经理还催着要功能,测试同学天天提bug,大家焦头烂额。那时候我就在想,这架构是不是该换了。

微服务:理想很丰满

后来公司上了微服务,用的Spring Cloud全家桶。说实话,刚拆的时候真的很爽,各个服务独立部署,团队各管各的,互不干扰。但很快我们就发现,微服务并不是银弹。

分布式事务就是个头疼的问题。原来一个@Transactional就能搞定的事情,现在要搞什么TCC、Saga,代码复杂度直接翻倍。有一次因为分布式事务没处理好,用户付了钱但订单状态没更新,客诉直接爆了。

服务治理也是个大坑。服务一多,调用链路就变得巨复杂。一个请求进来,可能经过网关、用户服务、订单服务、库存服务、支付服务,任何一环出问题都可能导致整个链路挂掉。我们后来上了SkyWalking做链路追踪,才勉强能看清调用关系。

运维成本更是直线上升。原来几台机器就能搞定,现在光服务就几十个,再加上注册中心、配置中心、消息队列、网关……没有专门的运维团队根本玩不转。

那段时间,我头发掉得比娃的奶粉消耗还快。

云原生:真正的解药?

说回现在这个新项目。CTO要求的"云原生",不是简单地往K8s上一扔就完事了。我们做的调研和方案设计,大概分了这么几个层面:

容器化是第一步

这个没啥好说的,Docker + K8s 是标配。但有几个坑需要提前避开:

# 一个典型的Go服务部署配置
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
      - name: order-service
        image: registry.company.com/order-service:v1.2.3
        resources:
          requests:
            memory: "256Mi"
            cpu: "200m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 15
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5

这里有个细节,resourcesrequestslimits一定要设置合理。我们之前有个服务,limits设太高,结果K8s调度器把它放到了一个资源紧张的节点上,反而导致OOM。后来我们根据实际压测数据,把每个服务的资源配额都精细化了一遍。

为什么我们选了Go

这是方案评审时争议最大的一个点。团队里大部分人都是Java出身,对Go不太熟悉。但我坚持推Go,理由很实在:

启动速度。Java应用启动慢是老大难问题,Spring Boot项目动辄几十秒甚至几分钟的启动时间,在弹性扩缩容场景下是致命的。Go编译出来就是一个二进制文件,启动时间毫秒级,K8s扩容的时候体验完全不一样。

内存占用。同样一个服务,Java版可能要吃掉500MB内存,Go版可能只要50MB。在K8s环境下,这意味着同样的机器能跑更多实例,成本直接降下来。

并发模型。Go的goroutine天生适合高并发场景,写起来也比Java的线程池舒服。最近我在研究一些开源项目的源码,发现很多云原生领域的项目——Docker、K8s、Etcd——都是Go写的,这不是没有原因的。

当然,Go也有短板。泛型支持来得太晚(虽然1.18终于有了),错误处理的if err != nil写到让人怀疑人生,生态相比Java还是差一截。但综合来看,对于云原生场景下的后端服务,Go确实是个更合适的选择。

// Go的错误处理虽然啰嗦,但很清晰
func (s *OrderService) CreateOrder(ctx context.Context, req *CreateOrderReq) (*Order, error) {
    // 参数校验
    if err := req.Validate(); err != nil {
        return nil, fmt.Errorf("invalid request: %w", err)
    }
    
    // 库存扣减
    if err := s.inventoryClient.Deduct(ctx, req.SkuID, req.Quantity); err != nil {
        return nil, fmt.Errorf("deduct inventory failed: %w", err)
    }
    
    // 创建订单
    order, err := s.repo.Create(ctx, req)
    if err != nil {
        // 库存回滚
        s.inventoryClient.Rollback(ctx, req.SkuID, req.Quantity)
        return nil, fmt.Errorf("create order failed: %w", err)
    }
    
    return order, nil
}

服务网格:解耦业务和基础设施

这是我觉得最有意思的一部分。我们引入了Istio做服务网格,把服务治理的能力从业务代码中剥离出来。

以前在代码里要写熔断、限流、重试的逻辑,现在这些全部下沉到Sidecar代理里。业务代码只需要关心业务逻辑,干净了很多。

# Istio VirtualService 配置示例
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: order-service
spec:
  hosts:
  - order-service
  http:
  - route:
    - destination:
        host: order-service
    retries:
      attempts: 3
      perTryTimeout: 2s
    fault:
      abort:
        percentage:
          value: 1
        httpStatus: 500

上面这个配置里,我还加了一个1%的故障注入,用于测试系统的容错能力。这个招数是跟一个开源项目学来的,具体名字我记不太清了,反正就是定期往系统里注入各种故障,看看系统能不能扛住。有点像给系统打疫苗,提前产生抗体。

可观测性:不能当瞎子

云原生架构下,服务数量暴增,如果没有好的可观测性方案,出了问题就是盲人摸象。我们搭了一套完整的可观测性体系:

  • 日志:EFK(Elasticsearch + Fluentd + Kibana),统一收集所有服务的日志
  • 指标:Prometheus + Grafana,监控各种业务和技术指标
  • 链路追踪:Jaeger,追踪请求在各个服务间的调用链路

这里分享一个实战经验:一定要在网关层做好Trace ID的透传。我们之前有个服务,日志里打的Trace ID跟上游传过来的对不上,排查问题的时候简直要命。后来在中间件层统一处理了这个问题,世界终于清净了。

一些关于数据库设计的思考

架构演进不只是应用层的事,数据库层面也要跟着变。

分库分表是绕不过去的话题。我们用的是ShardingSphere,按用户ID做分片键。这里有个坑:如果你的查询场景经常需要按非分片键查询,就会触发全路由,性能会很差。所以分片键的选择一定要结合业务场景来定。

读写分离也要注意数据一致性的问题。我们之前遇到过,主库写完数据立刻去从库读,结果读不到,因为主从同步有延迟。后来加了一个"强制走主库"的逻辑,对于写后立刻要读的场景,直接路由到主库。

-- 分片规则配置示例
spring:
  shardingsphere:
    datasource:
      names: ds0,ds1
    sharding:
      tables:
        t_order:
          actual-data-nodes: ds$->{0..1}.t_order_$->{0..3}
          database-strategy:
            inline:
              sharding-column: user_id
              algorithm-expression: ds$->{user_id % 2}
          table-strategy:
            inline:
              sharding-column: user_id
              algorithm-expression: t_order_$->{user_id % 4}

那些让我又爱又恨的新玩意儿

最近研究云原生的过程中,接触了不少新东西,有几个让我印象特别深。

Manus 这个名字可能很多人还不熟悉。它是一个AI Agent框架,可以自主完成复杂的任务。我在想,以后运维工作是不是可以交给这种AI Agent来做?比如自动扩缩容、自动故障恢复、自动性能调优。虽然目前还比较早期,但这个方向我觉得很有想象空间。想象一下,半夜系统出了问题,不用被钉钉叫醒,Manus自动帮你排查、恢复、发报告,第二天早上起来一看,问题已经解决了。这简直是奶爸程序员的福音啊。

AI视频生成也是一个让我觉得"未来已来"的领域。虽然跟后端架构关系不大,但我最近在用一些AI视频工具给娃做动画故事。技术上来说,AI视频生成背后需要大量的GPU计算和分布式任务调度,这其实也是云原生架构的一个典型应用场景。我们公司的推荐系统团队就在用K8s跑各种模型训练和推理任务,资源调度那块的设计还是挺有参考价值的。

写在最后

凌晨一点半了,老二刚才又醒了一次,老婆起来哄的。我趁着这功夫把文章收尾。

回头看后端架构这些年走过的路,从单体到微服务再到云原生,每一次演进都不是为了追时髦,而是被业务逼的、被线上事故打的、被成本压力逼的。没有最好的架构,只有最合适的架构。

对于正在考虑架构升级的团队,我有几点建议:

  1. 不要为了微服务而微服务。如果你的团队就三五个人,业务也没那么复杂,单体架构完全够用。过早引入微服务只会增加不必要的复杂度。

  2. 云原生的核心不是K8s。而是容器化、不可变基础设施、声明式API、服务网格这些理念。K8s只是实现这些理念的一个工具。

  3. 可观测性先行。在拆服务之前,先把监控、日志、链路追踪搞好。不然拆完之后出了问题,你连问题在哪都不知道。

  4. 小步快跑。不要搞什么"大爆炸"式的重构,一个服务一个服务地迁,迁一个稳定一个。

  5. 团队能力要跟上。技术再好,团队不会用也白搭。我们为了推Go,搞了好几次内部分享,还买了书让大家一起学。

好了,咖啡喝完了,眼皮也开始打架了。明天还要早起送老大上学,这篇文章就写到这吧。

如果你也在做架构升级,或者对云原生有什么想法,欢迎在评论区交流。毕竟,带娃之余能跟同行聊聊技术,也是一种放松嘛。

最后吐槽一句:产品经理今天又提了个需求,说要加个"AI智能客服"功能。我心想,等Manus成熟了,第一个就把你替了。

晚安,各位打工人。

评论 0

最热最新
暂无评论
优雅_探险家Lv.1
0
影响力
0
文章
0
粉丝