1. 项目概述
去年接手公司电商平台重构项目时,我们面临着一个典型的技术困境:原有单体架构在促销活动期间频繁崩溃,数据库连接池经常被打满。经过技术选型评估,最终决定采用Spring Cloud微服务架构进行改造,其中用户模块和商品模块作为核心业务组件率先完成迁移。这次重构不仅让系统QPS从原来的200提升到5000+,更重要的是建立了可弹性扩展的技术底座。
2. 技术架构设计
2.1 微服务组件选型
在技术栈选择上,我们采用了以下组合方案:
- 服务注册中心:Nacos(替代原计划的Eureka)
- API网关:Spring Cloud Gateway
- 配置中心:Nacos Config
- 服务调用:OpenFeign + Ribbon
- 熔断降级:Sentinel
- 链路追踪:SkyWalking
特别说明:选择Nacos而非Eureka的主要原因是其配置管理一体化特性,实测发现配置变更推送速度比Spring Cloud Config快3倍以上。
2.2 模块划分原则
用户模块与商品模块的拆分遵循了三个核心原则:
- 业务高内聚:用户相关操作(注册/登录/信息管理)全部收敛在user-service
- 数据独立性:用户基础信息与商品信息分库存储
- 性能隔离:将读多写少的商品查询与高频更新的用户行为分离
3. 用户模块实现细节
3.1 关键数据结构设计
用户核心表结构如下:
CREATE TABLE `user` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '雪花算法ID', `username` varchar(64) COLLATE utf8mb4_bin NOT NULL, `password` varchar(128) COLLATE utf8mb4_bin NOT NULL COMMENT 'BCrypt加密', `phone` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL, `status` tinyint NOT NULL DEFAULT '1' COMMENT '0-禁用 1-正常', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_username` (`username`), KEY `idx_phone` (`phone`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;3.2 认证授权方案
采用JWT+OAuth2混合方案实现:
- 密码加密:BCryptPasswordEncoder(迭代次数设为12)
- Token生成:JJWT库生成HS256签名token
- 权限控制:基于Spring Security实现RBAC模型
关键配置示例:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/auth/**").permitAll() .antMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() .and() .addFilter(new JwtAuthenticationFilter(authenticationManager())) .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); } }4. 商品模块核心技术
4.1 多级缓存架构
为解决商品详情页的高并发读取问题,设计了四级缓存方案:
| 缓存层级 | 技术实现 | 命中率 | 响应时间 |
|---|---|---|---|
| 客户端缓存 | HTTP Cache-Control | 30% | <10ms |
| CDN缓存 | 阿里云CDN | 40% | 20-50ms |
| 分布式缓存 | Redis Cluster | 25% | 1-3ms |
| 本地缓存 | Caffeine | 4% | 0.1ms |
4.2 库存扣减方案
采用Redis+Lua脚本实现原子性扣减:
-- KEYS[1]: 库存key -- ARGV[1]: 扣减数量 local stock = tonumber(redis.call('GET', KEYS[1])) if stock >= tonumber(ARGV[1]) then return redis.call('DECRBY', KEYS[1], ARGV[1]) else return -1 end配合定时任务进行数据库同步:
@Scheduled(cron = "0 */5 * * * ?") public void syncStockToDB() { // 获取Redis中所有需要同步的商品ID Set<String> keys = redisTemplate.keys("stock:*"); for (String key : keys) { Long productId = Long.parseLong(key.split(":")[1]); Integer stock = Integer.valueOf(redisTemplate.opsForValue().get(key)); productMapper.updateStock(productId, stock); } }5. 服务通信设计
5.1 Feign最佳实践
定义商品服务接口:
@FeignClient(name = "product-service", configuration = FeignConfig.class, fallbackFactory = ProductClientFallbackFactory.class) public interface ProductClient { @GetMapping("/api/internal/products/{id}") Result<ProductDTO> getProductById(@PathVariable Long id); @PostMapping("/api/internal/products/stock/deduct") Result<Boolean> deductStock(@RequestBody StockDeductDTO dto); }配置类示例:
public class FeignConfig { @Bean public Logger.Level feignLoggerLevel() { return Logger.Level.FULL; } @Bean public Retryer retryer() { return new Retryer.Default(100, 1000, 3); } }5.2 熔断降级策略
Sentinel配置示例:
@PostConstruct public void initFlowRules() { List<FlowRule> rules = new ArrayList<>(); FlowRule rule = new FlowRule(); rule.setResource("getProductById"); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(1000); // 阈值 rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP); rule.setWarmUpPeriodSec(10); // 预热时间 rules.add(rule); FlowRuleManager.loadRules(rules); }6. 生产环境问题实录
6.1 典型故障排查
问题现象:商品查询接口在晚高峰出现周期性超时
排查过程:
- 通过SkyWalking发现Gateway到product-service的延迟突增
- 检查Redis监控发现CPU使用率超过90%
- 分析慢查询日志发现大量
KEYS *操作 - 定位到某个开发在代码中误用了keys命令扫描
解决方案:
- 替换所有keys命令为SCAN迭代
- 对热点商品增加本地缓存
- 调整Redis线程池大小
6.2 性能优化记录
优化前后对比:
| 指标 | 优化前 | 优化后 | 提升幅度 |
|---|---|---|---|
| 用户登录RT | 120ms | 65ms | 45% |
| 商品查询QPS | 800 | 3500 | 337% |
| 订单创建TPS | 50 | 200 | 300% |
关键优化措施:
- 引入Caffeine缓存用户权限数据
- 商品图片迁移到OSS
- 数据库分库分表(用户表按ID哈希,商品表按类目范围)
7. 持续集成方案
7.1 容器化部署
Dockerfile示例:
FROM openjdk:11-jre WORKDIR /app COPY target/user-service-1.0.0.jar ./app.jar ENV JAVA_OPTS="-Xms512m -Xmx512m -XX:MaxMetaspaceSize=256m" EXPOSE 8080 ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar app.jar"]Kubernetes部署片段:
apiVersion: apps/v1 kind: Deployment metadata: name: user-service spec: replicas: 3 selector: matchLabels: app: user-service template: metadata: labels: app: user-service spec: containers: - name: user-service image: registry.example.com/user-service:1.0.0 ports: - containerPort: 8080 resources: limits: cpu: "1" memory: 1Gi7.2 监控告警配置
Prometheus监控指标示例:
- job_name: 'user-service' metrics_path: '/actuator/prometheus' static_configs: - targets: ['user-service:8080'] relabel_configs: - source_labels: [__address__] target_label: instance regex: '(.*):\d+' replacement: '$1'Grafana看板包含的关键图表:
- JVM内存使用趋势
- 接口成功率热力图
- 数据库连接池使用情况
- Redis命中率变化曲线
8. 踩坑经验分享
Feign超时陷阱:默认超时设置过短导致偶发失败
- 解决方案:全局配置超时时间
feign: client: config: default: connectTimeout: 5000 readTimeout: 10000Nacos注册延迟:服务下线后客户端感知延迟
- 解决方案:调整心跳参数
spring.cloud.nacos.discovery.heart-beat-interval=5s spring.cloud.nacos.discovery.heart-beat-timeout=15s分布式事务问题:用户积分与订单状态不一致
- 最终方案:采用本地消息表+定时任务补偿
缓存雪崩防护:商品缓存同时失效导致DB压力骤增
- 实施措施:
- 设置随机过期时间
- 使用永不过期的基准数据+增量过期策略
- 实现缓存重建互斥锁
- 实施措施:
这个项目让我深刻体会到,微服务架构不是简单的技术堆砌,而是需要根据业务特点进行深度定制。特别是在电商场景下,对一致性和可用性的平衡取舍,往往需要结合具体业务场景做出决策。比如在用户注册流程中,我们最终选择了AP而非CP,允许极端情况下短暂的数据不一致,换取更高的系统可用性。