[!NOTE]
在今年的秋招面试中,“秒杀系统”也是本人遇到的一个相对高频考点,所以在秋招结束后,我学习了极客时间的《如何设计一个秒杀系统》课程,并总结了一些要点,面试可从这几点来阐述。
目标 | 原则 |
---|---|
1. 高性能。数据动静分离、热点发现和隔离、请求的削峰和分层过滤、服务端的性能优化。 2. 一致性。减库存方案。 2. 高可用。兜底方案。 |
1. 数据尽量少 2. 请求数量尽量少(热点发现和处理) 3. 路径尽量短 4. 依赖尽量少 5. 不要有单点 |
动静分离
热点发现和处理
- 热点操作:双十一零点下单、添加购物车等。分读请求和写请求处理。
- 静态热点数据:能够通过卖家报名、数据分析等方式提前预测热点数据。
- 动态热点数据:系统运行时临时产生的热点数据。
热点数据发现
静态热点数据发现
- 卖家报名,通过运营系统提前做缓存。
- 数据分析,提前统计TOP N商品。
- 缺点:增加卖家使用成本,实时性差不灵活。
动态热点数据发现
构建热点发现系统,收集交易链路上的热点key,将上游发现的热点透传给下游系统,下游系统做热点保护处理。
比如以来上游的导购决策页面,如首页/搜索/商品详情,提前识别热点商品,通过上游系统的中间件收集热点数据,记录到日志中。对日志进行聚合和分析,把符合规则的热点数据通过订阅分发系统推送到相应的下游系统中,进行热点保护(填充到缓存中或应用服务器的内存等等)。
热点发现系统注意点
- 热点服务异步抓取热点数据。异步保证通用性,且不影响主流程。
- 热点服务发现系统与中间件自身的热点保护模块并存。
- 热点发现要接近实时,才能对下游系统做动态的保护。
热点数据处理
三种思路:优化,限制,隔离。
- 优化:使用队列,临时缓存热点数据。
- 限制:对商品ID做一致性hash分桶,每个分桶设置一个处理队列,可以将热点商品限制在一个请求队列里,防止热点数据影响到其他请求。
- 隔离:
- 业务隔离:热点请求单独做秒杀业务,卖家报名,提前做预热。
- 系统隔离:集群做分组部署,秒杀业务通过单独的域名进行分组隔离。
- 数据隔离:启用单独的cache集群或mysql数据库存放热点数据。
- 其他:接入层针对URL中的不同path设置限流策略;服务层调用不同的服务接口进行区分;数据层给数据打标进行区分。目的是识别出热点请求。
削峰
三种方式:排队,答题,分层过滤
排队
- 使用消息队列来缓冲瞬时流量
- 线程池加锁等待
- 将请求序列化到文件中顺序读文件
答题
防止秒杀器,延长请求峰值时间。
分层过滤
通过CDN、前台读系统、后台写系统、数据库四层,进行无效请求过滤。需要做数据的分层校验:
- 读数据不做强一致性校验,减少瓶颈。
- 对写数据进行基于时间的分片,过滤掉过期请求。
- 对写请求做限流,将超出系统承载能力的请求过滤。
- 对写数据做强一致性校验,只保留有效数据。
在读系统中,尽量减少由于一致性校验带来的系统瓶颈,但是尽量将不影响性能的检查条件提前,如用户是否具有秒杀资格、商品状态是否正常、用户答题是否正确、秒杀是否已经结束、是否非法请求、营销等价物是否充足等;
在写数据系统中,主要对写的数据(如“库存”)做一致性检查,最后在数据库层保证数据的最终准确性(如“库存”不能减为负数)。
性能优化
性能指标和影响因素
性能指标:QPS,响应时间
- 总QPS = (1000ms / 响应时间)* 线程数
- 响应时间:CPU执行时间+线程等待时间
真正影响性能的因素:CPU执行时间。因为CPU的执行真实消耗了服务器资源。
多线程
线程数不是越多越好,因为线程切换需要成本,每个线程也会耗费一定的内存
线程数配置
- 默认配置:线程数 = 2 * CPU 核数 + 1
- 线程数 = [(线程等待时间 + 线程 CPU 时间) / 线程 CPU 时间] × CPU 数量
- 通过性能测试来找到最佳配置
性能瓶颈
可能产生瓶颈的地方:CPU、内存、磁盘、网络
秒杀系统的瓶颈更多发生在CPU上。
可以通过CPU诊断工具,判断出请求中函数的CPU执行时间,有针对性地优化。
如何判断CPU是否是瓶颈:
看当 QPS 达到极限时,你的服务器的 CPU 使用率是不是超过了 95%,如果没有超过,那么表示 CPU 还有提升的空间,要么是有锁限制,要么是有过多的本地 I/O 等待发生。
优化手段
-
减少编码。字符串的IO操作需要将字符转为字节,消耗资源。可以将静态数据提前转换为字节,直接输出字节内容到页面。
-
减少序列化。减少RPC调用,将多个应用进行合并部署。
-
Java优化。做静态化改造,将大部分请求和数据直接在Nginx服务器或web代理服务器上直接返回,Java层只处理少量的动态请求:
- 直接使用servlet,避免MVC框架的复杂处理逻辑。
- 直接输出流数据。
-
并发读优化。解决单点缓存瓶颈:使用应用层Local Cache,在秒杀系统单机上缓存商品相关数据。
- 静态数据:类似商品标题和描述,在秒杀开始前全量推送到秒杀机器上,缓存到秒杀结束。
- 动态数据:类似库存,采用被动失效缓存一段时间(几秒),失效后再去缓存拉取最新数据。
数据不一致(超卖)问题?读场景允许一定的脏数据,在真正写数据的时候才会保证一致性。
-
其他手段。
- 减少数据;
- 数据分级,次要信息异步加载;
- 减少中间环节;
- 做好应用基线……
Servlet是 Java Web 应用的基础组件,直接与 HTTP 请求和响应交互,提供了底层的控制。这意味着开发者需要手动处理请求参数、路由等,负责数据的输入、输出和渲染视图的每一步工作。
性能稍高,因为 Servlet 是底层技术,少了许多框架的封装层,执行效率更高。高并发或性能要求特别高的场景下,直接使用 Servlet 可以减少资源占用。
减库存
减库存的几种方式
- 下单减库存。问题:恶意下单不付款,使商品无法正常售卖。
- 付款减库存。问题:库存超卖,买家下单成功数远大于库存数量,下单成功却无法付款,买家购物体验差。
- 预扣库存,下单时先预扣,在规定时间内不付款再释放库存。问题:没法完全解决恶意下单和库存超卖的现象。
- 恶意下单:标记恶意买家、给商品设置最大购买数量、重复下单不付款操作次数限制。
- 库存超卖:补货;付款时提示库存不足。
大型秒杀场景的常用策略
- 使用“下单减库存”策略,原因:
- 秒杀场景下,一般来说成功下单却不付款的情况较少。
- 下单减库存相比付款减库存、预扣库存在实现逻辑上更简单,性能上更好。
- 保证数据一致性(库存不为负数):
- 通过事务来判断,保证库存不为负数,否则回滚。
- 设置数据库字段为无符号整数,这样一旦库存为负数,SQL语句执行时就会报错。
- 使用CASE WHEN判断语句。
热点数据(库存)的优化
- 若减库存逻辑非常单一,可以使用缓存(如redis)完成。
- 若减库存逻辑复杂,或需要使用事务,则必须使用数据库。
由于 MySQL 存储数据的特点,同一数据在数据库里肯定是一行存储(MySQL),因此会有大量线程来竞争 InnoDB 行锁,而并发度越高时等待线程会越多,TPS(Transaction Per Second,即每秒处理的消息数)会下降,响应时间(RT)会上升,数据库的吞吐量就会严重受影响。
所以单个热点数据会影响整个数据库的性能。
解决方法:
- 应用层排队。按照商品维度设置队列顺序执行(控制单个商品占用数据库连接的数量)。
- 数据库层面排队。对mysql innodb做补丁,实现在数据库层面对单行记录做到排队。
兜底
高可用系统建设
阶段 | 内容 |
---|---|
架构阶段 | 异地容灾,异步化,分组隔离,避免单点 |
编码阶段 | 限流保护,超时处理,异步线程,错误捕获 |
测试阶段 | beta测试,自动化对比测试 |
发布阶段 | 分批发布,多版本发布 |
运行阶段 | 数据对账,自动降级,过载保护,实时监控报警 |
故障发生 | 快速恢复,故障定位 |
降级
- 当系统的容量达到一定程度时,限制或者关闭系统的某些非核心功能,从而把有限的资源保留给更核心的业务。
- 通过预案系统和开关系统实现降级。
- 例子:在双 11 零点时,如果优惠券系统扛不住,可能会临时降级商品详情的优惠信息展示,把有限的系统资源用在保障交易系统正确展示优惠信息上,即保障用户真正下单时的价格是正确的。
限流
- 客户端限流:在客户端设置阈值,限制请求的发出。难以设置合理的阈值。
- 服务端限流:可以根据服务端性能设置出合理的阈值。需要额外处理无效的请求。
拒绝服务
- 当系统负载达到一定阈值时,系统直接拒绝一切请求。
- 例子:在最前端的 Nginx 上设置过载保护,当机器负载达到某个值时直接拒绝 HTTP 请求并返回 503 错误码,在 Java 层同样也可以设计过载保护。
总结
秒杀系统是为了高并发下保障服务稳定以及用户体验而设计的,该系统的构建主要围绕三个核心目标:高性能、一致性、高可用。
- 高性能:数据动静分离与热点发现和处理是应对高并发的关键手段。通过静态和动态分析提前预测热点商品,将其缓存至合适的位置,减轻服务器负担。同时,为减少服务器压力,系统设计了削峰策略,包括排队、答题、和分层过滤等方式来控制突发流量,确保关键请求能被优先处理。
- 一致性:减库存机制是保障数据一致性的关键之一。通过“下单减库存”的方式来实时更新库存,结合库存保护手段(如事务处理和无符号整数字段),可以防止库存变负数,并避免恶意下单对系统的冲击。
- 高可用性:为确保秒杀系统在高峰时段的稳定性,采用了异地容灾、限流、降级等措施,保障系统的核心业务。降级策略会在非核心功能负载过大时关闭,保留资源给最核心的交易业务,避免系统宕机。限流和拒绝服务策略可有效防止系统过载,通过客户端和服务端双层限流,使秒杀系统在压力下仍能高效地服务用户。
总的来说,秒杀系统的构建是一个从架构、编码到实际运行的全链条过程,通过数据缓存、性能优化、降级容灾等手段应对秒杀场景中的高并发压力,保障系统的稳定性和用户体验。