场景题和系统设计

分布式id

  • 唯一
  • 高可用
  • 高性能:生成速度快、资源消耗小
  • 有序性:趋势递增
  • 安全:需要脱敏,不能包含订单数量等敏感信息

数据库主键自增

数据库号段

其实就是批量获取一批号,current_max_idcurrent_max_id + step,减少数据库的访问次数

1
2
3
4
5
6
7
8
CREATE TABLE `sequence_id_generator` (
`id` int(10) NOT NULL,
`current_max_id` bigint(20) NOT NULL COMMENT '当前最大id',
`step` int(10) NOT NULL COMMENT '号段的长度',
`version` int(20) NOT NULL COMMENT '版本号',
`biz_type` int(20) NOT NULL COMMENT '业务类型',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

上面两种都存在单点问题和安全问题(可以推算出订单量),而且id没有业务含义

redis incr

UUID

  • 优点:生成速度快
  • 缺点:存储消耗空间大、无序、基于mac地址生成的uuid不安全、无业务含义、重复问题

snowflake

1
2
3
4
*------*------------*----------------*-------------*-----------*
| sign | timestamp | datacenter id | worker id | sequence |
*------*------------*----------------*-------------*-----------*
1bits 41bits 5bits 5bits 12bits
  • sign:默认为0,表示正数
  • timestamp:时间戳,默认为ms
  • sequence:单台机器每毫秒能产生的最大id数

优缺点

  • 优点:生成速度快,id有序递增
  • 缺点:重复id(时间回拨),依赖机器id对分布式环境不友好

分布式锁实现

条件

  • 存储空间,可以访问到
  • 唯一标识
  • 至少两种状态

典型实现

  • Zookeeper:先创建临时顺序节点,然后get得到所有创建的子节点,如果序号最小,认为得到锁,否则监视minmax节点,尝试继续获取锁,释放锁则删除节点。临时节点可以在网络故障的时候自动解锁,防止死锁。
  • Redis:setnx,判断超时,getset设置新值返回旧值,如果还是超时说明获取了锁,否则获取失败。(会有全局时钟问题)
  • MySQL

MySQL分布式锁

建表

1
2
3
4
5
6
CREATE TABLE distributed_locks (
lock_name VARCHAR(255) NOT NULL PRIMARY KEY,
locked_by VARCHAR(255) NOT NULL,
locked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL
);

加锁

1
2
3
4
5
6
INSERT INTO distributed_locks (lock_name, locked_by, locked_at, expires_at)
VALUES ('my_lock', 'unique_client_id', CURRENT_TIMESTAMP, DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 30 SECOND))
ON DUPLICATE KEY UPDATE
locked_by = IF(expires_at < CURRENT_TIMESTAMP, 'unique_client_id', locked_by),
locked_at = IF(expires_at < CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, locked_at),
expires_at = IF(expires_at < CURRENT_TIMESTAMP, DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 30 SECOND), expires_at);

解锁

1
DELETE FROM distributed_locks WHERE lock_name = 'my_lock' AND locked_by = 'unique_client_id';

select for update

加锁

1
2
3
4
5
6
7
8
START TRANSACTION;
SELECT * FROM distributed_locks WHERE lock_name = 'my_lock' FOR UPDATE;

-- 如果记录不存在,插入新锁记录
INSERT INTO distributed_locks (lock_name, locked_by)
SELECT 'my_lock', 'unique_client_id'
WHERE NOT EXISTS (SELECT * FROM distributed_locks WHERE lock_name = 'my_lock');
COMMIT;

解锁

1
2
3
START TRANSACTION;
DELETE FROM distributed_locks WHERE lock_name = 'my_lock' AND locked_by = 'unique_client_id';
COMMIT;

设计秒杀系统

高性能

热key二级缓存:热点数据可以在redis写一份,jvm内存写一份(访问最快)

如何检测热key,京东的解决方案?

  • etcd集群:worker ip和规则配置,例如userId_ 开头的key,每2s出现20次算热key之类的,过期时间之类的
  • worker:上报ip,监听和计算client发来的key,达到规则的阈值后推送到client和etcd
  • client:获取规则,worker ip等,定时任务每500ms批量发送一批待测key到worker(经过shard能保证固定的key的到同一个机器),已经热了的key不会再发送。收到worker推送的热key后,本地caffeine缓存。

高可用

  • Redis集群,哨兵
  • 限流
    • 根据ip、用户限流
    • 验证码
    • 提前预约
  • 流量削峰
  • 降级:优先保障核心功能,比如关闭视频评论,保留播放功能
  • 熔断:比如A要调B的接口,但是B出故障了,当A调B的失败次数到阈值后,停止A对B的调用

一致性

  • 减库存方案:下单减库存(即使不付款)
    • 不超卖:秒杀商品在缓存中,lua脚本,Redis中减库存成功,通过MQ异步更新到MySQL,达到最终一致性
  • 余额扣减方案:悲观锁,MySQL的select for update,并发量不高可以使用乐观锁
  • 接口幂等:悲观锁、乐观锁、token、唯一索引、分布式锁
    • token:第一次请求时,服务器生成token带过期时间的,存入redis,第二次请求header带上token,后面先删除token再执行请求(大不了重写生成token,好过带着token再进行第二次非法操作)

Feed流实现

什么是Feed流

知乎、抖音首页推荐,朋友圈动态等。

  • 纯智能推荐:兴趣点
  • 纯Timeline:时间线
  • 两者结合

架构设计

  • 推模式:写入数据库太多
  • 拉模式:存储成本低,但是实时性差
  • 推拉结合:推模式写入活跃用户的数据库,不活跃的用户自己去拉

短链系统

为什么要有短链

  • 短信字数限制
  • 微博字数限制
  • 长链生成的二维码太过复杂

短链生成

比较一般的方法

  • 现在redis中查找长链,找到则说明已经生成
  • 没找到就MD5或者murmurhash生成,转成62进制短码,拼成短网址,检查短网址是否存在于redis
  • 如果不存在,保存长链到短链和短链到长链的映射,返回,结束
  • 存在说明发生哈希冲突,加盐生成短码直到没有冲突

存在的问题?

  • 外部操作太多,访问redis,hbase等
  • 随着数量增加,哈希碰撞增加
  • 大部分短链的有效期很短,1个月后就不再访问了,可以优化生成服务

如何优化?

  • 采用自增算法生成短码
  • 去掉长短映射(没必要)
  • 内存自增+缓存批量区号(比如一次取10000个)
  • 30天内有一次访问重置30天有效期,否则删除

第三方授权登录

OAuth 2.0

为第三方应用颁发一个有时效性的token,使第三方应用能通过token得到用户的信息

  • 客户端向用户发送授权申请
  • 用户同意授权
  • 客户端使用获得的授权码,向认证服务器申请access token
  • 认证服务器认证后,发放token
  • 客户端使用token,向资源服务器申请资源
  • 资源服务器确认token有效,发放资源

授权码有效期一般为5-20min,只能使用一次

二维码登录

扫码登录可以分为三个阶段:未扫描、已扫描待确认、已确认

  • pc携带设备信息发送生成二维码请求至服务器
  • 服务器生成二维码id,并和设备绑定,返回二维码id
  • pc启动定时任务轮询服务器二维码状态,直到成功
  • 用户扫描二维码,将手机登录凭证(token)和二维码id发送给服务器
  • 服务器将二维码id和用户身份信息绑定,返回临时token至用户
  • 用户携带临时token确认登录
  • 服务器更改二维码状态为已确认,生成pc端token
  • pc由于一直在轮询二维码状态,所以会得到token,后续可以凭借token访问服务端api

优惠券系统

券模板、券记录都需要持久化


场景题和系统设计
http://hhubibi.github.io/2024/08/26/system-design/
作者
hhubibi
发布于
2024年8月26日
许可协议