【面试】系统架构设计(持续更新)

文章目录

    • 服务稳定性
      • 82原则
      • 如何衡量系统稳定性?
      • 事故按影响程度不同划分等级
    • 如何提高服务稳定性?
      • 一、系统设计规范
      • 二、单服务稳定性
      • 三、集群稳定性
      • 四、稳定性专项
  • 二、场景设计
    • 2.1 微博热搜数据设计
      • 2.1.1 Hot Key问题怎么应对?
        • Hot Key可能引发的问题
        • 极热 key 解决方案
      • 2.1.2 限流问题
        • 设计一个限流器,满足100w QPS 的限流。
    • 2.2 帖子(post)的设计实现与优化
    • 2.3 点赞业务如何落地
    • 订单表的分库分表方案设计(大数据)
      • b2c平台。订单的卖家就一个,就是平台自己。
      • c2b2c 平台的订单分卖家和买家的时候,选择什么字段来分库分表呢?
      • 微博百亿数据-粉丝关系链 如何分库分表
    • 冷热数据
    • 数据库缓存一致性问题
    • 如何设计低延时服务
    • 如何在备库(读库)读到最新数据?
    • 数据库主从延时问题
    • 秒杀架构的思考
      • 常用技术手段
      • 高性能热点优化
      • 服务性能优化
      • 一致性性能优化
      • 高可用
      • 秒杀架构
          • 库存服务 被调用超时怎么办?
        • 缓存数据一致性
        • 缓存命中率问题
        • binlog延迟、并行问题
    • http接口 何如防篡改、防重放
        • 防重放
          • 阿里云api网关的解决方案
    • 如何在短时间内 在一个接口中 并发请求多个http链接
    • 分布式限流
  • 字节跳动团队:春节钱包大流量奖励系统入账及展示的设计与实现
      • 4.4 难点四:大流量发卡券预算控制
      • 4.5 难点五:高 QPS 场景下的热 key 的读取和写入稳定性保障
  • 商品服务 多级缓存
      • 本地缓存策略
      • 热点问题
      • 缓存击穿

服务稳定性

82原则

【面试】系统架构设计(持续更新)_第1张图片

如何衡量系统稳定性?

【面试】系统架构设计(持续更新)_第2张图片

事故按影响程度不同划分等级

【面试】系统架构设计(持续更新)_第3张图片

如何提高服务稳定性?

一、系统设计规范

业内喜欢用SLA (服务等级协议,全称:service level agreement)来衡量系统的稳定性,对互联网公司来说就是网站服务可用性的一个保证。9越多代表全年服务可用时间越长服务越可靠,停机时间越短。就以一个标准99.99%为例,停机时间52.6分钟,平均到每周也就是只能有差不多1分钟的停机时间,也就是说网络抖动这个时间可能就没了。保证一个系统四个9或者更高的五个9,需要一套全体共识严格标准的规章制度,没有规矩不成方圆。创建的规范有如下几种:
1、研发规范、自身稳定;

2、事务中不能包含远程调用;

3、超时时间和重试次数要合理;

4、表数据操作必须double check,合理利用索引,避免出现慢查询、分库分表不走分表键;

5、没有有效的资源隔离, 避免不同业务共用一个线程池或连接池;

6、合理的系统拓扑,禁止不合理服务依赖,能依赖就依赖,否则同步尽量改成异步弱依赖;

7、精简的代码逻辑;

8、核心路径流程必须进行资源隔离,确保任何突发情况主流程不能受影响。

二、单服务稳定性

关键字:开关可控、单一职责、服务隔离、异常兜底、监控发现!

对于稳定性来说,抛开整体系统架构设计,单就每个业务域服务的稳定性也是非常的重要。只有每个业务环节都稳如泰山,才能保障整个稳定性。单服务稳定可以从以下几个方面来进行:

1、禁用设计:应该提供控制具体功能是否开启可用的配置,在相应的功能服务出现故障时,快速下线局部功能,以保证整体服务的可用性;

2、必要的缓存:缓存是解决并发的利器,可以有效的提高系统的吞吐量。按照业务以及技术的纬度必要时可以增加多级缓存来保证其命中率;

3、接口无状态性:服务接口应是无状态的,当前接口访问不应该依赖上层接口的状态逻辑;

4、接口单一职责性:对于核心功能的接口,不应该过多的耦合不属于它的功能。如果一个接口做的事情太多应做拆分,保证单接口的稳定性和快速响应;

5、第三方服务隔离性:任何依赖于第三方的服务(不论接口还是中间件等),都应该做到熔断和降级,不能有强耦合的依赖;

6、业务场景兜底方案:核心业务场景要做到完整兜底方法,从前端到后端都应有兜底措施;

7、服务监控与及时响应:每个服务应做好对应监控工作,如有异常应及时响应,不应累积。

三、集群稳定性

关键字:系统架构、部署发布、限流熔断、监控体系、压测机制!

对于集群维度的稳定性来说,稳定性保障会更加复杂。单服务是局部,集群是全局。一个见微知著,一个高瞻远瞩。

1、合理的系统架构:合理的系统架构是稳定的基石;

2、小心的代码逻辑:代码时刻都要小心,多担心一点这里会不会有性能问题,那里会不会出现并发,代码就不会有多少问题;

3、优秀的集群部署:一台机器永远会有性能瓶颈,优秀的集群部署,可以将一台机器的稳定放大无限倍,是高并发与大流量的保障;

4、科学的限流熔断:高并发来临时,科学的限流和熔断是系统稳定的必要条件;

5、精细的监控体系:没有监控体系,你永远不会知道你的系统到底有多少隐藏的问题和坑,也很难知道瓶颈在哪里;

6、强悍的压测机制:压测是高并发稳定性的试金石,能提前预知高并发来临时,系统应该出现的模样;

7、胆小的开发人员:永远需要一群胆小的程序员,他们讨厌bug,害怕error,不放过每一个波动,不信任所有的依赖。

四、稳定性专项

专项指的是针对某些特定场景下的特定问题而梳理出对应的方案。下面是针对一些常见的稳定性专项的概述:

1、预案:分为定时预案和紧急预案,定时预案是大促常规操作对于一系列开关的编排,紧急预案是应对突发情况的特殊处理,都依赖于事前梳理;

2、预热:分为JIT代码预热和数据预热,阿里内部有专门的一个产品负责这块,通过存储线上的常态化流量或者热点流量进行回放来提前预热, 起源于某年双十一零点的毛刺问题,原因是访问了数据库的冷数据rt增高导致的一系列上层限流,现在预热已经成了大促之前的一个必要流程。

3、强弱依赖:梳理强弱依赖是一个偏人肉的过程,但是非常重要,这是一个系统自查识别潜在风险点并为后续整理开关限流预案和根因分析的一个重要参考,阿里内部有一个强弱依赖检测的平台,通过对测试用例注入RPC调用的延迟或异常来观察链路的依赖变化,自动梳理出强弱依赖关系。

4、限流降级熔断:应对突发流量防止请求超出自身处理能力系统被击垮的必要手段;

5、监控告警&链路追踪:监控分为业务监控、系统监控和中间件监控和基础监控,作为线上问题发现和排查工具,重要性不言而喻。

二、场景设计

2.1 微博热搜数据设计

2.1.1 Hot Key问题怎么应对?

参考:06 Hot Key和Big Key引发的问题怎么应对?

Hot Key可能引发的问题

Hot key。对于大多数互联网系统,数据是分冷热的。比如最近的新闻、新发表的微博被访问的频率最高,而比较久远的之前的新闻、微博被访问的频率就会小很多。而在突发事件发生时,大量用户同时去访问这个突发热点信息,访问这个 Hot key,这个突发热点信息所在的缓存节点就很容易出现过载和卡顿现象,甚至会被 Crash。

Hot key 引发缓存系统异常,主要是因为突发热门事件发生时,超大量的请求访问热点事件对应的 key,比如微博中数十万、数百万的用户同时去吃一个新瓜。数十万的访问请求同一个 key,流量集中打在一个缓存节点机器,这个缓存机器很容易被打到物理网卡、带宽、CPU 的极限,从而导致缓存访问变慢、卡顿。

极热 key 解决方案

引发 Hot key 的业务场景很多,比如明星结婚、离婚、出轨这种特殊突发事件,比如奥运、春节这些重大活动或节日,还比如秒杀、双12、618 等线上促销活动,都很容易出现 Hot key 的情况。

要解决这种极热 key 的问题,首先要找出这些 Hot key 来。Hot key可以分为两种,已知和未知两种。

  • 已知Hot key

对于重要节假日、线上促销活动、集中推送这些提前已知的事情,可以提前评估出可能的预热 key 来。

  • 未知Hot key
    对于突发事件,无法提前评估,可以通过 Spark,对应流任务进行实时分析,及时发现新发布的热点 key。
    而对于之前已发出的事情,逐步发酵成为热 key 的,则可以通过 Hadoop 对批处理任务离线计算,找出最近历史数据中的高频热 key。

找到热 key 后,就有很多解决办法了。

  • key打散分片
    首先可以将这些热 key 进行分散处理,比如一个热 key 名字叫 hotkey,可以被分散为 hotkey#1、hotkey#2、hotkey#3,……hotkey#n,这 n 个 key 分散存在多个缓存节点,然后 client 端请求时,随机访问其中某个后缀的 hotkey,这样就可以把热 key 的请求打散,避免一个缓存节点过载。

【面试】系统架构设计(持续更新)_第4张图片

  • 多级缓存架构
    其次,也可以 key 的名字不变,对缓存提前进行多副本+多级结合的缓存架构设计。

  • 缓存实例 实时扩容
    再次,如果热 key 较多,还可以通过监控体系对缓存的 SLA 实时监控,通过快速扩容来减少热 key 的冲击。

  • 本地缓存
    业务端还可以使用本地缓存,将这些热 key 记录在本地缓存,来减少对远程缓存的冲击。

2.1.2 限流问题

设计一个限流器,满足100w QPS 的限流。

令牌桶。100w的计数器,然后每次请求去获取令牌,拿到就请求,拿不到就丢弃或者等待,等待超时就丢弃。
然后面试官问怎么实现?我回答了,然后不满足他的要求,让我继续优化。

然后我说在代理层面做,他说不一定能抗住这么大的请求量,你的处理延时怎么处理。
然后我说分发在每个服务器上面做,每个服务器限小流,然后如果负载均衡就可以实现整体限流满足要求。

2.2 帖子(post)的设计实现与优化

db读写分离

  1. 采用读写分离之后存在数据延迟问题
  2. 较早的数据极少访问。而一旦读写分离,意味着每一条记录都需要存储多份。当这些数据刚发布的时候,访问频繁,随着时间的推移,他们就不再被访问了。那么百分之99的数据副本不会被读取到,存储效率低。

引入缓存,缓存的数据过期机制天然避免了陈旧数据对空间的占用。

这里我们如何设计Redis的key-value呢?我们不难发现,同一个用户一天发出的帖子数量是有限的,通常不超过10条,平均3条左右,单个用户一周发的帖子很难超过100KB,极端情况下1MB,远低于Redisvalue大小的上限。所以:

key:userId+时间戳(精确到每星期)

value:Redis为hash类型,field为postId,value为帖子内容

expire设置为一个星期,即最多同时存在两个星期的数据(假设每贴平均长度0.1KB,1亿用户每天发3贴预计数量为400GB)

对某个用户一段时间范围的查找变为针对该用户本周时间戳的hscan命令,用户发帖等操作同时同步更新DB和缓存,DB的变更操作记录保证一致性。

但是,有些热用户的follower数量极高,意味着这个热点用户所在Redis服务器的查询频率为1000万每秒。

当一些比较热点的用户查询比较频繁的时候,我们可以直接把热点用户存入本地缓存中。用来缓解服务端的缓存的压力

2.3 点赞业务如何落地

根据点赞业务特点可以发现:

  1. 吞吐量高
  2. 能够接受数据不一致

如果只考虑点赞可以怎么做?

可以用MySQL做持久存储,Redis做缓存,读写操作落缓存,异步线程定期刷新DB。

counter表:id,postId,count

RedisKV存储:key:postId value:count

但是以微博为例,不仅有点赞还有转发数,评论数,阅读量等。所以,业务拓展性和效率问题是难点,如果这么设计,我们就需要多次查询Redis,多次查询DB。

假设首页有100条消息,就需要for循环每一条消息,每条消息要进行4次Redis访问进行拼装。

所以这里我们进行优化的话,可以在MySQL层面进行增加列优化

conunter表:id,postId,readCount,forwordCount,commentCount,PraiseCount

这样增加的话,缺点是如果增加一个计数服务的话,列就需要改变。那么不想改变列怎么办呢?

conunter表:id,postId,countKey(计数类型名称,比如readCount),countValue

Redis来获取业务的话,可以存储为hash用来计数。

订单表的分库分表方案设计(大数据)

https://www.cnblogs.com/wangtao_20/p/7115962.html

b2c平台。订单的卖家就一个,就是平台自己。

按用户id取模(分库分表) 思考优点和缺点

优点

订单水平分库分表,为什么要按照用户id来切分呢?

好处:查询指定用户的所有订单,避免了跨库跨表查询。

因为,根据一个用户的id来计算节点,用户的id是规定不变的,那么计算出的值永远是固定的(x库的x表)

那么保存订单的时候,a用户的所有订单,都是在x库x表里面。需要查询a用户所有订单时,就不用进行跨库、跨表去查询了。

缺点

缺点在于:数据分散不均匀,某些表的数据量特别大,某些表的数据量很小。因为某些用户下单量多,打个比方,1000-2000这个范围内的用户,下单特别多,

而他们的id根据计算规则,都是分到了x库x表。造成这个表的数据量大,单表的数据量撑到极限后,咋办呢?

总结一下:每种分库分表方案也不是十全十美,都是有利有弊的。目前来说,这种使用用户id来切分订单数据的方案,还是被大部分公司给使用。实际效果还不错。程序员省事,至于数据量暴涨,以后再说呢。毕竟公司业务发展到什么程度,不知道的,项目存活期多久,未来不确定。先扛住再说。

c2b2c 平台的订单分卖家和买家的时候,选择什么字段来分库分表呢?

b2b平台,上面支持开店,买家和卖家都要能够登陆看到自己的订单。

先来看看,分表使用买家id分库分表和根据卖家id分库分表,两种办法出现的问题

如果按买家id来分库分表。有卖家的商品,会有n个用户购买,他所有的订单,会分散到多个库多个表中去了,卖家查询自己的所有订单,跨库、跨表扫描,性能低下。

如果按卖家id分库分表。买家会在n个店铺下单。订单就会分散在多个库、多个表中。买家查询自己所有订单,同样要去所有的库、所有的表搜索,性能低下。

所以,无论是按照买家id切分订单表,还是按照卖家id切分订单表。两边都不讨好。

淘宝的做法是拆分买家库和卖家库,也就是两个库:买家库、卖家库。

买家库,按照用户的id来分库分表。卖家库,按照卖家的id来分库分表。

实际上是通过数据冗余解决的:一个订单,在买家库里面有,在卖家库里面也存储了一份。下订单的时候,要写两份数据。先把订单写入买家库里面去,然后通过消息中间件来同步订单数据到卖家库里面去。

买家库的订单a修改了后,要发异步消息,通知到卖家库去,更改状态。

微博百亿数据-粉丝关系链 如何分库分表

https://mp.weixin.qq.com/s/fVt7rvX-uurIEV7hMSAHtA

粉丝列表拆分为: 关注列表、粉丝列表

正常用户分库分表, 百万、千万等大V单独处理(单独分库分表)

一些大V账号,我们也可以进行服务器端的本地、中间件进行多级缓存。

冷热数据

数据库缓存一致性问题

1.依赖redis过期时间(对于时间不敏感业务)
【面试】系统架构设计(持续更新)_第5张图片

1.分布式锁使请求串行化 缺点:请求积压过多 可能会使请求超时

2.mq异步更新
【面试】系统架构设计(持续更新)_第6张图片

3.canal+mq+redis

【面试】系统架构设计(持续更新)_第7张图片

【面试】系统架构设计(持续更新)_第8张图片

如何设计低延时服务

场景:读多写少,服务返回需求在 毫秒级

1.一二级缓存,local cache + redis cache

2.大map 可以分成多个小map,读写锁保证并发安全

如何在备库(读库)读到最新数据?

binglog 传送,中继日志重放需要时间,所以理论上备库延迟只能减少

中继日志:复制架构中,备服务器用于保存主服务器的二进制日志中读取到的事件;用于实现mysql的主从复制。

1.业务强制走主库,会影响业务
2.业务上增加 强制延时(如转账后进度条一直转)

gtid:事物id global transcation id

【面试】系统架构设计(持续更新)_第9张图片

【面试】系统架构设计(持续更新)_第10张图片

数据库主从延时问题

业务上等待刷新(如银行转账等)

1)、架构方面

1.业务的持久化层的实现采用分库架构,mysql服务可平行扩展,分散压力。

2.单个库读写分离,一主多从,主写从读,分散压力。这样从库压力比主库高,保护主库。

3.服务的基础架构在业务和mysql之间加入memcache或者redis的cache层。降低mysql的读压力。

4.不同业务的mysql物理隔离,分散压力。

5.使用比主库更好的硬件设备作为slave,mysql压力小,延迟自然会变小。

2.硬件

3.对于要求更新立马能查到的,可以强制走主库查询数据

秒杀架构的思考

【面试】系统架构设计(持续更新)_第11张图片
【面试】系统架构设计(持续更新)_第12张图片

常用技术手段

【面试】系统架构设计(持续更新)_第13张图片

高性能热点优化

静态化 或 cdn 等暂不考虑

1.业务隔离
2.系统隔离(秒杀服务单独)
3.数据隔离 提前预热 (热点发现- 热点数据、操作 )
4.缓存+限流

服务性能优化

1.减少序列化 (减少rpc调用) 合并部署
2.代码优化 控制堆栈日志输出

一致性性能优化

高读 高写场景

高读
分层次校验,滤掉无效请求
1.权限
2.秒杀是否结束
3.本地缓存

行记录排队

【面试】系统架构设计(持续更新)_第14张图片

1.减后库存要大于0
2.设置数据库类型为无符号整数(unsigned)

type Category struct {
    Id          uint    `json:"id" gorm:"column:id;type:int(10) unsigned not null AUTO_INCREMENT;primary_key"`
    Title       string `json:"title" gorm:"column:title;type:varchar(250) not null;default:''"`
    Description string `json:"description" gorm:"column:description;type:varchar(250) not null;default:''"`
    Content     string `json:"content" gorm:"column:content;type:longtext default null"`
    ParentId    uint    `json:"parent_id" gorm:"column:parent_id;type:int(10) unsigned not null;default:0;index:idx_parent_id"`
    Status      uint   `json:"status" gorm:"column:status;type:tinyint(1) unsigned not null;default:0;index:idx_status"`
    CreatedTime int64   `json:"created_time" gorm:"column:created_time;type:int(11) not null;default:0;index:idx_created_time"`
    UpdatedTime int64   `json:"updated_time" gorm:"column:updated_time;type:int(11) not null;default:0;index:idx_updated_time"`
    DeletedTime int64   `json:"-" gorm:"column:deleted_time;type:int(11) not null;default:0"`
}

高可用

1.流量消峰(1.答题、验证码 防作弊,提交时间验证(大于2s) 2.按钮置灰 3.排队)

秒杀架构

降低rt,提高并发

1.降低数据库锁力度:分库分表

2.降低锁持有时间

请求 队列化,
比如将200ms 请求放在一个内存队列,起一个异步线程,将200ms所有商品库存进行扣减,

api请求最差为200ms

秒杀商品id计数器,如果当前并发超过阈值,会创建内存队列,会将内存队列中购买一并扣减,降低数据库io次数

库存服务 被调用超时怎么办?

假如上游库存服务 被调用超时,上游订单服务 会发条消息给库存服务,进行库存回滚,

库存服务会根据mq中订单号查库存流水,判断订单是否扣减库存

秒杀的高并发分析

insert和update需要先执行insert
因为update同一行会导致行级锁,而insert是可以并行执行的。

【面试】系统架构设计(持续更新)_第15张图片

缓存数据一致性

cache aside db更新 创建缓存

监听binlog 保持缓存一致性,业务代码不关心缓存了,另外一个模块去刷新

【面试】系统架构设计(持续更新)_第16张图片

缓存命中率问题

监听binlog时,设计时间窗口, 判断5s内有更新,在5s结束时刷新下redis

binlog延迟、并行问题

【面试】系统架构设计(持续更新)_第17张图片

因为binlog会并行过来,所有后面的binlog可能呗先监听到,会有消息乱序的情况。
数据库字段加版本号,先判断redis版本号,binglog中 大于版本号才去更新

阿里云团队针对数据库

由业务队列改为数据库队列

http接口 何如防篡改、防重放

签名机制

http 是一种无状态的协议, 服务端并不知道客户端发送的请求是否合法, 也并不知道请求中的参数是否正确

  • 客户端: 每次请求客户端都带一个 sign 参数给服务端, 这个所谓的 sign 就是一个字符串
  • 服务端: 每次处理请求之前先验证 sign 是否合法, 如果不合法就不处理
    生成 sign 和验证 sign: 怎么生成和验证, 需要客户端和服务端约定好

客户端生成 sign: 可使用 公私钥非对称加密 的方式, 也可以使用计算字符串 md5 或者 hash 值的方式, 这个被加密的字符串最好不是固定的,取时间戳, 请求参数等就可以, 每次客户端把生成sign传递给服务端

服务端根据约定好的算法验证sign是否正确就可以

防重放

设计了防篡改之后, 接口总算是安全了那么一点点, 但是还不够…还需要对接口设计防重放设置

客户端在请求中添加两个参数

1.1 添加一个随机不重复的字符串参数 比如uuid 至于怎么让他不重复,可以考虑拼接时间戳,md5随机数等
1.2 添加一个请求时间的参数 如 request_time 值就是发送请求时的 时间戳

服务端接到这个请求:
1 先验证 sign 签名是否合理,证明请求参数没有被中途篡改
2 再验证 timestamp 是否过期,证明请求是在最近 60s 被发出的
3 最后验证 uuid 是否已经有了,证明这个请求不是 60s 内的重放请求

把这些值按照账号来区分存储在redis增加过期时间就可以了。

阿里云api网关的解决方案

客户端调用API时,需要在请求中添加计算的签名。API网关在收到请求后会使用同样的方法计算签名,同用户计算的签名进行比较,相同则验证通过,不同则认证失败。
在API网关的签名中,提供X-Ca-Timestamp、X-Ca-Nonce两个可选HEADER,客户端调用API时一起使用这两个参数,可以达到防止重放攻击的目的。

  • X-Ca-Timestamp
    发起请求的时间,可以取自机器的本地实现。当API网关收到请求时,会校验这个参数的有效性,误差不超过15分钟。
  • X-Ca-Nonce
    这个是请求的唯一标识,一般使用UUID来标识。API网关收到这个参数后会校验这个参数的有效性,同样的值,15分内只能被使用一次。

如何在短时间内 在一个接口中 并发请求多个http链接

场景:一个数组,里面有100个标题id, 每一个都需要通过http请求拿标题

1.长链接
2.合并请求为一次, 网络io从多次变为1次
3.mq 削峰填谷(不符合),接口需返回标题

分布式限流

常见的限流方法:

  • 固定窗口计数器:按照时间段划分窗口,有一次请求就+1,最为简单的算法,但这个算法有时会让通过请求量允许为限制的两倍。
    滑动窗口计数器:通过将窗口再细分,并且按照时间“滑动”来解决突破限制的问题,但是时间区间的精度越高,算法所需的空间容量就越大。
  • 漏桶:请求类似水滴,先放到桶里,服务的提供方则按照固定的速率从桶里面取出请求并执行。缺陷也很明显,当短时间内有大量的突发请求时,即便此时服务器没有任何负载,每个请求也都得在队列中等待一段时间才能被响应。
  • 令牌桶:往桶里面发放令牌,每个请求过来之后拿走一个令牌,然后只处理有令牌的请求。令牌桶满了则多余的令牌会直接丢弃。令牌桶算法既能够将所有的请求平均分布到时间区间内,又能接受服务器能够承受范围内的突发请求,因此是目前使用较为广泛的一种限流算法。
    Google 的开源项目 guava 提供了 RateLimiter 类,实现了单点的令牌桶限流。

分布式环境下,可以考虑用 Redis+Lua 脚本实现令牌桶。

如果请求量太大了,Redis 也撑不住怎么办?我觉得可以类似于分布式 ID 的处理方式,Redis 前面在增加预处理,比如每台及其预先申请一部分令牌,只有令牌用完之后才去 Redis。如果还是太大,是否可以垂直切分?按照流量的来源,比如地理位置、IP 之类的再拆开。

字节跳动团队:春节钱包大流量奖励系统入账及展示的设计与实现

https://www.toutiao.com/article/7081542340891951657

4.4 难点四:大流量发卡券预算控制

需求背景:

春节活动除夕晚上 7 点半会开始烟火大会,是大流量集中发券的一个场景,钱包侧与算法策略配合进行卡券发放库存控制,防止超发。

具体实现:

(1)钱包资产中台维护每个卡券模板 ID 的消耗发放量。

(2)每次卡券发放前算法策略会读取钱包 sdk 获取该卡券模板 ID 的消耗量以及总库存数。同时会设置一个阈值,如果卡券剩余量小于 10%后不发这个券(使用兜底券或者祝福语进行兜底)。

(3) 同时钱包资产中台方向在发券流程累计每个券模板 ID 的消耗量(使用 Redis incr 命令原子累加消耗量),然后与总活动库存进行比对,如果消耗量大于总库存数则拒绝掉,防止超发,也是一个兜底流程。

4.5 难点五:高 QPS 场景下的热 key 的读取和写入稳定性保障

在方案一实现的基础上进行优化,并且要考虑数字不断累加、节约成本与实现容灾方案。在写场景,通过本地缓存进行合并写请求进行原子性累加,读场景返回本地缓存的值,减少额外的存储资源占用。使用 Redis 实现中心化存储,最终大家读到的值都是一样的。

每个 docker 实例启动时都会执行定时任务,分为读 Redis 任务和写 Redis 任务。

读取流程:

  • 本地的定时任务每秒执行一次,读取 Redis 单 key 的值,如果获取到的值大于本地缓存那么更新本地缓存的值。
    对外暴露的 sdk 直接返回本地缓存的值即可。
  • 有个问题需要注意下,每次实例启动第一秒内是没有数据的,所以会阻塞读,等有数据再返回。

写入流程:

  • 因为读取都是读取本地缓存(本地缓存不过期),所以处理好并发情况下的写即可。
  • 本地缓存写变量使用 go 的 atomic.AddInt64 支持原子性累加本地写缓存的值。
  • 每次执行更新 Redis 的定时任务,先将本地写缓存复制到 amount 变量,然后再将本地写缓存原子性减去 amount 的值,最后将 amount 的值 incr 到 Redis 单 key 上,实现 Redis 的单 key 的值一直累加。
  • 容灾方案是使用备份 Redis 集群,写入时进行双写,一旦主机群挂掉,设计了一个配置开关支持读取备份 Redis。两个 Redis 集群的数据一致性,通过定时任务兜底实现。

注意点:

本方案调用 Redis 的流量是跟实例数成正比,经调研读取侧的服务为主会场实例数 2 万个,写入侧服务为资产中台实例数 8 千个,所以实际 Redis 要支持的 QPS 为 2.8 万/定时任务执行间隔(单位为 s),经压测验证 Redis 单实例可以支持单 key2 万 get,8k incr 的操作,所以设置定时任务的执行时间间隔是 1s,如果实例数更多可以考虑延长执行时间间隔。

【面试】系统架构设计(持续更新)_第18张图片

商品服务 多级缓存

本地缓存策略

redis 多个zset根据点击商品次数(热点),定时轮训做本地缓存,redis缓存

商品被修改, 删除缓存(会有网络抖动更新失败情况)
canal订阅binlog,消费mq,更新redis缓存, 广播模式重置本地缓存(兜底)

注意⚠️:更新缓存最好有版本号,防止多个线程同时修改导致缓存不一致问题

热点问题

热点问题,本地缓存

缓存击穿

1.同一进城内模式下 singleflight单飞模式 保证同一时间一样的请求 只有一次能够访问数据库

2.不同进程内,设置分布式锁,其它线程sleep+失败重试,

你可能感兴趣的:(计算机基础,后端开发,工作面试总结,面试,服务器)