三年前接手这个电商返利APP时,我们还在用传统的Spring Boot单体架构。随着用户量从10万暴涨到300万,每天要处理200万笔订单分润计算,原有架构开始暴露出致命问题:每次大促期间CPU利用率直接飙到95%,修改一个返利规则就需要全站发布,新功能上线周期从1周延长到1个月。
最严重的一次事故发生在去年双11,由于佣金计算模块的一个BUG导致整个支付系统雪崩,直接损失了当天37%的订单。这次事件让我们下定决心进行架构改造,目标很明确:
我们首先按照业务边界拆分了六个核心微服务:
技术选型上坚持"稳字当头":
关键决策:没有盲目上K8s,先用Docker Compose做服务编排。这个决定后来证明非常明智,让我们有充足时间完善监控体系。
分润计算涉及最复杂的分布式事务场景。用户下单后需要:
我们对比了三种方案:
最终实现的可靠消息系统包含:
java复制// 发送准备消息
TransactionSendResult sendResult = producer.sendMessageInTransaction(
new Message("order_topic", JSON.toJSONBytes(orderDTO)),
// 本地事务执行器
new LocalTransactionExecutor() {
@Override
public LocalTransactionState execute(Message msg, Object arg) {
return orderService.createOrder((OrderDTO)arg) ?
LocalTransactionState.COMMIT_MESSAGE :
LocalTransactionState.ROLLBACK_MESSAGE;
}
}, orderDTO);
当微服务数量超过20个时,Dubbo的治理能力开始捉襟见肘。我们在测试环境对比了三种方案:
| 方案 | 学习成本 | 性能损耗 | 功能完整性 |
|---|---|---|---|
| Istio + Envoy | 高 | 15%~20% | ★★★★★ |
| Linkerd | 中 | 8%~12% | ★★★☆☆ |
| Dubbo Mesh | 低 | 5%~8% | ★★★★☆ |
最终选择Dubbo Mesh方案,关键改造步骤:
yaml复制apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
dubbo.apache.org/service-mesh: "enabled"
dubbo.apache.org/interface: com.example.RebateService
yaml复制apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
spec:
hosts:
- rebate-service
http:
- route:
- destination:
host: rebate-service-v1
weight: 90
- destination:
host: rebate-service-v2
weight: 10
使用JMeter模拟50万用户并发,对比架构演进前后的关键指标:
| 指标 | 单体架构 | 基础微服务 | Service Mesh |
|---|---|---|---|
| 平均响应时间(ms) | 423 | 287 | 312 |
| 错误率(%) | 1.8 | 0.6 | 0.3 |
| 最大QPS | 12,000 | 28,000 | 25,000 |
| 扩容耗时(min) | 30 | 8 | 3 |
通过Arthas发现分润服务存在严重的Young GC问题:
code复制[arthas@1]$ dashboard
Memory: used=5.2G max=6.0G
GC: YGC=45 FGC=2
优化方案:
bash复制-XX:MetaspaceSize=256M
-XX:MaxMetaspaceSize=512M
-XX:NewRatio=1
-XX:+UseG1GC
java复制// 错误写法
private static Map<Long, RebateRule> cache = new HashMap<>();
// 正确写法
private static LoadingCache<Long, RebateRule> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
最初使用SkyWalking时遇到trace丢失问题,排查发现是线程池未做透传:
java复制// 错误用法
executorService.submit(() -> {
// 丢失TraceId
});
// 正确方案
ExecutorService tracedExecutor = Executors.newFixedThreadPool(
10,
new TracingExecutorService(Executors.defaultThreadFactory())
);
某次修改Nacos配置后,部分节点未及时生效。解决方案:
java复制@RefreshScope
public class RebateConfig {
@Value("${rebate.rate:#{null}}")
private String rate;
@PostConstruct
public void checkVersion() {
if(rate == null) {
throw new IllegalStateException("配置未加载");
}
}
}
在零度以下环境测试时发现,Envoy sidecar启动比应用慢导致流量丢失。最终方案:
yaml复制initContainers:
- name: envoy-wait
image: busybox
command: ['sh', '-c', 'until nc -z 127.0.0.1 15021; do sleep 1; done']
经过18个月的渐进式改造,系统获得显著提升:
但微服务不是银弹,我们为复杂度付出的代价包括:
对于中小型团队,我的建议是: