系统设计经典案例

基本步骤:

  1. 拆分功能性需求和非功能性需求
  2. 抽象架构设计:web/app、前端、后端、数据库、缓存、部署、http/websocket、ci/cd
  3. 考虑可优化的点:负载均衡、数据库索引、分库分表、安全防范、分布式
  4. 完善系统抽象设计

基础知识

三高架构设计

  • 高性能:读写分离、缓存、异步、负载均衡
  • 高可用:BASE/最终一致性、集群、超时和重试机制、降级、熔断、限流
  • 高扩展:拆分系统、MQ

系统设计原则

  1. 合适优于先进
  2. 演化优于一步到位
  3. 简单优于复杂

性能优化法则

优化的优先级如下:

  1. SQL、JVM、DB、Tomcat 参数调优
  2. 硬件性能优化
  3. 业务逻辑优化、缓存
  4. 读写分离、集群
  5. 分库分表

秒杀系统

  • 热点数据:放在 Redis 缓存中,并且写入到 JVM 一份,进行多级缓存。一定要设置过期时间和淘汰策略。
  • 静态资源:配置 CDN(内容分发网络)
  • 一主二从三哨兵:通过 Sentinel 哨兵自动实现故障转移,保证集群可用性。
  • 限流:SpringSecurity+Redis+Lua,根据用户 IP 进行拦截限流;MQ
  • 降级:应对系统自身的故障。请求量达到阈值的时候,关闭或降低非核心功能,将资源留给核心业务(产生经济收益的)。
  • 熔断:应对第三方系统的故障。
  • 幂等性:Redisson 分布式锁
  • 性能测试:Jmeter

Feed 流

  • 智能推荐(基于标签)+时间线:实时性、高并发、高性能
  • MySQL 持久化
    • 读写分离:主服务器写,从服务器读。
      • 加一层代理层,如 MySQL Router、MyCat
      • 引入第三方组件:sharding-jdbc
    • 一致性方案
      • Sharding-JDBC 可以设置一些必须获取最新数据的字段,只读取主库。
      • 前端设置一个跳转,为主从同步拖延时间
  • Redis 集群做缓存

短链系统

  • 状态码:302,表示临时重定向。(301 永久重定向)
  • 哈希算法
    • 加密型:MD5、SHA
    • 非加密型:MurmurHash,效率更高
  • 哈希冲突判断
    • 给短链字段添加唯一索引
    • 布隆过滤器
  • 哈希冲突解决:拼接分布式 id(Redis incr)

第三方授权

  • OAuth2.0 通过为第三方应用颁发一个有时效性的令牌Access Token,解决了授权问题,而不是认证问题。
  • 核心流程:客户端向资源拥有者(微信)发送授权申请,用户同意给予客户端授权(授权模式一般采用授权码模式 Authorization Code,一般为 5-20 分钟),客户端使用授权先向认证服务器申请 Access Token 令牌,再向资源服务器申请获取资源。
  • Access Token 失效的时机
    • 用户修改密码
    • 第三方应用将用户冻结或拉入黑名单
    • 用户取消对第三方应用的授权
    • 有效期到,一般为 2-3 小时
      • 可以通过 refresh token 进行刷新(比Access Token时间长,可以续期或获取新的 Access Token)
  • 对于微信登录:用户统一 OAuth2.0 登录请求后,第三方应用通过 code(授权临时票据)+appsecret 换取 access_token
  • 服务器 B 如何获取服务器 A 的 Session 信息:分布式缓存,Redis 集群。

抢订单/抢红包

  • 悲观锁
    • JVM 本地锁:synchronized、ReentrantLock,性能差
    • MySQL 行锁:SELECT...FOR UPDATE SELECT...LOCK IN SHARE MODE ,性能差,存在死锁问题
    • 分布式锁:Redis,需要集群保证可靠性
  • 唯一索引,保证一个订单只与一名用户建立联系

排行榜

小型业务使用 MySQL 的 ORDER BY,数据量大使用 Redis 的 Sorted Set

  • 和 Set 相比,Zset 增加了一个权重参数 score
  • 添加:zadd zset_key 1 user1 2 user2 3 user3
  • 排序:zrevrange zset_key 0 -1 倒序,-1 表示最后一个元素
    • 后缀 withscores 可以同时展示分数
  • 查询分数:zscore zset_key "user1"
  • 查询排名:zrevrank zset_key "user1"
  • 更新数据:zincrby zset_key -2 "user1" 负数代表减

如何实现近7天数据排序?

zunionstore last_n_days n 20231004 20231005...

用不同的天数作为不同的 zset_key ,再合并时间范围内的 zset

大文件上传

  • 分片上传:断点续传、多线程上传
    • 前端:Blob.slice()
    • 后端:RandomAccessFile 类进行合并
  • 秒传:根据文件内容来计算 MD5 的值

统计 UV

  • HyperLogLog:节约服务器资源(10w 数据 Hyperloglog 消耗 10kb,Set 消耗 965kb)
    • PFADD key values
    • PFCOUNT key
    • PFMERGE newkey sourcekey1 sourcekey2
0%