这是我参与「第五届青训营 」笔记创作活动的第十二天
系统设计实践
系统设计方法论
系统
- 关联的个体
- 规则运作
- 组成工作的个题
设计
- 设想和计划
- 目的
- 过程安排
流程
- 场景分析
- 存储设计
- 服务设计
- 可扩展性
电商秒杀业务介绍
- 供给侧
- 消费者侧
- 交易环境
-
如何做系统设计
- 4S分析法
-
如何分析系统瓶颈和优化
- 火焰图分析
- 链路分析
- 全链路压测
-
如何验证系统的可用性和稳定性
- 链路梳理
- 可观测性
- 全链路测试
- 稳定性控制
- 容灾演练
秒杀的挑战
课程实践
存储
- Mysql
- Redis
- Localcache(更快)
系统架构
扣减库存
- 先get 然后decrease 不行 不是原子性
- 直接decrease 也不行 不能减到负数
- lua脚本支持原子性可以get 再减
if (redis.call('exists', KEYS[1]) == 1) then
local stock = tonumber(redis.call('get', KEYS[1]))
if (stock == -1) then
return 1
end
if (stock > 0) then
redis.call('incrby', KEYS[1], -1)
return stock - 1
end
return 0
end
return -1
- 消息队列为rocketmq
- 序列化方式为Json如果想要性能更高可以用Google 的protobuf
- 线程数为500(通过压测确定)
- 限流组件通过AOP实现
切面
@Slf4j
@Aspect
@Component
public class RateLimitAspect {
private Map<String, RateLimiter> limiterMap = new HashMap<>();
@Around("@annotation(com.camp.promotion.limit.RateLimit)")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = signature.getMethod();
RateLimit annotation = method.getAnnotation(RateLimit.class);
if (annotation == null) {
return proceedingJoinPoint.proceed();
}
String key = annotation.value();
if (StringUtils.isBlank(key)) {
String className = method.getDeclaringClass().getSimpleName();
String methodName = method.getName();
key = className + "_" + methodName;
}
log.info("rate limit key = {}", key);
RateLimiter limiter = limiterMap.get(key);
if (limiter == null) {
limiter = RateLimiter.create(annotation.rate().getCount());
limiterMap.put(key, limiter);
}
if (!limiter.tryAcquire()) {
throw new BizException(ResponseEnum.OVER_RATE_LIMIT);
}
return proceedingJoinPoint.proceed();
}
}
- 分布式锁 为保证可靠性通过lua脚本进行解锁 每个锁id不同防止误删
local key = KEYS[1]
local content = ARGV[1]
local value = redis.call('get', key)
if value == content then
local delResult = redis.call('del', key)
if delResult == 1 then
return 1
end
return 0
end
return 0