共计 1926 个字符,预计需要花费 5 分钟才能阅读完成。
背景痛点
最近在开发一个用户技能激活系统时,遇到了典型的性能瓶颈问题。当用户量突然激增(比如营销活动期间),系统响应时间从正常的 200ms 飙升到 5 秒以上,甚至出现部分用户激活状态不一致的情况。通过 JMeter 压测发现,在 500 并发请求下:

- 同步处理方式成功率仅 68%
- 95% 响应时间突破 3 秒
- 数据库 CPU 持续 100%
传统同步处理的核心问题在于:
- 数据库行锁竞争导致线程阻塞
- 事务时间长引发连接池耗尽
- 重试机制缺失造成数据不一致
技术方案
分层架构设计
采用三明治架构隔离关注点:
flowchart TD
A[API 层] -->| 事件 | B[RabbitMQ]
B --> C[业务层]
C --> D[Redis 缓存]
D --> E[MySQL 持久化]
事件驱动模型
关键设计决策:
- 用户请求先写入消息队列,立即返回 ” 处理中 ” 状态
- 消费者线程池异步处理,控制数据库并发度
- 通过事件溯源保证最终一致性
DDD 领域模型
核心聚合根设计:
public class UserSkill {
@EmbeddedId
private UserSkillId id; // 包含 userId+skillId 的组合键
@Enumerated(EnumType.STRING)
private ActivationStatus status;
@Version
private Long version; // 乐观锁字段
}
代码实现
服务层核心逻辑
@Transactional
public class SkillActivationService {
// 带 Redisson 分布式锁的激活方法
public void activateSkill(Long userId, Long skillId) {
String lockKey = "lock:skill:" + skillId;
RLock lock = redissonClient.getLock(lockKey);
try {if (lock.tryLock(1, 5, TimeUnit.SECONDS)) {
// 幂等性检查
if (skillRepository.existsByUserAndSkill(userId, skillId)) {return;}
// 发布领域事件
eventPublisher.publishEvent(new SkillActivatedEvent(userId, skillId));
}
} finally {lock.unlock();
}
}
}
消息序列化最佳实践
@Bean
public MessageConverter messageConverter() {
Jackson2JsonMessageConverter converter =
new Jackson2JsonMessageConverter();
converter.setTypeMapper(typeMapper());
return converter;
}
private DefaultClassMapper typeMapper() {DefaultClassMapper mapper = new DefaultClassMapper();
mapper.setTrustedPackages("com.example.events");
return mapper;
}
生产考量
锁性能对比
| 方案 | 吞吐量 (QPS) | 锁获取时间 (ms) | 集群支持 |
|---|---|---|---|
| Redis | 12,000 | 1.2 | 是 |
| ZooKeeper | 3,500 | 8.5 | 是 |
| 数据库行锁 | 2,100 | 15.4 | 否 |
补偿机制设计
- 死信队列处理失败消息
- 定时任务扫描异常状态
- 人工干预接口
冷启动预热
-- 提前加载热点数据到 Redis
INSERT INTO redis_store
SELECT CONCAT('skill:', id), JSON_OBJECT('hot', 1)
FROM skills
WHERE is_popular = TRUE;
避坑指南
JPA 优化技巧
避免 N + 1 查询:
@EntityGraph(attributePaths = {"category"})
@Query("SELECT s FROM Skill s WHERE s.isActive = true")
List<Skill> findAllActiveWithCategory();
时钟同步问题
分布式系统使用 NTP 协议同步时间,关键业务采用数据库时间:
@Query(value = "SELECT NOW()", nativeQuery = true)
Instant getDatabaseTime();
幂等性方案对比
- 数据库唯一索引 :简单但无法区分重试
- 乐观锁版本号 :需配合重试机制
- 状态机校验 :业务逻辑最严谨
思考与延伸
当 skill 激活需要跨多服务协作时(如扣减积分、发送通知),如何设计 Saga 事务方案?是否可以将事件日志作为审计追溯的依据?欢迎在评论区分享你的分布式事务实践。
正文完
