一个极简、高效的秒杀系统(战略设计篇)

标签: 系统 战略 设计 | 发表时间:2020-04-02 04:54 | 作者:老马
出处:http://weekly.dockone.io

2018年,楼主所在业务线开始发力探索线上O2O业务,但楼主所在公司并非纯电商公司,电商体系标配的商品中心、库存中心、活动系统都处于萌芽阶段不成气候。10月中旬,业务决定搞一波双十一大促提升品牌知名度。整个大促最核心功能点就是造一个秒杀系统,为整个活动会场积蓄流量。作为该业务线营销工作负责人,这个秒杀系统设计任务自然就落到楼主身上。时间紧任务重,从需求提出到上线,2周时间紧锣密鼓开发,多次代码写到凌晨两点。值得欣慰的是,最终这个系统完美支撑了大促,帮助业务拿到显著超出预期的结果。时光飞逝,一晃一年多过去了,楼主19年初也转战另一条新业务线负责导购交易,这篇文章就当是对这个念念不忘的秒杀系统一个总结,祭奠那段写代码不知疲倦激情满满的岁月。

友情提示:网上搜秒杀系统架构,大多一上来就开始讲CDN静态资源缓存、Nginx请求拦截、缓存设计、消息队列削峰、请求限流等一系列技术点。看似面面俱到考虑周全,但楼主真忍不住想问,以当前业务体量,真有必要搞那么多招式吗?以上技术点本文通通不涉及!阅读本文不需要高深的知识,楼主就想从纯后端角度,带领大家看一看如何快速落地一个秒杀系统。

这一篇题目标注「战略篇」,事实上来源就是楼主做秒杀需求的技术方案。内容过于真实,楼主摘取最核心的内容贴出来。从一个亲历者角度来复盘一个秒杀系统是如何设计并落地的。

业务需求

产品需求

此处应该贴产品需求文档;我简单概述下:双十一业务要上线活动大促,需要通过秒杀这种玩法来为整个会场蓄水流量。秒杀活动的玩法是:指定几个商品,商品库存有限,同时每个商品还限制单个用户累计只能买N件。

业务流程

秒杀活动整体业务流程

通过对需求分析,我们提炼出秒杀活动的三大子流程,即:(运营)创建秒杀活动、(运营/买家)查看秒杀活动、(买家)参与秒杀活动;有了这个整体把握,我们再针对每一个子流程,分析该场景下需要编排哪些产品功能。

创建秒杀活动

创建秒杀活动:做的事情很简单,需要配置好活动场次,每个场次又需配置参与活动的秒杀商品;这个功能点,主要是面向运营,为了方便运营完成活动配置编辑,附带着还需提供活动查询能力:如查看活动列表、活动详情、发布/禁用活动等mis接口。

查看秒杀活动

查看秒杀活动:主要功能为支持秒杀会场的活动列表、秒杀商品列表、秒杀商品详情等活动页面;这些都是直接面向C端用户的系列读接口,承载流量会很高。

参与秒杀活动

查看秒杀活动:这一块核心述求是要能正确高效完成库存扣减,严格保证不能出现超卖!从功能点上来看,C端买家下单实现秒杀商品库存扣减,如果买家在规定时间未完成支付或拍下后取消订单,需及时释放用户下单锁定的库存,也就是要回库存。

小节

这一章首先通过需求分析提炼,建立需求整体大局观(创建秒杀活动、查看秒杀活动、参与秒杀活动)。然后通过拆解需求三大业务子流程,明确各子流程功能点,进一步分而治之。明确业务流程和场景,有了清晰的产品功能认识,我们就可以开展下一步概要设计了。

概要设计

E-R 关系图

E-R关系图,用于指导如何建立领域模型。从E-R图上我们能看出,几个比较重要的领域模型:如活动、活动商品,系统真正编码落地的时候,就紧紧围绕这些领域模型去建模,做到代码和领域模型的表达是一致的。

产品边界

概要设计的目的是为了明确产品功能和系统边界,通过领域驱动的界限上下文图,能清晰地看出完成当前需求需要参与协作的团队,以及团队与团队之间任务划分边界。活动上下文是我们关注的重点,同时也应该看到,我们需要商品团队、交易团队的协作。

接口定义

通过概要设计划清了系统边界,每部分每个团队应该做什么就容易确定了,Api定义呼之欲出。

配置活动

1、定义
  • 设置活动、活动商品、活动库存、开始结束时间等配置
  • 系统需为每场活动分配全局唯一活动id:若提交的数据带有活动id,则表示更新


2、接口变更:新增接口

3、API:POST http://${domain}/api/v1/activity/save

4、输入:
{  
"activityName": "双十一秒杀第一场",
"startTime": 1540174800000, // 活动开始时间
"endTime": 1541988000000, // 活动结束时间
"itemLine": [
{
  "itemId": 123, // 活动商品id
  "itemType": 7, // 活动商品类型
  "itemTitle": "这是商品标题",
  "subTitle": "这是商品副标题",
  "itemImage": "这是图片链接",
  "salePrice": 66800, // 商品原价(单位:分)
  "activityPrice": 100, // 活动价
  "quota": 3, // 单个用户商品抢购件数限制
  "stock": 100 // 商品活动库存
}
],
"activityRuleConfigs": [ // 活动规则列表
{
  "configKey": "city", // 城市规则:在规则列表的城市可看到活动
  "configValue": "17,5,10,2,3,4,11"
}
]


5、输出:
{  
"traceId": "2910c88d0d4f45d5fe299f0c5829d72c",
"code": "SERVICE_RUN_SUCCESS",
"msg": "服务运行成功",
"status": 10000,
"success": true


活动列表

1、定义:返回已创建的全部活动简要信息(不包含活动商品及销量)

2、接口变更:新增接口

3、API:POST http://${domain}/api/v1/activity/list

4、输入:无

5、输出:
{  
"traceId": "2910c88a0d4f45d5be290f0c5829d72c",
"success": true,
"status": 10000,
"msg": "OK",
"code": "SUCCESS",
"data": [
{
  "activityId": 1,
  "activityName": "双十一秒杀第一场",
  "startTime": 1541901600000,
  "endTime": 1741951999000,
  "enabled" true
}
]


活动详情

1、定义:返回指定活动详细信息(包含活动商品及其销量)

2、接口变更:新增接口

3、API:GET http://${domain}/api/v1/activity/detail

4、输入:activityId=1

5、输出:
{  
"traceId": "889924ef8e6241a7a766107f38c5e0c0",
"success": true,
"status": 10000,
"msg": "OK",
"code": "SUCCESS",
"data": {
"activityId": 1,
"activityName": "双十一秒杀第一场",
"startTime": 1541901600000,
"endTime": 1741951999000,
"enabled" true
"items": [
  {
    "itemId": 53725,
    "itemType": 1,
    "itemTitle": "x商品",
    "subTitle": "x商品副标题",
    "itemImage": "http://img.xxxx.com/static/do1_QtSq1m2xM7VL6zEI4sUH",
    "itemPrice": 19800,
    "activityPrice": 4800,
    "quota": 3,
    "stock": 50,
    "sold": 0
  },
  {
    "itemId": 53724,
    "itemType": 1,
    "itemTitle": "y商品",
    "subTitle": "y商品副标题",
    "itemImage": "http://img.xxxx.com/static/MrcNjUeeoOG24zZH7nR.png",
    "itemPrice": 42800,
    "activityPrice": 17000,
    "quota": 3,
    "stock": 50,
    "sold": 0
  }
]
}


活动商品详情

1、定义:返回活动商品详细信息(包含活动商品销量、活动信息)

2、接口变更:新增接口

3、API:GET http://${domain}/api/v1/activity/itemDetail

4、输入:activityId=1&itemId=53725

5、输出:
{  
"traceId": "a77edf653da644959d331b7b55607958",
"success": true,
"status": 10000,
"msg": "OK",
"code": "SUCCESS",
"data": {
"itemId": 53724,
"itemType": 1,
"itemTitle": "x商品",
"subTitle": "商品副标题",
"itemImage": "http://img.xxxx.com/static/do1_QtSq1m2xM7VL6zEI4sUH",
"itemPrice": 42800,
"activityPrice": 17000,
"quota": 3,
"stock": 50,
"sold": 0,
"activity": {
  "activityId": 1,
  "activityName": "双十一秒杀第一场",
  "startTime": 1541901600000,
  "endTime": 1741951999000,
  "enabled" true
}
}


扣库存

1、定义:扣活动库存、扣用户参与抢购资格(注:不局限Http接口,可采用Dubbo调用;此处仅方便演示)

2、接口变更:新增接口

3、API:POST http://${domain}/api/v1/stock/reduce

4、输入:
{  
"activityId": 1,
"buyerId": "buyer_001",
"itemId": 53724,
"orderId": "20191111123456789",
"orderTime": 1541901700000,
"quantity": 1


5、输出:
{  
"traceId": "f689852f113e413d9940ce24020e7083",
"success": true,
"status": 10000,
"msg": "OK",
"code": "SUCCESS",
"data": true


回库存

1、定义:回商品活动库存、回用户参与资格;可看做是扣库存的逆向操作;(注:笔者在真正实现时,未采用Http接口,而是通过监听订单MQ来异步回库存)

2、接口变更:新增接口

3、API:POST http://${domain}/api/v1/stock/cancelReduce

4、输入:
{  
"activityId": 1,
"orderId": "20191111123456789"


5、输出:
{  
"traceId": "5342243fd424468ab9ad13d03ffcdc62",
"success": true,
"status": 10000,
"msg": "OK",
"code": "SUCCESS"


详细设计

系统流程

1、创建秒杀活动

2、查看秒杀活动

3、参与秒杀活动

数据模型

纵观整个需求的核心,就是如何做好库存扣减。在系统落地上,楼主采用了业界较为广泛的Redis + Lua脚本方式来实现库存扣减控制。

活动配置表结构

说明:
  • activity_catalog这个Hash结构,用来配置活动信息(活动id、名称、开始/结束时间、活动准入规则等);全部活动共用这一个结构
  • activity_items:$活动id这个Hash结构,用来配置指定活动的商品信息(活动库存、限购量、活动价等);每一个活动都有一个这样的结构


库存扣减核心表结构

说明:
  • buyer_hold:$活动id:$商品id,这个Hash结构,用来记录买家在某个活动拍下活动商品的数量
  • item_salse:$活动id,这个Hash结构,用来记录活动商品销量
  • stock_reduce_flow$活动id,这个Hash结构,称为库存扣减流水表。用来记录「哪个活动(activityId)哪个买家(buyerId)在何时(orderTime)下了哪个订单(orderId)拍下哪个商品(itemId)多少件(quantity)」这一库存扣减流水


为什么是这三个结构?
  • buyer_hold:$活动id:$商品id,可以知道用户已拍下多少件,就能做到控制用户累计只能买N件;
  • 通过 item_salse:$活动id 能知道商品已售出多少件,再结合商品的库存限制,就有办法去控制库存避免超卖;同时也能给C端透出商品秒杀进度。
  • 通过库存扣减流水stock_reduce_flow$活动id,在做回库存的时候,就能依据这一流水,知道去回哪个商品的库存、以及回哪个买家的已拍下数量。


放张图,直观感受下上面罗列的五种数据结构;眼精的同学肯定会发现,其中有四个Hash结构的key都带上了{seckill_$活动id}的前缀。为何要这样特殊处理?其实楼主也在《 这就是你要找的分布式锁》这篇中有所提及,原因在于Redis集群环境下Lua脚本操作的key,必需限制这些key落在同一个slot中,否则运行会报错Lua script attempted to access a non local key in a cluster node . channel;对此,Redis就提供了HashTag的方案,HashTag是用{和}包裹的一个子串,相同HashTag子串,会落到同一个slot中。

库存扣减Lua脚本伪代码演示

扣库存:扣用户抢购资格、扣商品库存、记录库存扣减流水
hincrby buyer_hold:$活动id:$商品id  $买家id  $抢购数量  
hincrby item_sales:$活动id  $商品id   $抢购数量
hset stock_reduce_flow:$活动id  $订单id  $json化库存扣减流水

回库存:回用户抢购资格、回商品库存、删除库存扣减流水
hincrby buyer_hold:$活动id:$商品id $买家id -1*$抢购数量  
hincrby item_sales:$活动id  $商品id -1*$抢购数量
hdel stock_reduce_flow:$活动id $订单id

总结

至此,我们就就从业务流程、产品流程、系统流程,由整体到局部,完成了整个秒杀系统的需求分析和接口定义;Talk is cheap, Show me your code,在下一篇《一个极简、高效的秒杀系统(实战篇)》楼主会再结合源码,一步步揭开整个秒杀系统的面纱;最后的最后,写作不易,人过留名燕过留声,如果有收获,点个赞呗~

原文链接: https://blog.csdn.net/caiguoxi ... 66073,作者:温柔一cai刀

相关 [系统 战略 设计] 推荐:

一个极简、高效的秒杀系统(战略设计篇)

- - DockOne.io
2018年,楼主所在业务线开始发力探索线上O2O业务,但楼主所在公司并非纯电商公司,电商体系标配的商品中心、库存中心、活动系统都处于萌芽阶段不成气候. 10月中旬,业务决定搞一波双十一大促提升品牌知名度. 整个大促最核心功能点就是造一个秒杀系统,为整个活动会场积蓄流量. 作为该业务线营销工作负责人,这个秒杀系统设计任务自然就落到楼主身上.

评价系统设计篇

- - 互联网 - ITeye博客
评论系统大家都见得非常多了,大到京东、淘宝、亚马逊,小到个人网站、博客都有评论系统,小型网站采用传统PHP+Mysql方式就能很快将系统搭建起来,同时采用单库单表方式就能轻松解决数据存储、数据查询等问题,但是对于上述中大型网站而言,已经远远不能支撑系统正常运行了. 接下来将从系统架构、数据存储、高性能服务等方面来揭示京东的评价系统在面对海量数据、海量请求的情况是如何处理的.

Mozilla 2012年新战略:挑战微软谷歌生态系统

- - TechWeb 今日焦点 RSS阅读
Mozilla试图利用开放的Web来挑战封闭的生态系统.   导语:Mozilla周日公布了2012年发展战略,Mozilla认为,网民的敌人就是苹果、谷歌、亚马逊和微软等竞争对手建立的由设备、操作系统、应用商店和应用组成的封闭生态系统,而Mozilla的使命就是利用开放的Web来挑战这些封闭的生态系统.

思考系统API设计的问题

- edware_love - C++博客-首页原创精华区
最近正好在思考系统API设计中考量的一些问题,. 我现在的理解是这样的,假设有巨大的真实内存. windows首先将高2G的内存自己占了,用作各种内核对象. 这2G内存共享给每个进程,但进程不能直接访问,只能通过windows给定的函数访问. : 然后每个进程都给他2G内存,进程如果创建自己的对象就放到自己那2G内存里面,如果要建立内核对象就放到共享的那高2G里面去.

系统设计中的简单法则

- - 酷勤网-挖经验 [expanded by feedex.net]
最近,包云岗在自己的 博客中总结了系统设计中的基本法则——简单之美,列举了不少经典观点和案例. 他首先总结了麻省理工方法(MIT Approach)和新泽西方法(New Jersey Approach)的异同:. 简单性:两种方法都强调设计必须简单,这既是对实现的要求,也是对接口的要求. 但是,MIT方法认为接口的简单要比实现的简单更加重要,而NJ方法认为实现的简单要比接口的简单更加重要.

财务系统设计的思考

- - 行业应用 - ITeye博客
说到财务系统的设计,就不由得联想到了目前很流行的一个职业“互联网产品经理”,他们的设计着眼于用户体验,创造出新的功能,改善着上亿网民的生活,比如扫一扫,摇一摇等. 财务系统不同于互联网的产品,它的复杂性对于没有深入了解它的人来说,是不太能想象出来的. 互联网的功能开发,讲究的是时效,从一个点子,到产品发布可能只用一周的时间,然后如果市场冷淡,可能第三周就下线了.

12306订票系统设计关键点

- - 互联网旁观者
12306全国火车票网上售票网站的情况大家都见到了,如果让你来设计该订票网站,你会如何设计才能应对如此大规模以及高并发的情况呢. 以下是百度前技术总监邵辉给出的设计:. 列车在线订票系统的业务逻辑比较简单,不用多说. 可能的瓶颈有两个,一个是车次和剩余票量的查询,一个是下单. 在设计软件架构之前,需要先研究产品需求、软硬件条件、网络环境以及关联系统的接口,但这些资料无从获得,所以只能做几点分析和假设,做为设计的前提条件.

网购秒杀系统架构设计

- - 企业架构 - ITeye博客
秒杀活动只是网站营销的一个附加活动,这个活动具有时间短,并发访问量大的特点,如果和网站原有应用部署在一起,必须会对现有业务造成冲击,稍有不慎可能导致整个网站瘫痪. 用户在秒杀开始前,通过不停刷新浏览器页面以保证不会错过秒杀,这些请求如果按照一般的网站应用架构,访问应用服务器、连接数据库,会对应用服务器和数据库服务器造成极大的负载压力.

秒杀系统设计的知识点

- - 互联网 - ITeye博客
A, 高并发,cache,锁机制 . B, 基于缓存架构redis,Memcached的先进先出队列. C, 稍微大一点的秒杀,肯定是分布式的集群的,并发来自于多个节点的JVM,synchronized所有在JVM上加锁是不行了. F, 如何防止用户来刷, 黑名单. G, 利用memcached的带原子性特性的操作做并发控制. .

O2O供应链系统架构设计

- - 美团技术团队
本文是美团技术沙龙第一期, O2O技术架构与实践上的分享内容. 请在微信搜索“美团技术团队”关注我们的公众账号,了解更多活动信息. 英国知名供应链专家Martin Christopher曾经说过一句非常深刻的话:“21世纪的竞争不是企业和企业之间的竞争,而是供应链和供应链之间的竞争. 在风云变幻、寡头纷争的O2O战场,美团屡出重拳并步步为营,战绩不俗.