面试-业务-秒杀功能总结

一、架构原则

秒杀系统本质上就是一个满足大并发、高性能和高可用的分布式系统

架构原则: 4要 1不要

  1. 数据尽量少:用户请求的数据能少就少,包括上传给系统的数据和系统返回的数据
  2. 请求数尽量少
  3. 路径尽量短
  4. 依赖尽量少

二、动静分离

如何才能做好动静分离?有哪些方案可选?

1、什么是动静数据
以商详页为例,产品的基本信息就是静态数据,像库存这样的或者是和用户相关的信息就理解成动态数据

2、静态数据
缓存静态数据,因为Java服务器不善于处理大量的http请求,所以像一些静态数据,可以通过其他的Web框架来缓存,比如Nginx、Apache、Varnish去缓存

比如:让前端通过url作为Key,因为url往往只带有商品id这种唯一的标识,把整个页面的一部分静态数据作为value缓存起来,把页面静态数据和动态数据分离开来,像浏览器的信息,或者用户的Cookies分来出来,然后把页面只包含静态数据缓存起来

3、动态数据
前端单独发起一个异步请求,以向服务端获取动态内容,再把这些数据组织起来

问题

1、缓存和DB的一致性
问题:如果DB的动态数据放在缓存里面,如何维护一致性
解决方法:维护数据一致性的时候,大多数都是采用让缓存失效,而不是去更新缓存数据来解决。有2种一种是主动失效,一种是被动到了失效时间。可以通过bigLog监听,有变动触发事件,然后设置缓存失效,或者使用canal,原理也是通过binlog解析,触发事件。

当并发量上来的时候,还是会有缓存还没更更新,业务请求就进来了,拿到了和数据库不一样的数据,比如查询库存操作,刚有下单成功然后库存就没了,但是这个时候还没来的急解析,但是此时请求又进来了,肯定会有不一致的情况,解决办法就是,不能完全看缓存,最终下单成功还要看在DB层,减库存的量来判断,是否update更新成功来作为下单成功结果

三、热点数据隔离

1、系统隔离
系统拆分,单一业务独立成一个系统

2、数据隔离
比如分库分表

四、流量削峰

1、排队
最容易想到的解决方案就是用消息队列来缓冲瞬时流量,把同步的直接调用转换成异步的间接推送

  1. 利用线程池加锁等待也是一种常用的排队方式
  2. 先进先出、先进后出等常用的内存排队算法的实现方式
  3. 把请求序列化到文件中,然后再顺序地读文件(例如基于 MySQL binlog 的同步机制)来恢复请求等方式。

问题

1、那就执行结果如何给到前端是同步还是异步
同步:等待消息被正确投递后才返回结果,技术上也可以实现,只是需要等待,如果链路长,耗时时间比较久。或者服务端和前端通过SSE,后端处理完,主动通知给前端。或者后端收到服务请求,把请求放到RocketMQ消息里面,同步等待RocketMQ返回对应的结果。(RocketMQ支持同步发送消息并等待消息处理的结果)

异步:大部分就是异步的,发送后即返回,然后由消息队列保证最后最终被投递,这个要由消息队列自己来承诺,前端采用轮训

2、依赖服务多,链路变成耗时增多
问题:如果采用排队,那么假如这个请求,涉及的业务逻辑比其他请求还多,调用很多下游服务,接口耗时肯定会很长,这不就导致排队的其他线程要等很久,这就很不公平了
解决:如果涉及到服务很多就要抽离出来,简短调用,并且进入队列的线程时秒杀活动的线程,不是所有的请求都进队列

3、前端提交请求,进入队列,等待结果返回对服务器性能问题

前端轮训查询: 因为要轮训,要不停的给后端发请求,服务端的请求数会增加不少。
解决方法:增加节点数量,这样能接收到请求的数量也就可以变多,然后把处理结果可以通过缓存存起来,不至于打到DB

如果是长链接:服务端的连接数会比较多,久久不释放,后面线程一直进来。
解决方法:其实和上面的处理方式大同小异

4、处理业务请求不均匀
问题:因为参与秒杀的用户多,那么队列里面可能全是秒杀请求,那么后来来了一个非秒杀请求,那要等多久才会轮到他执行,那这不就导致处理请求公平,导致非热卖商品的请求迟迟得不到解决,从而导致非热卖商品接口性能变慢
解决方法:就是在部分服务调用的地方对请求进行 Hash分组,来限制一部分热点请求过多地占用服务器资源,分组的策略就可以根据商品ID来进行Hash,这样同一个商品的Id进行Hash,出来的结果肯定是一样的,并且都在一个请求队列里面,当非秒杀和秒杀商品请求过来,就会产生多个分组,通过缓存从每个分组依次取一个,就可以保障正常运行了,或者给秒杀商品的队列,设置权重,权重大的多取几个线程。

2、分层过滤
分层过滤的核心思想是:在不同的层次尽可能地过滤掉无效请求,让“漏斗”最末端的才是有效请求

3、限流
令牌桶,其实没必要限流,为什么,因为拿到令牌不代表请求成功,而且上面的第一点队列,通过队列容量,就已经控制了流量,然后一个个去消费,就控制了速度,这就没必要用限流了,用限流的目的是为了什么?为了控制一秒钟处理对少个请求?还是为了控制拿到了多少个令牌就表示多少个库存扣掉了?

五:减库存逻辑

  1. 下单减库存
  2. 付款减库存:会有超卖的风险,如果通过锁强行控制那就下单成功付不了款。
  3. 预扣库存,这种方式相对复杂一些,买家下单后,库存为其保留一定的时间,超过这个时间,库存将会自动释放,释放后其他买家就可以继续购买

减库存sql

Update table set 库存-数量 where 库存-数量>0

或者

UPDATE table 
SET 库存 =
CASE
	WHEN 库存 >= 扣减数量 THEN  库存 - 扣减数量
	ELSE 原库存数量 
END
where Id in('xx','yy')

问题

1、数据库并发写问题
问题:并发锁的问题,库存数据在数据库里肯定是一行存储(MySQL),因此会有大量线程来竞争InnoDB行锁,而并发度越高时等待线程会越多,TPS会下降,响应时间(RT)会上升,数据库的吞吐量就会严重受影响。
解决方法:

方案1:应用层做排队,按照商品维度设置队列顺序执行,这样能减少同一台机器对数据库同一行记录进行操作的并发度,同时也能控制单个商品占用数据库连接的数量,防止热点商品占用太多的数据库连接。
方案2:应用层只能做到单机的排队,但是应用机器数本身很多,这种排队方式控制并发的能力仍然有限,所以如果能在数据库层做全局排队是最理想的。阿里的数据库团队开发了针对这种,MySQL 的 InnoDB 层上的补丁程序(patch),可以在数据库层上对单行记录做到并发排队。

2、单行数据成为热点数据,影响整个库
问题:单个热点商品会影响整个数据库的性能,而这个数据有多个业务的数据
解决方案:可以通过独一处一个单独库来做热点商品处理,或者分库分表,以商品的维度拆分

3、下单和扣库存两个操作的事务性是怎么做的?
问题:下单和扣库存两个操作的事务性是怎么实现
解决方案:可以分两步来做,先创建订单但是先不生效,然后减库存,如果减库存成功后再生效订单,否则订单不生效

六、系统高可用建设

1、降级
所谓“降级”,就是当系统的容量达到一定程度时,限制或者关闭系统的某些非核心功能,从而把有限的资源保留给更核心的业务

2、限流
如果说降级是牺牲了一部分次要的功能和用户的体验效果,那么限流就是更极端的一种保护措施了,限流就是当系统容量达到瓶颈时,我们需要通过限制一部分流量来保护系统

3、拒绝服务
这种方式就更粗暴了,都是为了牺牲一部分体验,提其他业务稳定运行

七、设计方法总结

方案1

1、前端页面:动静分离,静态资源存放于CDN,或者后端本地缓存,动态数据放redis里面
2、前端限流,同一个用户5秒内只提交一个请求
3、后端redis对同一用户限流,同样5秒内提交一个请求
3、请求保存队列,队列长度为库存2倍或者更多,放入队列的数据最好是基本上可以下单成功的用户
4、队列满后,后续请求直接返回秒杀结束
5、消费线程消费队列内容,下订单,直接操作MySQL扣库存或者操作Redis减一操作
6、创建订单成功后,往延时消息里面设置消息,如果支付超时后,将库存回收。

方案2

1、在确定库存,提前下好单,下单人留空,订单无失效时间
2、订单记录压入Redis队列
3、请求来到,订单队列Lpop,队空则返回失败,取到之后通过当前用户Id绑定订单,并且设置缓存失效时间
4、异步把订单保存到数据库中,成功返回给用户创建成功
5、如果订单过期失效则回收这个订单,并且存入队列

缺点:过于复杂

相关链接:
AliSQL跳转

canal跳转

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

信仰_273993243

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值