系统设计实践 | 青训营笔记

60 阅读1分钟

这是我参与「第五届青训营 」笔记创作活动的第十二天

系统设计实践

系统设计方法论

系统

  • 关联的个体
  • 规则运作
  • 组成工作的个题

设计

  • 设想和计划
  • 目的
  • 过程安排

流程

  • 场景分析
  • 存储设计
  • 服务设计
  • 可扩展性

电商秒杀业务介绍

  • 供给侧
  • 消费者侧
  • 交易环境
  • 如何做系统设计

    • 4S分析法
  • 如何分析系统瓶颈和优化

    • 火焰图分析
    • 链路分析
    • 全链路压测
  • 如何验证系统的可用性和稳定性

    • 链路梳理
    • 可观测性
    • 全链路测试
    • 稳定性控制
    • 容灾演练

秒杀的挑战

image.png

课程实践

存储

  • Mysql
  • Redis
  • Localcache(更快)

系统架构

image.png

扣减库存

  • 先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