从单体PHP到微服务:一个五年老项目的血泪重构史

几年前的一个凌晨三点,手机响了。我看了一眼屏幕------是监控系统发来的电话报警,这是一级生产事故,手机短信更是几十条。

这是这个月第三次。

我打开笔记本,ssh连到服务器:

  • CPU使用率:98%
  • 内存:16G用了15.8G
  • MySQL连接数:1521/1500(爆了)
  • PHP-FPM进程:全部卡在waiting状态

凌晨三点半,我在键盘上敲着重启命令。凌晨四点,系统恢复。凌晨四点半,我躺回床上。

但我知道,这只是权宜之计。

第二天早上的会议室

当时我们的系统是一个典型的PHP单体应用,运行了5年,代码量30万行。整个架构长这样:

graph TB A[Nginx] --> B[PHP-FPM
200个进程] B --> C[(MySQL主库)] B --> D[(MySQL从库)] B --> E[Redis] B --> F[文件存储] style B fill:#ffcccc style C fill:#ffcccc

所有功能都塞在这一个项目里:用户管理、内容发布、评论系统、消息通知、数据统计、后台管理...改一行代码要重新部署整个系统,测试要测所有功能。

CTO拍了拍桌子:"必须重构了,改成微服务。"

我心里是拒绝的。

网上那些微服务的文章我都看过。服务拆分、网关、注册中心、配置中心、分布式事务...光是看着就头大。而且我们团队只有8个人,产品经理每周都在催新功能,哪有时间搞这些?

但架不住系统真的撑不住了。

第一步:别想着一次性重写

我们技术团队一共8个人。产品经理每周都在催新功能,根本不可能停下来搞三个月大重构。

所以定了个原则:老系统继续跑,新功能用新服务开发,慢慢蚕食老系统。

第一刀:砍掉短信服务

为什么先选短信?因为它最独立:

  • 只有发送短信一个功能
  • 其他模块只是调用,不关心内部实现
  • 挂了也不影响核心业务(大不了短信晚点发)

我用Spring Boot写了一个短信微服务,技术栈选了Spring Cloud Alibaba。为什么选这套?主要是:

  • 团队之前用过Spring,上手快
  • Nacos既能做注册中心又能做配置中心,省事
  • 中文文档多,遇到问题好搜

部署流程是这样的:

graph TB A[Nacos注册中心] B[短信服务
Spring Boot] --> A C[PHP老系统] -.HTTP调用.-> B D[Gateway网关] --> B style B fill:#ccffcc style C fill:#ffffcc

关键配置:

yaml 复制代码
# application.yml
spring:
  application:
    name: sms-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        
server:
  port: 8081

关键点在于:

  1. 短信服务启动时自动注册到Nacos
  2. PHP老系统里,原来的发短信代码改成HTTP调用新服务
  3. 如果新服务挂了,降级回老的短信发送逻辑

代码改动很小:

php 复制代码
// 老代码
function sendSMS($phone, $content) {
    // 直接调用短信SDK
    $smsClient->send($phone, $content);
}

// 新代码
function sendSMS($phone, $content) {
    try {
        // 先尝试调用新服务
        $response = $httpClient->post('http://sms-service/send', [
            'phone' => $phone,
            'content' => $content
        ]);
        return $response;
    } catch (Exception $e) {
        // 降级:新服务挂了就用老方式
        $smsClient->send($phone, $content);
    }
}

上线第一周,新短信服务处理了10%的流量。观察了一周没问题,逐步放量到100%。

这个过程中学到的第一个教训:千万别一上来就把流量全切过去。 我们同事负责的另一个项目,直接全量切换,结果新服务有个bug没测出来,导致一天内5万条短信发送失败。

第二步:Spring Cloud Gateway替代Kong

短信服务拆出去后,接着拆了消息推送服务、文件上传服务。问题来了:

  • 手机App要调三个服务的接口,每个服务地址都硬编码在App里
  • 每个服务都要做一遍JWT验证,重复代码到处都是
  • 前端同学抱怨:以前调一次接口就行,现在要调三次,体验变差了

这时候我们引入了Spring Cloud Gateway作为API网关。为什么不用Kong?因为我们已经选了Spring全家桶,Gateway跟Nacos集成更方便。

sequenceDiagram participant App participant Gateway participant Nacos participant 短信服务 participant 推送服务 短信服务->>Nacos: 注册服务 推送服务->>Nacos: 注册服务 App->>Gateway: 发送验证码请求 Gateway->>Gateway: JWT验证(统一鉴权) Gateway->>Nacos: 查询短信服务地址 Nacos-->>Gateway: 返回服务实例列表 Gateway->>短信服务: 转发请求(负载均衡) 短信服务-->>Gateway: 返回结果 Gateway-->>App: 返回

Gateway的配置:

yaml 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: sms-service
          uri: lb://sms-service  # lb表示从Nacos负载均衡
          predicates:
            - Path=/api/sms/**
          filters:
            - StripPrefix=1
            
        - id: push-service
          uri: lb://push-service
          predicates:
            - Path=/api/push/**
          filters:
            - StripPrefix=1

网关做的事情:

  1. 统一鉴权:JWT验证写一次,所有服务复用
  2. 动态路由:从Nacos自动发现服务,不用写死IP地址
  3. 负载均衡:Ribbon自动在多个服务实例间分配请求
  4. 限流保护:用Sentinel做限流,某个用户请求太频繁直接拦截

上线网关后,遇到的第一个坑:Gateway成了单点故障。

有一次Gateway挂了,所有请求都502。后来我们部署了3个Gateway实例,前面用Nginx做负载均衡:

graph TB A[Nginx负载均衡] --> B[Gateway-1] A --> C[Gateway-2] A --> D[Gateway-3] B --> E[Nacos
服务发现] C --> E D --> E E --> F[后端微服务]

第三步:数据库拆分的噩梦

到2023年初,我们已经拆出来了6个微服务。但有个大问题一直没解决:所有服务还在共用一个MySQL数据库。

这违背了微服务的核心原则。但我们不敢拆,因为:

  • 用户表被10个地方引用
  • 订单表和评论表有外键关联
  • 到处都是JOIN查询

直到有一天,DBA说MySQL主库快撑不住了,必须拆。

第一次尝试:直接拆库

我们先拿评论服务开刀,给它分配了独立的数据库。

graph TB A[用户服务] --> B[(用户库)] C[内容服务] --> D[(内容库)] E[评论服务] --> F[(评论库
新建)] style F fill:#ccffcc

问题马上来了:评论列表要显示用户昵称,但用户数据在另一个库里。

错误方案一:评论服务直连用户数据库查询

  • 违背了微服务原则,服务间耦合

错误方案二:每次都调用用户服务接口查询

  • 性能太差,展示100条评论要调100次用户服务

最终方案:数据冗余

  • 评论表里加一个user_name字段,发评论时就存进去
  • 用户改昵称时,发消息通知评论服务更新
sql 复制代码
-- 评论表设计
CREATE TABLE comments (
    id INT PRIMARY KEY,
    user_id INT,
    user_name VARCHAR(50),  -- 冗余字段
    content TEXT,
    created_at TIMESTAMP
);

很多人觉得这样不"优雅",数据重复存储了。但这就是微服务的代价:为了服务独立,必须接受一定程度的数据冗余。

分布式事务:能避免就避免

最头疼的是发布内容的流程:

  1. 内容服务:创建内容记录
  2. 用户服务:用户积分+10
  3. 消息服务:给粉丝发通知

三个操作要么都成功,要么都失败。以前在一个数据库里,一个事务就搞定。现在跨服务了,怎么办?

网上看到的方案:

  • 两阶段提交(2PC):太慢,而且协调者挂了就全卡住
  • TCC:要写三倍的代码,复杂度爆炸
  • Seata的AT模式:听起来不错,但我们团队没人用过,不敢上生产

我们的实际做法:RocketMQ + 补偿

sequenceDiagram participant 客户端 participant 内容服务 participant RocketMQ participant 用户服务 participant 消息服务 客户端->>内容服务: 发布内容 内容服务->>内容服务: 1. 创建内容(写数据库) 内容服务->>RocketMQ: 2. 发消息: 内容已创建 内容服务-->>客户端: 返回成功 RocketMQ->>用户服务: 消费消息 用户服务->>用户服务: 积分+10 用户服务->>用户服务: 失败了怎么办?
写入补偿任务表 RocketMQ->>消息服务: 消费消息 消息服务->>消息服务: 发通知给粉丝 Note over 用户服务: 定时任务每分钟扫描
补偿任务表,重试失败的操作

内容服务的代码:

java 复制代码
@Service
public class ContentService {
    
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    
    public void publishContent(Content content) {
        // 1. 保存内容到数据库
        contentMapper.insert(content);
        
        // 2. 发送MQ消息
        ContentEvent event = new ContentEvent();
        event.setContentId(content.getId());
        event.setUserId(content.getUserId());
        
        rocketMQTemplate.convertAndSend("content-topic", event);
    }
}

用户服务消费消息:

java 复制代码
@Service
@RocketMQMessageListener(
    topic = "content-topic",
    consumerGroup = "user-service-group"
)
public class ContentEventListener implements RocketMQListener<ContentEvent> {
    
    @Override
    public void onMessage(ContentEvent event) {
        try {
            // 增加用户积分
            userMapper.addPoints(event.getUserId(), 10);
        } catch (Exception e) {
            // 失败了写入补偿任务表
            compensationMapper.insert(new CompensationTask(
                "add_points", 
                event.getUserId(), 
                10
            ));
        }
    }
}

这个方案的核心:

  1. 内容创建成功后,立即返回给用户
  2. 后续操作异步进行,允许短暂的不一致
  3. 失败了用补偿机制兜底,最终会一致

有一次用户服务挂了2小时,积分没加上。但重启后,补偿任务把这2小时的积分都补上了,用户无感知。

第四步:监控体系搭建

6个微服务上线后,排查问题变成了噩梦。

用户投诉:"我发布内容失败了。"

我要查什么?

  • 先看Gateway日志,看请求有没有进来
  • 再看内容服务日志,看有没有报错
  • 如果内容服务调用了用户服务,还要看用户服务日志
  • 还要看RocketMQ有没有消息堆积

一个问题要翻5个地方的日志,还要对着时间戳猜哪条日志是同一个请求的。

ELK日志聚合:别再登服务器

第一步是把所有日志收集到一起。我们用的是ELK技术栈:

  • Elasticsearch:存储日志
  • Logstash:收集和处理日志
  • Kibana:查询界面

架构是这样的:

graph TB A[内容服务] -->|Logback| B[Logstash] C[用户服务] -->|Logback| B D[消息服务] -->|Logback| B E[Gateway] -->|Logback| B B --> F[Elasticsearch
存储日志] F --> G[Kibana
查询界面]

每个Spring Boot服务的配置:

xml 复制代码
<!-- pom.xml -->
<dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>7.2</version>
</dependency>
xml 复制代码
<!-- logback-spring.xml -->
<configuration>
    <appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
        <destination>logstash-server:5000</destination>
        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
            <customFields>{"service":"content-service"}</customFields>
        </encoder>
    </appender>
    
    <root level="INFO">
        <appender-ref ref="LOGSTASH"/>
    </root>
</configuration>

好处是什么?以前要ssh登录6台服务器看日志,现在在Kibana里搜索关键字,所有服务的相关日志都出来了。

SkyWalking链路追踪:找到慢在哪

但光有日志还不够,我需要知道一个请求经过了哪些服务,每一步花了多长时间。

这时候我们引入了SkyWalking。它是Apache的开源项目,跟Spring Cloud Alibaba集成特别好。

部署很简单,每个服务启动时加上agent:

bash 复制代码
java -javaagent:/path/to/skywalking-agent.jar \
     -Dskywalking.agent.service_name=content-service \
     -Dskywalking.collector.backend_service=skywalking-server:11800 \
     -jar content-service.jar

SkyWalking会自动:

  1. 给每个请求分配一个TraceID
  2. 记录请求经过的所有服务
  3. 记录每个服务的处理时间
  4. 记录数据库查询、HTTP调用的耗时
graph LR A[Gateway
TraceID: abc123
10ms] --> B[内容服务
TraceID: abc123
50ms] B --> C[用户服务
TraceID: abc123
200ms] B --> D[RocketMQ
TraceID: abc123
5ms] style C fill:#ffcccc

有一次用户说"发布内容很慢",我在SkyWalking界面一看,发现用户服务的一个SQL查询花了180ms。原来是忘了加索引。

排查问题从以前的半天,缩短到现在的5分钟。

第五步:Sentinel熔断降级

2023年6月,遇到了一次严重故障。

消息推送服务调用了第三方推送平台的API,那天第三方平台挂了。结果是:

  • 消息服务疯狂重试,把自己的CPU打满
  • 因为消息服务慢,调用它的内容服务也开始超时
  • 内容服务慢,导致Gateway的请求队列堆积
  • 最终整个系统都不可用

一个第三方服务的故障,把我们整个系统拖垮了。

这次之后,我们在所有服务间调用上都加了Sentinel做熔断降级。为什么选Sentinel不选Hystrix?因为:

  1. Hystrix已经停止维护了
  2. Sentinel是阿里开源的,跟Spring Cloud Alibaba原生集成
  3. Sentinel有可视化的控制台,规则可以动态修改
stateDiagram-v2 [*] --> 关闭状态 关闭状态 --> 打开状态: 失败率超过50% 打开状态 --> 半开状态: 10秒后 半开状态 --> 关闭状态: 调用成功 半开状态 --> 打开状态: 调用失败 note right of 关闭状态: 正常调用下游服务 note right of 打开状态: 直接返回降级响应
不调用下游 note right of 半开状态: 放一个请求试探
看下游是否恢复

内容服务调用消息服务的代码:

java 复制代码
@Service
public class ContentService {
    
    @Autowired
    private MessageServiceClient messageServiceClient;
    
    @SentinelResource(
        value = "publishContent",
        blockHandler = "publishContentBlockHandler",
        fallback = "publishContentFallback"
    )
    public void publishContent(Content content) {
        // 1. 保存内容
        contentMapper.insert(content);
        
        // 2. 调用消息服务通知粉丝
        messageServiceClient.notifyFollowers(content.getUserId());
    }
    
    // 降级处理:消息服务挂了,先不通知,后台补偿
    public void publishContentFallback(Content content, Throwable e) {
        contentMapper.insert(content);
        // 写入补偿任务表,定时任务后续会补发通知
        compensationMapper.insert(new NotifyTask(content.getId()));
        log.warn("消息服务降级,写入补偿任务");
    }
    
    // 限流处理:请求太多直接拒绝
    public void publishContentBlockHandler(Content content, BlockException e) {
        throw new BusinessException("系统繁忙,请稍后重试");
    }
}

Sentinel的规则配置:

java 复制代码
@Configuration
public class SentinelConfig {
    
    @PostConstruct
    public void initRules() {
        // 熔断规则
        List<DegradeRule> rules = new ArrayList<>();
        DegradeRule rule = new DegradeRule();
        rule.setResource("publishContent");
        rule.setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType());
        rule.setCount(0.5); // 错误率超过50%
        rule.setTimeWindow(10); // 熔断10秒
        rule.setMinRequestAmount(5); // 最少5个请求才统计
        rules.add(rule);
        
        DegradeRuleManager.loadRules(rules);
    }
}

实际效果:第三方推送平台再次出问题时,Sentinel在1秒内熔断了对消息服务的调用,内容发布功能正常工作,只是粉丝通知延迟了。等第三方平台恢复后,补偿任务把延迟的通知都补发了。

第六步:配置中心和服务发现的实战

到2023年底,我们已经有12个微服务在运行。这时候新问题来了:

场景一:修改一个配置要重启所有服务

比如修改Redis的连接地址,要改12个服务的配置文件,然后重启12个服务。一次配置调整要花半小时。

场景二:服务实例动态扩容

业务高峰期,我们要临时启动几个服务实例。但前面的Nginx配置是写死的IP地址,每次扩容都要手动改Nginx配置。

这两个问题,Nacos都能解决。

Nacos配置中心:一处修改,全局生效

我们把所有配置都迁移到了Nacos配置中心:

graph TB A[Nacos配置中心] B[内容服务
实例1] -.动态拉取.-> A C[内容服务
实例2] -.动态拉取.-> A D[用户服务] -.动态拉取.-> A E[消息服务] -.动态拉取.-> A style A fill:#e1f5ff

服务的配置文件简化成这样:

yaml 复制代码
# bootstrap.yml (每个服务都一样)
spring:
  application:
    name: content-service
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yaml
        group: DEFAULT_GROUP

真正的配置都在Nacos里:

yaml 复制代码
# Nacos中的content-service.yaml
spring:
  datasource:
    url: jdbc:mysql://192.168.1.100:3306/content_db
    username: root
    password: xxx
    
redis:
  host: 192.168.1.101
  port: 6379
  
rocketmq:
  name-server: 192.168.1.102:9876

最爽的地方:在Nacos界面修改配置后,所有服务不用重启,30秒内自动生效。

java 复制代码
@RestController
@RefreshScope  // 关键注解:支持配置动态刷新
public class ContentController {
    
    @Value("${content.max-size}")
    private int maxSize;  // 配置改了,这个值会自动更新
    
    @PostMapping("/publish")
    public void publish(@RequestBody Content content) {
        if (content.getLength() > maxSize) {
            throw new BusinessException("内容超长");
        }
        // ...
    }
}

有一次线上出了个bug,是因为配置值写错了。以前要重新打包、发版、重启,至少要20分钟。现在直接在Nacos改配置,1分钟搞定。

Nacos服务发现:自动感知实例变化

之前我们的Nginx配置是这样的:

nginx 复制代码
upstream content-service {
    server 192.168.1.10:8081;  # 写死的IP
    server 192.168.1.11:8081;
}

这意味着每次扩容或缩容,都要改Nginx配置,然后reload。

现在有了Nacos服务发现:

sequenceDiagram participant 新实例 participant Nacos participant Gateway 新实例->>Nacos: 启动时注册
IP: 192.168.1.15
端口: 8081 Nacos-->>新实例: 注册成功 Note over Nacos: 服务列表更新
content-service: 3个实例 Gateway->>Nacos: 定期拉取服务列表 Nacos-->>Gateway: 返回最新实例列表
[10, 11, 15] Gateway->>Gateway: 更新路由表 Note over Gateway: 现在3个实例都会收到请求

实际效果:晚上8点业务高峰,我在服务器上执行:

bash 复制代码
java -jar content-service.jar --server.port=8082 &
java -jar content-service.jar --server.port=8083 &

30秒内,这两个新实例就自动加入了负载均衡,开始分担流量。完全不用改任何配置文件。

现状:两年后的架构

现在是2024年12月,距离开始重构已经过去了两年半。我们的架构变成了这样:

graph TB A[用户] --> B[Nginx] B --> C[Gateway
集群x3] C --> D[Nacos
注册&配置中心] D --> E[用户服务] D --> F[内容服务] D --> G[评论服务] D --> H[消息服务] D --> I[推送服务] D --> J[搜索服务] D --> K[统计服务] D --> L[支付服务] E --> M[(用户库)] F --> N[(内容库)] G --> O[(评论库)] E --> P[Redis集群] F --> P G --> P F --> Q[RocketMQ] H --> Q I --> Q R[ELK] -.收集日志.-> E R -.收集日志.-> F R -.收集日志.-> G S[SkyWalking] -.链路追踪.-> C S -.链路追踪.-> E S -.链路追踪.-> F style C fill:#ccffcc style D fill:#e1f5ff style P fill:#fff4cc style Q fill:#ffe6cc

关键数据对比:

指标 2022年(单体) 2024年(微服务)
服务数量 1个 15个
代码库 1个30万行 15个(平均1-2万行)
部署时间 15分钟(全量) 3分钟(单个服务)
数据库 1个MySQL 8个独立库
平均响应时间 450ms 180ms
日请求量 100万 500万
半夜被叫醒次数 月均3次 月均0.5次

血泪教训:我们踩过的坑

坑1:过度拆分

一开始我们把服务拆得太细了。比如"文件上传服务"、"文件下载服务"、"文件删除服务"分成了三个服务。

结果:

  • 维护成本剧增:三个代码库、三套部署、三套监控
  • 调用链路变长:上传一个文件要调三个服务
  • 没有任何性能提升

后来我们把它们合并成一个"文件服务"。

教训:拆分的粒度要以业务能力为准,不是功能越细越好。

坑2:忽略了网络延迟

单体应用里,调用一个方法就是内存操作,几乎没有延迟。

微服务里,每次调用都是HTTP请求:

  • 建立连接:5ms
  • 序列化数据:2ms
  • 网络传输:10ms
  • 反序列化:2ms
  • 总耗时:约20ms

一个页面展示如果要调用5个服务,延迟就是100ms。

我们的解决方案:

  1. 能批量查询的就批量查询
  2. 前端合理聚合请求,减少接口调用次数
  3. 关键路径用异步处理

教训:微服务不是银弹,会引入网络延迟。要权衡利弊。

坑3:没有做好监控就上线

消息服务最开始没有接入SkyWalking,结果出了问题完全不知道是哪个环节慢。

后来有一次故障,光排查问题就花了2小时。

教训:监控和告警必须在服务上线前就配置好,不是出问题了再补。

坑4:分布式事务滥用

看到网上的文章说要用Seata,我们就尝试引入。结果:

  • 团队没人真正理解Seata的原理
  • 调试困难,出问题不知道怎么排查
  • 反而增加了系统复杂度

后来我们把大部分"分布式事务"改成了最终一致性方案,反而更稳定了。

教训:不要为了用新技术而用新技术。如果业务可以接受最终一致性,就不要强上分布式事务。

给想做微服务改造的团队的建议

1. 先问自己:真的需要微服务吗?

如果你的系统:

  • 日请求量<10万
  • 团队<5人
  • 开发速度比性能重要

建议:先别拆。 把单体应用优化好,加缓存、做数据库读写分离、优化SQL,性能提升可能比微服务还好。

微服务的代价:

  • 开发成本:调试困难,本地联调复杂
  • 运维成本:要维护注册中心、配置中心、网关、监控系统
  • 学习成本:团队要学新技术栈

如果团队压力大、人员紧张,贸然上微服务可能雪上加霜。

2. 渐进式改造,不要推倒重来

我见过太多失败的案例:

  • 产品说等你们三个月,三个月后新系统上线
  • 结果开发发现低估了复杂度,三个月变成了六个月
  • 老系统还要继续改bug,新系统进度延期
  • 最后新系统草草上线,问题一堆

正确做法:

  1. 老系统继续跑,满足日常需求
  2. 新功能用微服务开发
  3. 老系统的模块,找到边界清晰的先拆出来
  4. 一个一个慢慢蚕食,用1-2年时间完成迁移

3. 技术选型:够用就好

我们选Spring Cloud Alibaba,不是因为它最好,而是因为:

  • 团队熟悉Spring
  • 中文文档多,出问题能搜到答案
  • 社区活跃,有问题能找到人问

相反,如果选了某个特别新、特别酷的技术,出了问题可能连Google都搜不到答案。

4. 监控比你想象的重要100倍

不夸张地说,监控系统救了我们无数次。

必须要有的监控:

  • 日志聚合(ELK):所有日志集中查看
  • 链路追踪(SkyWalking):知道请求经过了哪些服务
  • 指标监控(Prometheus):CPU、内存、QPS实时监控
  • 业务监控:支付成功率、注册转化率等关键指标

这些监控系统,建议在第一个微服务上线前就搭建好。

5. 文档和规范:省不了

微服务多了以后,如果没有统一的规范,会一团乱:

  • 有的服务用RESTful,有的用RPC
  • 有的返回驼峰,有的返回下划线
  • 异常处理方式五花八门

我们后来定了几个规范:

  • 统一的错误码设计
  • 统一的日志格式
  • 统一的接口返回结构
  • 每个服务必须有接口文档(用Swagger)

虽然前期会觉得麻烦,但长远看能省很多沟通成本。

写在最后

从那个凌晨电话,到现在系统稳定运行,这两年半我们学到了很多。

微服务不是银弹,它解决了我们的扩展性问题,但也带来了复杂度。如果重新选择,我可能会:

  • 更晚才开始拆分,把单体优化到极限
  • 初期拆得更保守,只拆最必要的部分
  • 在拆分前先把监控体系搭建好

但不管怎样,对于我们当时的场景,微服务改造是正确的选择。系统稳定了,我也终于可以睡个安稳觉了。

如果你的团队也在考虑微服务改造,希望我们的经验能帮到你。记住:没有完美的架构,只有合适的架构。


2025年12月记于上海

作者在某互联网公司任后端架构师,负责过多个系统的微服务改造。本文所有技术细节均来自真实项目经验,部分业务数据做了脱敏处理。

相关推荐
小镇cxy1 小时前
VibeCoding实践,Spec+Claude Code小程序开发
后端·claude·vibecoding
GeekPMAlex1 小时前
深入理解 Python 元组、哈希、堆与 enumerate
后端
shark_chili1 小时前
基于arthas量化监控诊断java应用方法论与实践
后端
+VX:Fegn08951 小时前
计算机毕业设计|基于springboot + vue在线考试管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
okseekw1 小时前
Java泛型从入门到实战:原理、用法与案例深度解析
java·后端
core5121 小时前
[硬核解析] 从感知到交互:InternVideo 1/2/2.5 全系列架构演进与原理解析
架构·大模型·交互·视频·video·intern
雨中飘荡的记忆1 小时前
Spring WebFlux详解
java·后端·spring
文攀1 小时前
Go 语言 GMP 调度模型深度解析
后端·go·编程语言
银嘟嘟左卫门1 小时前
使用openEuler进行多核性能测评,从单核到多核的极致性能探索
后端