如何构建高可用skill网站:从架构设计到性能优化实战

4次阅读
没有评论

共计 2837 个字符,预计需要花费 8 分钟才能阅读完成。

image.webp

背景痛点分析

随着 skill 网站用户量激增,原有单体架构逐渐暴露出以下问题:

如何构建高可用 skill 网站:从架构设计到性能优化实战

  • 接口响应延迟 :核心接口平均响应时间从 200ms 飙升至 1.2s
  • 数据库负载高 :MySQL CPU 长期维持在 80% 以上,慢查询占比 15%
  • 扩展性差 :垂直扩容成本高,新功能上线频繁引发服务不可用
  • 数据一致性风险 :订单状态与技能库存出现不同步现象

技术选型对比

单体架构 vs 微服务架构

维度 单体架构 微服务架构
开发效率 高(代码集中) 中(需协调多模块)
部署复杂度 低(单包部署) 高(需 CI/CD 流水线)
伸缩性 垂直扩展受限 水平扩展灵活
技术栈统一性 强制统一 可混合使用

为什么选择 Spring Cloud Alibaba

  1. 国产化适配 :完美兼容阿里云基础设施
  2. 组件成熟度
  3. Nacos(服务发现)
  4. Sentinel(流量控制)
  5. RocketMQ(消息队列)
  6. 社区活跃度 :GitHub Star 数超 20k
  7. 开箱即用 :提供分布式事务 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%

避坑指南

缓存雪崩预防

  1. 差异化过期时间 :基础数据设置 30±5 分钟随机过期
  2. 双层缓存策略
    // 伪代码
    LocalCache.get(key, () -> {return RedisCache.get(key, () -> {return DB.query(key);
        });
    });
  3. 熔断降级 :当 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));
    }

容器化部署要点

  1. 资源限制
    # 限制容器内存使用
    resources:
      limits:
        cpu: "2"
        memory: "4Gi"
      requests:
        cpu: "1"
        memory: "2Gi"
  2. 健康检查配置
    livenessProbe:
      httpGet:
        path: /actuator/health
        port: 8080
      initialDelaySeconds: 30
      periodSeconds: 10

思考题

现有架构已实现单机房高可用,请设计跨机房容灾方案,需考虑:
1. 数据同步延迟问题(如 MySQL 异地多活)
2. DNS 流量切换策略
3. 分布式锁的跨机房协调

正文完
 0
评论(没有评论)