从单体到云原生,我们踩过的那些坑
那个让人又爱又恨的单体
我们最早的系统是一个标准的Spring Boot单体,所有模块打成一个war包扔Tomcat里。订单、用户、库存、支付全在一个代码库里,数据库也是一台MySQL扛所有。
前两年业务量小,这玩意儿其实挺好用。部署简单,本地起服务就能调,排查问题也方便。问题出在去年双11前后,订单量翻了三倍,单体服务的线程池被打满,一个慢查询把整个库拖死,所有接口跟着遭殃。
这就是单体的核心问题:一荣俱荣,一损俱损。一个模块的故障能拖垮整个系统,而且代码越堆越多,改一个地方要重新构建整个项目,CI/CD跑一次要二十多分钟。
拆服务,不是简单的Ctrl+X和Ctrl+V
我们踩的第一个坑是拆分粒度。按业务模块拆成订单、用户、库存三个服务后,服务之间互相调用,网络延迟加了一层,数据一致性开始出问题。原本一个事务能搞定的事情,现在要搞分布式事务。我们试过Seata,配置麻烦,性能也有损耗。后来改成最终一致性,用消息队列做异步补偿,但代码复杂度直接上了一个台阶。
说句实话:如果团队规模不大、业务量也没到瓶颈,别急着拆微服务。拆完之后,部署运维的成本翻了好几倍。
云原生不是上K8s就完事了
拆完服务后开始容器化,上Kubernetes。Dockerfile写起来不难,CI/CD用GitLab Runner,K8s用Helm管理。弹性扩缩容确实爽,但坑也接踵而至。
第一个坑是配置管理。 原来一个application.yml搞定,现在十几个服务每个都有一堆配置。后来引入Nacos做配置中心,但有次同事本地改了配置忘了同步,上线后排查了半天。
第二个坑是日志和监控。 十几个Pod的日志散落在不同节点上。我们上了Prometheus + Grafana + Loki,但告警规则调了好久。有段时间半夜经常收到误报,后来发现是阈值设得太敏感,一个GC停顿就触发CPU告警。
第三个坑是数据库。 服务拆了,数据库没拆干净。好几个服务还在共用同一个MySQL实例,只是分了不同的库。后来逐步把核心库迁移到独立的RDS实例,引入Redis做缓存,才把数据库压力降下来。
接口设计的重新思考
拆服务之后,接口设计也得跟着变。我们主要用gRPC做服务间通信,对外暴露RESTful接口。
教训一:接口一定要考虑版本兼容性。有次服务升级改了返回字段,依赖方没同步更新,线上直接报空指针。后来在接口里加了版本号,/api/v1/xxx和/api/v2/xxx并存,给下游留迁移时间。
教训二:超时和熔断。服务A调服务B,B响应慢,A的线程池也会被拖垮。我们引入Resilience4j做熔断和限流,超时时间设成1秒,失败率达到阈值就快速失败。这个配置调了好久,设太短会误伤,设太长又起不到保护作用。
AI辅助开发的真实体验
今年年初组里开始推DeepSeek辅助开发。说实话,有些场景确实好用。比如写K8s的YAML文件、Dockerfile、Helm模板,这些配置繁琐但模式固定,让AI生成个初版再改,能省不少时间。还有写单元测试,让它生成测试用例,覆盖边界条件,比自己想得全面。
但我也踩过坑。有次让DeepSeek生成Kafka消费者代码,它用了旧版API,跟项目版本不兼容,编译直接报错。还有一次让它优化SQL,给的方案在数据量大的时候反而更慢,因为它没有考虑索引分布。
所以我现在的态度是:AI可以当助手,但不能当主力。核心的业务逻辑、架构设计,还是得自己来。尤其是涉及事务一致性、并发控制的地方,AI生成的代码往往考虑不周全。
最近在关注A2A(Agent-to-Agent)协议,传统微服务之间的调用是同步的、固定的,而A2A让不同的AI Agent之间可以动态发现、协商、协作。未来的后端架构可能不再是固定的服务网格,而是一群智能Agent在动态编排。这个方向还在早期,但值得关注。
写在最后
从单体到微服务再到云原生,我们花了差不多一年半。中间经历了无数次线上事故、半夜救火。回头看,这条路走得对不对?我觉得值。虽然复杂度上去了,但系统的可扩展性、弹性、隔离性确实提升了一个档次。
但架构演进没有银弹。单体和云原生不是对错问题,而是匹配问题。团队规模、业务阶段、技术储备,这些才是决定架构选择的根本因素。

评论 0