中国有一个著名的网站,几乎每个中国人都在使用它,我们通常每年只使用一次-12306.com,即火车票预订系统。
当网站打开农历新年门票销售时,许多人都在电脑前等待并且准备在销售开始的确切时间点按“ reserve
按钮。
然后,网站崩溃了,用户抱怨了。
(然后软件工程师会反映如何修复它)
什么是闪购系统
上面的示例是一个典型的快速销售系统。 它通常具有以下特征:
- 大量用户同时进入系统,导致流量激增。 (门票销售从上午6点开始,并在上午6:30结束)
- 订购请求的数量远远大于库存大小。 (100万用户争夺10,000张门票)
有哪些挑战
- 处理读取负载。 用户将不断刷新页面以检查可用库存。 所有用户也可能同时请求其他类似资源,例如帐户,商品说明等。
- 提供高吞吐量。 有限的清单记录将有许多并发读写,因此数据库锁定可能会导致某些请求超时。
- 避免库存超标或不足。 如何处理可用的库存编号? 如果多个请求保留了相同的库存,则物品可能超卖。 如果保留了库存但流程失败并且没有下达库存,则该项目可能会被抛售。 这发生在某些电子商务网站上,通常他们会打电话给客户退款。
- 防止使用脚本作弊。 用户可以编写脚本以循环触发请求。 因此此用户可能会产生许多重复的请求
设计注意事项
- 来自上游的限制请求:我们的库存有限,即使有100万用户,我们也只能为其中的1000个服务。 可以减少用户不必要的请求数量吗?
- 分散流量:根本原因是我们要求用户同时来,并造成峰值。 我们可以将销售分成不同的时段并邀请用户使用不同的时段吗?
- 异步处理:我们需要立即处理请求吗? 还是我们可以接受请求,以我们的系统可以支持的速度处理请求,并在可接受的时间内通知用户?
- 高速缓存,高速缓存,高速缓存:许多重复读取的资源,永远不会/很少改变。 我们可以将它们保留在缓存中以减轻数据库负载吗?
- 了解权衡并做出牺牲:我们真的需要显示实时库存吗? 我们每年只进行一次这种闪购,是否有必要倾销大量资源?
- 数据库设计:针对同一问题的不同架构设计将具有不同的权衡。
- 可扩展性:我们是否可以水平扩展以支持更多用户?
逐层优化
客户
当用户在提交请求后没有立即得到响应时,他们将倾向于一次又一次地单击按钮。 但是,这些请求是重复的,并且会给系统造成不必要的负载。 (假设每个用户再提交9个请求,那么系统收到的请求中有90%是冗余的)
我们可以做的是:
- 提交后禁用“提交”按钮
- 仅在最近1分钟内没有重复的请求时发送请求。
这将减少来自客户端的大多数不必要的请求,但是,这不会阻止用户编写脚本来绕过客户端限制。 因此,我们需要做更多的事情。
网关
当请求到达网关时,我们会跟踪同一用户的请求号。 简单有效的解决方案是在内存中为每个用户ID保留一个计数器。
如果该用户最近发出了相同的请求,我们可以删除该请求或返回相同的响应。
但是,如果该用户创建了数千个用户帐户,该怎么办? 我们将在服务层中处理它。
服务
现在到达服务层的请求要少得多。 让我们开始处理它们。
- 管理可用库存
我们需要做的第一件事是检查可用库存。 我们如何获得剩余库存量?
首先,如果我们使用COUNT
聚合查询数据库,则可能需要对事务具有可serialisable
隔离级别,这会影响性能。 如果不应用此隔离级别,则可能会以幻象读取并认为我们仍然有此请求的足够库存,然后我们的销售量超过了现有量。
为了解决这个问题,我们可以使用原子性计数器。 当需要处理请求时,Redis将是原子减量的不错选择。
2.带消息队列的缓冲区
处理交易可能非常复杂且繁重。 例如,根据用户的忠诚度积分自动应用促销。
因此,让我们使用消息队列来缓冲请求,然后让服务按照自己的步调处理它。
数据库
现在,到达数据库的请求数量已大大减少到可接受的数量。
我们可以专注于设计更好的模式,优化查询以及可能对表进行分片。
讨论区
现在,我可以直接将此设计复制到我的项目中吗?
没有。 首先,这取决于您的要求。 其次,您可能不需要花费很多精力,可能还有其他更好的方法。
结论
这篇文章是Flash销售系统设计的简要案例研究。 它为正在解决类似问题的人们提供了总体指导,并可能为他们提供启发。
From: https://hackernoon.com/developing-a-flash-sale-system-7481f6ede0a3