猿创|可能是最快的高并发单机秒杀系统设计方案

文章截图

点击图片,手动扩大图片即可清晰查看

下面的排版可能引起阅读不适

下面是我的答案,与 PHP 和 MySQL 没什么关系,因为本人认为高并发是不能到 PHP 处理和 MySQL 层面的,

目前比较理想的架构是 openresty + Redis,详见下文

推荐软硬件环境

1 Redis 最新稳定版

https://redis.io/download

$ wget http://download.redis.io/releases/redis-5.0.3.tar.gz $ tar xzf redis-5.0.3.tar.gz $ cd redis-5.0.3 $ make # 可以加上多核编译参数 -j2 表示使用 2 cpus $ sudo make install $ cd utis/ $ sudo ./install_server.sh # 一直回车 (Enter) $ sudo service --status-all |grep redis # 查看 redis 服务进程状态

2 Openresty 最新稳定版

http://openresty.org/en/installation.html

http://openresty.org/cn/installation.html(中文)

推荐安装方式:

http://openresty.org/en/linux-packages.html

http://openresty.org/cn/linux-packages.html(中文)

3 tree 和 htop

主要是便于查看文件树信息和进程信息

4 网络带宽临时调大至 10M/s-30M/s

峰值流量可能会很大

5 SSD 固态硬盘不可少

Redis 持久化必选项

实例分析

0 活动描述

现有商品 A(sku_id=1) 、B(sku_id=2) 、C(sku_id=3),要求 2018.12.15 当天的 10:30-11:00 开放秒杀数量: A(1000 件)、B(2000 件)、C(3000 件) 与 14:30-15:00 开放秒杀数量:A(1000 件)、B(2000 件)、C(3000 件),

每次秒杀活动持续 30 分钟,每人限购:A(1 件)、B(2 件)、C(3 件)

1 刷入商品信息(难度系数:★★)

步骤:

第一步,刷入 sku 秒杀数量的数据到 Redis 的 List 数据结构中

刷入 sku_id 为 1 的商品 A 到 ms1544841000_1544842800:sku1 和 ms1544841000_1544842800:sku_copy1(备份) 中(B、C 商品类似),其中1544841000_1544842800 表示活动开始的 unix 时间戳(精确到秒)和活动结束的 unix 时间戳(精确到秒),要求在活动开始前成功刷入数据,也就是分别在 10:30 之前和 14:30 之前,例如:

1,1,1,1,.....# 存入商品 sku_id 即可,但是也可以存入商品的其它信息,例如让用户秒杀到一样的价格,这时最好以 json 格式存储

第二步,设置列表的过期时间

使用 Redis 的 expireat 命令来指定 11:00 的 unix 时间戳(精确到秒)为过期时间,仅需要设置 key 为 ms1544841000_1544842800:sku1 即可,另一个 key 为 ms1544841000_1544842800:sku_copy1 仅用于记录

第三步,刷入历史活动信息列表到 Redis 的 key 为 act_hmap_20181215 的 hashmap 数据结构

注意,act_hmap_20181215 这个 key 是以天为单位的。

key:1_1544841000_1544842800 value:{"sku_id":1,"start":1544841000,"end":1544842800,"total":1000,"limit":1,"rest":1000} #其中 rest 表示剩余数量,用于提供给 API 接口,以及其它 sku 信息字段,不过我建议使用指定的 sku Redis key 来保存

2 前端展示秒杀活动信息(难度系数:★)

步骤:

第一步,前端通过 API 接口获取 2018.12.15 当天的活动列表 act_hmap_20181215,并按照 hashmap 的 key 排序

第二步,展示必要的 sku 信息字段

商品图片 URL 列表,自定义字段键值对,活动开始时间与结束时间等,建议活动以外的周边信息如 sku 详细信息与商铺信息应该放在其它 Redis key 中,以便于跟 MySQL 数据库同步

3 活动进行中(难度系数:★★★)

步骤:

第一步,秒杀资格检查+避免刷单

最好是让有秒杀资格的人来参与秒杀,存入到 Redis 缓存中,没有资格的人连秒杀的商品界面都不要展示出来,

为了避免机器刷单的情况,考虑加入 WAF (web 防火墙)和 单用户 Id 调用频率的控制问题,如指定用户 ID 出现异常行为,应该立刻把此用户 ID 禁用掉,或者展示图形验证码+短信验证码的形式,通过验证就可以再次秒杀。

第二步,避免重复下单

应该使用使用 act:1_1544841000_1544842800:user_12 这种形式的 Redis Key 来判断是否重复下单,Redis 的 setnx 可以帮你忙,

第三步,不要使用 MySQL

下单过程中,切记不可使用 MySQL,高并发的主要瓶颈之一,推荐架构:Openresty + Redis + MySQL(MySQL 最好放到另一台机器上,因为它太笨重了)

第四步,异步化订单处理

支付之后,系统需要生成秒杀订单数据(业务逻辑最重的环节),并调用第三方支付接口,这时可以先把信息放到 Redis 的 List 数据结构中,鉴于单机,所以这里不建议使用 Rabbit MQ,

因为它过重了,然后以恒定的速度向 MySQL 持久化订单信息以及同步 Redis 里面的秒杀订单数据,在此期间展示的秒杀订单状态是“支付结果处理中”,处理成功后,则在秒杀订单列表页展示对应的订单信息。

第五步,超量检测

由于使用了 Redis 的 List 数据结构,为了保证秒杀的成功率,秒杀成功则从里面 lpop 出指定数量的数据,应该只给用户的未支付秒杀订单 20 秒的倒计时支付时间,

如超过 20 秒未支付,则无法再次从本次活动中秒杀,并把没有秒杀成功的 sku 回收到该 List 中且最好把待回收(已锁定)数量也展示出来,因为使用了 List 数据结构,所以就算 lpop 到最后也不会出现超量的情况,但是却可能引发下面的问题:

问题1:明明前端展示还剩 20 件商品未秒杀完,为何我点击购买时却提示我秒杀完成

答:因为前端展示的数据跟服务器上面的数据并非实时同步的

4 活动结束后

这个就与并发没有什么关系了,主要是统计一下数据与核对订单信息什么的

同步更新的原文链接:http://note.youdao.com/noteshare?id=346f9e43a25b8fda1bb8f7c4722c9abd&sub=C629D330B273472AB9407E767F6B39A3

原文发布于微信公众号 - PHP技术大全(phpgod)

原文发表时间:2018-12-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券