共计 2837 个字符,预计需要花费 8 分钟才能阅读完成。
背景痛点分析
随着 skill 网站用户量激增,原有单体架构逐渐暴露出以下问题:

- 接口响应延迟 :核心接口平均响应时间从 200ms 飙升至 1.2s
- 数据库负载高 :MySQL CPU 长期维持在 80% 以上,慢查询占比 15%
- 扩展性差 :垂直扩容成本高,新功能上线频繁引发服务不可用
- 数据一致性风险 :订单状态与技能库存出现不同步现象
技术选型对比
单体架构 vs 微服务架构
| 维度 | 单体架构 | 微服务架构 |
|---|---|---|
| 开发效率 | 高(代码集中) | 中(需协调多模块) |
| 部署复杂度 | 低(单包部署) | 高(需 CI/CD 流水线) |
| 伸缩性 | 垂直扩展受限 | 水平扩展灵活 |
| 技术栈统一性 | 强制统一 | 可混合使用 |
为什么选择 Spring Cloud Alibaba
- 国产化适配 :完美兼容阿里云基础设施
- 组件成熟度 :
- Nacos(服务发现)
- Sentinel(流量控制)
- RocketMQ(消息队列)
- 社区活跃度 :GitHub Star 数超 20k
- 开箱即用 :提供分布式事务 Seata 集成方案
核心实现方案
Redis 热点数据缓存
@RestController
@RequestMapping("/skills")
public class SkillController {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@GetMapping("/{id}")
public Skill getSkill(@PathVariable Long id) {
// TODO: 可扩展添加本地缓存
String cacheKey = "skill:" + id;
Skill skill = (Skill) redisTemplate.opsForValue().get(cacheKey);
if (skill == null) {skill = skillService.getById(id);
// 设置 30 分钟过期,防止冷数据长期占用内存
redisTemplate.opsForValue().set(cacheKey, skill, 30, TimeUnit.MINUTES);
}
return skill;
}
}
RocketMQ 异步任务处理
消息生产者
@Service
public class OrderService {
@Autowired
private RocketMQTemplate rocketMQTemplate;
public void createOrder(OrderDTO dto) {
// 1. 本地事务
orderMapper.insert(dto);
// 2. 发送延时消息(15 分钟后检查未支付订单)Message<OrderCheckMsg> message = MessageBuilder
.withPayload(new OrderCheckMsg(dto.getOrderId()))
.setHeader(MessageConst.PROPERTY_DELAY_TIME_LEVEL, "3") // 对应 15 分钟
.build();
rocketMQTemplate.send("order_check_topic", message);
}
}
消息消费者
@RocketMQMessageListener(
topic = "order_check_topic",
consumerGroup = "order_check_group"
)
public class OrderCheckConsumer implements RocketMQListener<OrderCheckMsg> {
@Override
public void onMessage(OrderCheckMsg message) {Order order = orderService.getById(message.getOrderId());
if (order.getStatus() == OrderStatus.UNPAID) {// TODO: 触发订单超时逻辑}
}
}
ShardingSphere 读写分离
# application-sharding.yml
spring:
shardingsphere:
datasource:
names: master,slave1,slave2
master:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://master-host:3306/skill_db
username: root
password: xxxx
slave1:
# ... 类似配置
slave2:
# ... 类似配置
masterslave:
load-balance-algorithm-type: round_robin
name: ms_skill
master-data-source-name: master
slave-data-source-names: slave1,slave2
props:
sql.show: true
性能验证数据
使用 JMeter 进行压测(100 并发):
| 场景 | QPS | 平均响应时间 | 错误率 |
|---|---|---|---|
| 优化前 | 320 | 310ms | 1.2% |
| 优化后 | 2100 | 45ms | 0.01% |
| 缓存穿透场景 | 1800 | 60ms | 0.05% |
避坑指南
缓存雪崩预防
- 差异化过期时间 :基础数据设置 30±5 分钟随机过期
- 双层缓存策略 :
// 伪代码 LocalCache.get(key, () -> {return RedisCache.get(key, () -> {return DB.query(key); }); }); - 熔断降级 :当 Redis 不可用时自动切换本地缓存
分布式事务处理
- 柔性事务 :采用 RocketMQ 事务消息 + 本地事务表
- 补偿机制 :
@Transactional public void updateSkillInventory(Long skillId, int count) { // 1. 记录操作日志 inventoryLogMapper.insert(new InventoryLog(skillId, count)); // 2. 更新库存 inventoryMapper.decr(skillId, count); // 3. 发送 MQ 消息(若失败会通过日志表补偿)rocketMQTemplate.send("inventory_update", new InventoryUpdateEvent(skillId, count)); }
容器化部署要点
- 资源限制 :
# 限制容器内存使用 resources: limits: cpu: "2" memory: "4Gi" requests: cpu: "1" memory: "2Gi" - 健康检查配置 :
livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10
思考题
现有架构已实现单机房高可用,请设计跨机房容灾方案,需考虑:
1. 数据同步延迟问题(如 MySQL 异地多活)
2. DNS 流量切换策略
3. 分布式锁的跨机房协调
正文完
发表至: 技术分享
近两天内
