秒杀系统设计

服务器开发技术、方法与实用解决方案

一、主要技术难点

1. 高并发

秒杀活动开始前,大量用户不断刷新活动页面,会使读请求量飙升;秒杀活动开始后,大量用户瞬时涌入抢购有限商品,会形成写请求“洪峰”

为了满足高并发读请求所需容量,可采用资源扩展策略和数据缓存策略,限流策略作为兜底保护。数据缓存通常由本地缓存、分布式缓存和CDN共同组成缓存方案。其中,商品信息一般采用二级缓存,图片信息一般采用三级缓存

对于写请求(下单请求)可通过限流前置拦截绝大部分流量,如客户端限流,直接返回失败提示信息,从而减轻商品库存处理环节的压力

2. 高可用

高可用实际包括网络高可用、服务高可用及存储高可用

网络系统包括防火墙、路由器、交换机等网络设备;服务器系统主要指提供服务的应用集群;存储系统指存储设施

冗余备份是最常见的高可用方案,当服务或应用因意外终止时,通过故障转移机制快速启用冗余或备用的服务器、系统、硬件或网络接替工作

3. 一致性

秒杀活动的参与者有两个:买家和卖家。买家操作包括查询库存、扣减库存、落订单记录、支付。卖家操作包括查询库存、编辑库存

编辑库存时,实际库存和卖家所见库存可能不一致,如采用了热点散列等技术,统计全局剩余库存时有时延导致库存不一致,修改库存时导致库存被错误覆写导致超卖

扣减库存时,一些秒杀系统会采用缓存来实现,该方式可能会导致超卖:数据库和缓存不一致、缓存本身的可见性都可能导致超卖

此外还可能存在少卖的问题:1.幂等控制失败、重复扣减库存;2.买家下单后,放弃付款或付款超时,导致库存占用却未交易成功

4. 防作弊

避免黄牛采用技术手段作弊,除了采用“答题”,“防链接暴露”等基础手段外,大型电商平台还会采用更为有效的风控校验手段,如人机识别、用户画像、关系网络(人际网络、媒介网络)等

  • 用户画像:基于用户身份特质、行为特质、设备环境信息、历史信用、风险关系网络等信息,建立用户风险画像,以识别用户是否属于作弊人群。一般采用LR、RF、C5等有监督分类算法,以及聚类、图算法等无监督算法进行建模
  • 关系网络:风险账户背后往往有一整条产业链及风险账号团伙,这些风险账号之间不可避免地会产生各种关联而不自知,因此可以通过已发现的风险账号关联其团伙中的其他账号,进行防控,如基于各类图算法(图指纹算法、连通图算法)进行基于图的风险关联控制。此外账号与手机号、账号与设备之间的异构网络的关联也能作为风险防控的依据。

二、电商平台库存运作

1. 库存模型

  • 可售库存数(SQ):即用户在客户端所见的商品可销售数量
  • 预扣库存数(WQ):即被未付款的订单占用的库存数量
  • 占用库存数(OQ):用户已付款,但尚未发货的订单占据的库存数量

2. 扣减模式

  • 拍减模式:用户下单后直接扣减可售库存sq。该模式不会超卖,但防御能力弱,可能会因为用户大量下单而不付款导致少卖
  • 预扣模式:用户下单时,预减库存,若订单在规定时间内未完成支付则释放库存。
  • 付减模式:用户完成付款时扣减可售库存sq。由于无法保证付款后一定有库存,该模式存在超卖风险。

3. 扣减执行流程

扣减库存主要包括两步:

  1. 扣减库存
  2. 插入库存扣减流水单据以记录上下文信息,包括用户id,库存id,商品id,幂等号等。为了保证库存扣减操作的幂等性,通常需要为扣减流水单据构建数据库唯一索引

为了保证数据一致性,两个步骤须放在同一个事务中

4. 库存查询

从用户动线来看,购买一件商品需要经过浏览详情、加购、结算、提交订单、确认支付等环节,这些环节基本都需要查询对应商品的库存,如果已无库存或库存不足应提醒用户,阻断流程。为了保证并发性能,查询库存时不会直接访问数据库,而是查询缓存

缓存库存的更新可以通过数据库binlog同步到缓存

5. 核心链路

库存运作核心链路:

  1. 用户点击加购将商品加入购物车;购物车用户点击结算会跳转到确认订单页;点击提交订单,服务端通过交易平台发起交易逻辑
  2. 若采用预扣模式,则调用库存平台执行预扣;采用拍减模式则执行扣减
  3. 交易平台向支付宝发起付款请求,支付宝创建支付订单
  4. 如果采用预扣模式且订单超时,支付宝将调用交易平台服务回查库存,并重新执行预扣
  5. 用户付款完成后,交易平台调用库存平台扣减库存
  6. 交易平台发消息给仓储中心,仓储中心创建订单,准备配货发货
  7. 仓储中心发货后,调用库存平台扣减占用库存数

三、库存架构演进

1. 独立主机

所有商品的库存存放于同一数据库的同一表中,来自应用系统的所有库存扣减请求最终都路由到同一数据库实例。该方式性能并发量较小

2. 分库分表

对数据进行水平拆分后,将不同商品的库存扣减请求路由到不同数据库,降低单机负载并显著提升并发能力。

采用商品库存id作为分表键,可以保证对同一商品库存的扣减操作和插入流水操作在同一事务中实现

3. 热点处理

如果针对某一商品,其库存数据只对应数据库的一条行记录,分库分表依旧无法解决问题。为了解决库存扣减热点问题,一般有两条技术路线:内核优化和热点散列

  • 内核优化
    优化数据库内核,提升行更新操作的性能。如“水车”模型:在应用层做轻量化改造,对热点行SQL打上热点标签,当这类SQL进入内核后,在内存中维护一个hash表,将主键(如商品Id)hash到统一地方作请求合并,经过一段时间后(默认100us)后统一提交,从而将串行处理优化为批处理,避免每个热点请求都去扫描和更新Btree

  • 热点散列
    在大规模秒杀活动中,针对单一商品的库存扣减峰值可以达到几万甚至几十万,单凭优化数据库内核依旧很难满足需求。针对这种情况,最有效的方案是采用热点散列,即分布式库存扣减,将同一商品的库存提前分配至多个桶中,根据路由规则将库存扣减请求路由至不同的桶,从而将集中于单实例的请求分散

“分桶”的一种技术实现是采用Redis。在缓存中扣减库存以提升系统的吞吐量;缓存扣减成功后异步向数据库写入库存扣减流水并更新库存;此外,还需要通过定时任务等机制实现缓存与数据库总量同步

该方案存在的问题:

  1. 在缓存中扣减如何保证幂等
  2. 如何保证缓存和数据库中库存数据的一致性
  3. 如何保证用户请求被路由到的分桶有足够的库存

四、库存单元化

由于库存具有全局性,买家、卖家对商品库存的写操作需做到彼此可见才能有效避免商品超卖,因此库存系统一般采用中心写、单元读。随着业务的发展,“中心写”模式逐渐成为整个库存系统的容量瓶颈,由此产生了库存单元化架构

如果不采用中心写,可能在两个单元上分别进行扣减库存,导致超卖

1. 单元封闭

单元封闭,即保证每一次库存操作都在所属单元内完成。

如A、B两个单元,A处理UID尾号在00~10之间的用户,B处理尾号在11~20之间的用户,A、B互为备份。如果B发生故障,则需将B单元的流量切换到A。但切流是有前提的,需要将B单元写入的数据全部同步到A单元,否则B单元的用户被路由到A单元后将无法看到自己的最新数据

在故障切流的过程中,为了不产生脏数据,且减小对用户体验的影响,目前已形成一套标准的解决方案——禁止写+禁止更新

在用户路由切换过程中禁止写入操作,在数据同步过程中禁止更新操作,保证对历史单据的更新操作必须基于最新的镜像数据,但是对插入操作放开,因为插入操作意味着首次下单

2. 全局库存和局部无库存问题

为了实现购买数量上限应为所有单元可售卖库存之和,需要具备全局库存管理能力。在实际应用中,一般采用异步汇总的方式实现,本质上是中心写、单元读

在使用单元化库存架构时,可能会出现全局库存还有剩余,但是部分单元已售光的问题。为解决这一问题,可以发起库存调拨,从中心到单元做一次即使虚拟出库和入库操作;当中心无库存时,需要将所有单元的库存回收

你可能感兴趣的:(系统架构设计,java)