微服务拆分,我差点把公司系统搞崩了

Tomcat饲养员
2026-01-17 17:29
阅读 1391

上周五晚上十点半,我瘫在工位上盯着屏幕上密密麻麻的链路追踪图,心里一万只草泥马奔腾而过。这已经是连续第三天加班到深夜了,原因很简单——我们那个运行了快五年的单体应用,终于要向微服务架构迈进了。

作为Claude Code的早期尝鲜用户,我平时就喜欢在命令行里折腾各种新工具,但这次的微服务迁移可不是简单的技术升级。我在上海租房住在公司附近,早上8点就能开工,本来以为能从容应对这次架构改造,结果现实狠狠给我上了一课。

事情还得从上个月说起。产品经理拿着一份"区块链+供应链金融"的需求文档找到我们,说是要打造一个去中心化的交易验证平台。我当场就懵了——我们的老系统是用PHP写的单体架构,连基本的模块化都没做好,现在突然要集成区块链,还要支持高并发的交易验证?这不是开玩笑嘛!

更离谱的是,CTO直接拍板:三个月内必须上线,而且要保证99.99%的可用性。我当时真的想砸电脑,但想到下个月的房租还没着落,只能硬着头皮接了下来。

为什么非得拆微服务?

其实我们团队内部对是否要拆微服务一直有争议。运维老王觉得单体应用挺好的,部署简单,排查问题也方便;测试小李担心接口多了测试工作量会爆炸;只有我和几个后端同事觉得,再不拆迟早要出大事。

去年双11期间,我们的订单服务因为一个小小的数据库死锁,导致整个系统瘫痪了40分钟。那时候我就意识到,单体架构已经成了我们的瓶颈。所有的业务逻辑都耦合在一起,改一个功能可能会影响到其他十几个模块,简直就是"牵一发而动全身"。

这次区块链需求成了压垮骆驼的最后一根稻草。区块链相关的交易验证、智能合约调用、数据上链等功能,和我们现有的电商逻辑完全不是一个画风。如果硬塞进单体应用里,代码质量肯定会进一步恶化。

所以,拆!必须拆!

技术选型:Go还是Java?

确定要拆微服务后,第一个大问题是用什么语言重写核心服务。我们现有的系统是PHP,但没人愿意继续用PHP写微服务(别打我,PHP是最好的语言,但在微服务场景下确实不太合适)。

团队里分成了两派:

  • Java派:觉得Spring Cloud生态成熟,各种组件齐全
  • Go派:觉得Go的性能好,部署简单,内存占用低

作为Go的忠实粉丝,我自然是Go派的坚定支持者。但光靠情怀说服不了CTO,得拿数据说话。于是我花了两天时间做了个简单的性能对比测试:

指标 Spring Boot (Java 11) Go (1.19)
启动时间 3.2s 0.15s
内存占用 350MB 15MB
QPS (简单API) 2800 12000
Docker镜像大小 280MB 18MB

看到这个数据,连最顽固的Java派都沉默了。特别是在容器化部署的场景下,Go的优势太明显了。我们的K8s集群资源本来就紧张,用Go能省下不少钱。

更重要的是,Go的goroutine模型特别适合处理区块链场景下的高并发请求。想象一下,每个交易验证都需要和区块链节点通信,这种I/O密集型的操作用Go来处理简直是天作之合。

最终CTO拍板:核心的交易验证服务、区块链交互服务用Go重写,其他边缘服务可以慢慢迁移。

拆分策略:别一口吃成胖子

有了技术栈,接下来就是怎么拆的问题了。我见过太多团队一上来就想把所有功能都拆成微服务,结果把自己玩死了。我们的策略很明确:先拆最痛的点,再逐步扩展

第一步,我们把订单服务独立出来。为什么选订单?因为:

  1. 订单相关的数据库表已经相对独立
  2. 订单业务逻辑复杂,经常需要单独优化
  3. 订单是核心业务,出问题影响最大

第二步,把用户认证和权限管理拆出来。这部分和业务耦合度低,而且很多服务都需要调用。

第三步,也就是最重要的一步——实现区块链交互层。这个服务专门负责:

  • 与区块链节点通信
  • 执行智能合约
  • 验证交易签名
  • 数据上链和查询
// blockchain_service.go
package main

import (
    "context"
    "encoding/json"
    "time"
    
    "github.com/ethereum/go-ethereum/ethclient"
    "github.com/ethereum/go-ethereum/common"
)

type BlockchainService struct {
    client *ethclient.Client
    timeout time.Duration
}

func NewBlockchainService(rpcURL string) (*BlockchainService, error) {
    client, err := ethclient.Dial(rpcURL)
    if err != nil {
        return nil, err
    }
    
    return &BlockchainService{
        client: client,
        timeout: 30 * time.Second,
    }, nil
}

func (bs *BlockchainService) VerifyTransaction(ctx context.Context, txHash string) (bool, error) {
    // 设置超时,避免阻塞
    ctx, cancel := context.WithTimeout(ctx, bs.timeout)
    defer cancel()
    
    hash := common.HexToHash(txHash)
    receipt, err := bs.client.TransactionReceipt(ctx, hash)
    if err != nil {
        return false, err
    }
    
    // 验证交易是否成功(状态码为1)
    return receipt.Status == 1, nil
}

func (bs *BlockchainService) ExecuteSmartContract(ctx context.Context, contractAddr string, data []byte) ([]byte, error) {
    // 这里简化了,实际还需要处理ABI编码、gas估算等
    // 但核心思想是:所有区块链操作都封装在这个服务里
    // 其他服务只需要调用HTTP API,不用关心底层细节
    return nil, nil
}

这个设计的关键在于隔离复杂性。其他微服务完全不需要知道区块链是怎么工作的,它们只需要调用POST /verify-transaction这样的REST API就行了。这样即使以后我们要换区块链平台(比如从Ethereum换成Hyperledger),也只需要修改这一个服务。

服务间通信:别让网络成为瓶颈

微服务拆分后,最大的挑战就是服务间通信。以前在单体应用里,函数调用是纳秒级的,现在变成了网络调用,动辄几十毫秒。

我们考虑过几种方案:

  • RESTful HTTP:简单直接,但性能一般
  • gRPC:性能好,但调试复杂
  • 消息队列:异步解耦,但增加了系统复杂度

最后我们采用了混合策略

  • 同步调用用gRPC(比如订单服务调用用户服务验证权限)
  • 异步通知用Kafka(比如订单创建后通知风控服务)
  • 对外暴露的API还是用RESTful(方便前端和其他系统调用)
// order_service.proto
syntax = "proto3";

package orders;

service OrderService {
  rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse);
  rpc GetOrder(GetOrderRequest) returns (GetOrderResponse);
}

message CreateOrderRequest {
  string user_id = 1;
  repeated OrderItem items = 2;
  string blockchain_tx_hash = 3; // 区块链交易哈希
}

message CreateOrderResponse {
  string order_id = 1;
  bool success = 2;
  string message = 3;
}

选择gRPC还有一个重要原因:强类型契约。在微服务架构中,服务间的接口定义特别重要。gRPC的protobuf文件就像一份合同,明确了每个字段的类型和含义,避免了JSON API那种"猜字段"的痛苦。

不过gRPC也有坑。记得有一次我们升级了protobuf定义,但忘记通知调用方,结果线上服务直接报错。从那以后,我们建立了严格的版本管理流程:任何接口变更都要走Code Review,而且要保证向后兼容。

数据库拆分:最难啃的骨头

如果说服务拆分是外科手术,那数据库拆分就是开颅手术。我们的单体应用用的是MySQL,所有业务数据都在一个数据库里,表之间关系错综复杂。

最开始我想简单粗暴地按服务拆分数据库,每个微服务有自己的数据库。但很快发现这行不通——订单表里有用户ID,商品表里有分类ID,这些关联关系怎么办?

经过几轮讨论,我们采用了领域驱动设计(DDD) 的思路:

  1. 识别聚合根:订单、用户、商品都是独立的聚合根
  2. 消除跨聚合引用:订单表里不再存用户详细信息,只存用户ID
  3. 通过API获取关联数据:需要用户信息时,调用用户服务的API
-- 旧的订单表(单体应用)
CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    user_id BIGINT,
    user_name VARCHAR(100),  -- ❌ 不应该存在的冗余字段
    user_phone VARCHAR(20),  -- ❌ 
    product_id BIGINT,
    product_name VARCHAR(200), -- ❌ 
    product_price DECIMAL(10,2), -- ❌ 
    quantity INT,
    total_amount DECIMAL(10,2),
    status VARCHAR(20),
    created_at TIMESTAMP
);

-- 新的订单表(微服务)
CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    user_id BIGINT NOT NULL,     -- ✅ 只保留外键
    total_amount DECIMAL(10,2) NOT NULL,
    status VARCHAR(20) NOT NULL,
    blockchain_tx_hash VARCHAR(66), -- 区块链交易哈希
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 订单商品表
CREATE TABLE order_items (
    id BIGINT PRIMARY KEY,
    order_id BIGINT NOT NULL,
    product_id BIGINT NOT NULL,  -- ✅ 只保留外键
    quantity INT NOT NULL,
    unit_price DECIMAL(10,2) NOT NULL,
    FOREIGN KEY (order_id) REFERENCES orders(id)
);

这种设计虽然增加了网络调用次数,但保证了数据的一致性和服务的独立性。当然,为了优化性能,我们在应用层做了缓存:

  • 用户信息缓存到Redis,TTL 5分钟
  • 商品信息缓存到本地内存,TTL 1分钟
  • 热点数据预加载

监控和可观测性:别等到出事才后悔

微服务架构下,问题排查难度指数级上升。以前在单体应用里,一个错误日志就能定位问题,现在可能要查十几个服务的日志。

所以我们从一开始就重视监控体系的建设:

  • 日志:统一收集到ELK,用trace_id串联所有服务
  • 指标:Prometheus + Grafana监控QPS、延迟、错误率
  • 链路追踪:Jaeger跟踪完整的请求链路

特别是区块链相关的服务,我们加了额外的监控:

  • 区块链节点的连接状态
  • 智能合约执行的gas消耗
  • 交易确认的延迟时间
# prometheus.yml
scrape_configs:
  - job_name: 'order-service'
    static_configs:
      - targets: ['order-service:8080']
  - job_name: 'blockchain-service'
    static_configs:
      - targets: ['blockchain-service:8080']
    metrics_path: '/metrics'
    params:
      module: [blockchain]

上周五晚上那个让我崩溃的链路追踪图,其实就是因为区块链服务的一个bug导致的。某个智能合约执行超时,但没有正确处理超时异常,导致订单服务一直等待,最终雪崩。有了完整的监控体系,我们才能在30分钟内定位并修复问题。

心得体会:微服务不是银弹

经过这几个月的折腾,我深刻体会到:微服务不是万能的,它解决了一些问题,但带来了更多新问题

微服务适合的场景:

  • 团队规模大,需要独立开发部署
  • 业务复杂度高,需要清晰的边界划分
  • 有特定的性能要求(比如我们的区块链场景)

微服务不适合的场景:

  • 小团队小项目(增加的复杂度远大于收益)
  • 业务逻辑简单,变化不频繁
  • 团队缺乏DevOps能力

对于我们来说,微服务架构最大的价值不是技术上的,而是组织上的。每个小团队可以独立负责一个或几个服务,不用再协调十几个开发人员改同一个代码库。产品经理也能更清晰地理解系统边界,提需求时更有针对性。

至于区块链,说实话我觉得现在很多场景都是为了用而用。但在供应链金融这种需要多方信任的场景下,区块链确实能解决一些传统技术无法解决的问题。关键是要把区块链当作一种工具,而不是目标

现在每天早上8点,我坐在工位上看着各个服务的监控面板,心里踏实多了。虽然偶尔还会遇到半夜报警,但至少不会再出现"改一个按钮样式导致支付失败"这种离谱的事情了。

微服务之路还很长,但至少我们迈出了正确的第一步。如果你也在考虑微服务拆分,记住我的教训:别贪多,别求快,先把最痛的点解决掉

对了,Claude Code最近更新了个新功能,可以直接生成gRPC的客户端代码,省了不少手工编写的时间。作为早期用户,我觉得这波更新相当给力!

评论 0

最热最新
暂无评论
Tomcat饲养员Lv.1
0
影响力
0
文章
0
粉丝