马上双十一,教你用Python实现秒杀系统

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 马上双十一,教你用Python实现秒杀系统

简说Python,号主老表,Python终身学习者,数据分析爱好者,从18年开始分享Python知识,原创文章227篇,写过Python、SQL、Excel入门文章,也写过Web开发、数据分析文章,老表还总结整理了一份2022Python学习资料和电子书资源,关注后私信回复:2022 即可领取。

本篇用python+redis+rabbitmq搭建一个秒杀系统。用flask编写后端,只包含秒杀相关程序,省略具体的业务接口。项目会持续更新,完整代码见github:https://github.com/Sssmeb/seckilling(如果觉得有帮助的话可以点个star~~~~ )篇幅有限,不会介绍redis或rabbitmq的基本操作。如果没学过相关知识的只需先了解以下两点,也可以看懂本架构。

  1. redis是内存型数据库,读写速度远快于mysql这类磁盘型数据库,常用来作缓存。
  2. rabbitmq消息队列,可以理解为生产者消费者模型,用队列来存储任务,生产与消费解耦。

前言

在介绍架构之前,我们需要先知道秒杀系统面临的难点是什么。首先在普通的系统中,最大的瓶颈是在于底层的数据库端因为底层数据库(比如常见的mysql)是磁盘存储的,所以读写IO较慢,而且连接数有限。而在秒杀业务场景,最大的特点是瞬时的高并发,即在短时间内会有大量的请求到来。如果让所有请求都打到底层数据库上,很大可能数据库会直接崩掉,即使数据库能承受住大量的连接请求,但大量的请求读写都会导致大量的锁冲突,导致响应速度大大减慢。而响应速度对于用户体验来说,无疑是十分重要的。所以在这里,需要明确第一个目标:让尽可能少、尽可能有效的请求打到底层数据库当我们回头再考虑这个业务场景,其实绝大部分的请求都不应该打到底层数据库。因为一般商品库存可能只有抢购用户数的百分之一,甚至更少。所以我们需要一些机制、策略,提前将无效的请求返回。而站在整个网站设计的角度,第二个目标:越上层越容易实现,越有效。这里的层指:

  1. 页面层
  2. 网络层
  3. 应用层
  4. 服务层
  5. 数据层

例如在前端页面层,如果不做处理,用户在点击抢购按钮以后,见网页没有响应,可能会再点击3-4次甚至更多,这样可能会导致最终有80%的请求都是重复无效的。但只需要前端在设计时,将点击后的按钮置灰,防止用户多次点击发送请求。即简单又有效。

以下简单指出各层可实施的策略:

  1. 页面层(简单的实现可以屏蔽 90%的请求)
  • 按钮置灰,禁止用户重复提交
  • 验证码
  1. 网络层
  • 通过ip限制一定时间内的请求次数
  1. 应用层
  • 一个页面最占用资源、带宽的是cs js 图片等静态资源
  • 避免所有请求都到服务器的硬盘上取
  • 动静分离,压缩缓存处理(CDN nginx)
  • 根据uid限频,页面缓存技术(web服务器 nginx)
  • 反向代理 + 负载均衡 (nginx)
  1. 服务层
  • 微服务
  • redis
  • 消息队列 削峰 异步处理
  1. 数据层
  • 读写分离
  • 分库分表
  • 集群

每一层具体实现起来都是一个很大的架构,这里我们主要专注于服务层,使用redis+消息队列。

基础架构

image.png

架构.png核心:服务异步拆分,减少耦合,使用缓存,加快响应。避免同步的请求执行,如:请求→订单→支付→修改库存→结束返回,这种模型在高并发场景下,阻塞多,响应慢,服务器压力大,不可取。这里实现的架构是:1. 请求→返回      2. 支付→返回     3. 修改库存这种服务拆分归功于 消息队列。核心思想是,将接收到的请求 存储到队列中就可以响应用户了,后端在队列中取出请求再做后续操作即可。简单理解就是,我们将请求记录下来,晚点空闲了再处理。

基础数据存储

数据、请求的存储情况如:

  1. mysql中存储商品信息、订单信息
  2. redis存入商品信息、设置计数器、存储成功订单的数据结构等
  3. rabbitmq创建队列
  • 订单队列(用户提交请求)
  • 延迟队列(订单必须在15分钟内支付)
  • 成交队列(订单支付成功,等待写入数据库)

流程

以下所有代码都是截取核心部分,完整代码参看github: https://github.com/Sssmeb/seckilling

订单请求

redis计数器

假设我们只有100件商品库存,但可能会收到10万条抢购请求。也就是会有将近9.9万条无效的请求,所以我们要将这些请求阻隔。最简单的方法,也是我们使用的方法:实现一个count变量,每个请求进入都加一,当count大于100时则直接返回失败即可这里我们使用redis也是因为内存读写速度要远大于类似mysql的磁盘读写。

def plus_counter(goods_id, storage=100):
    count = redis_conn.incr("counter:"+str(goods_id))
    if count > storage:
        return False
    return True
代码实现增加了分布式锁。相关知识可以看: https://www.jianshu.com/p/cf311cfb1689

订单队列

异步拆分服务的关键。为了提高响应速度,我们只需要将请求订单任务保存下来(消息队列),就可以直接返回用户了。不需要将请求转到后端做大量的判断、处理、数据库读写操作后才返回用户所有可以大大的加快响应速度后端可以随时从队列中取出请求再做各自处理,即使等抢购活动结束再进行底层数据库读写也没有问题。所以核心思路就是把请求放入队列,然后直接返回用户即可。

# 计数器+1
flag = plus_counter(goods_id)
# 成功申请
if flag:
    # 生成唯一的订单号
    order_id = uuid.uuid1()
    # 订单信息(也是请求任务信息)
    order_info = {
        "goods_id": goods_id,
        "user_id": user_id,
        "order_id": str(order_id)
    }
    try:
        # 进入订单队列
        enter_order_queue(order_info)
        res["status"] = True
        res["msg"] = "抢购成功,请在15分钟之内付款!"
        res["order_id"] = str(order_id)
        return jsonify(res)
    except Exception as e:
        print("log: ", e)
        res["status"] = False
        res["msg"] = "抢购出错,请重试." + str(e)
        return jsonify(res), 202

enter_order_queue是将订单请求(订单信息),也就是order_info发送到对应的队列。与之对应的消费者,只需要将该订单信息写入数据库对应的订单表即可。注意:此时订单还没支付,所以数据库表中可以设置一个status字段,标识订单的状态。

唯一标识

不局限于uuid,可用毫秒时间戳之类的唯一标识。

可以看到上面代码中,我们利用uuid生成了一个唯一标识作为订单号,并且返回给用户。主要的作用是:

  1. 标识订单。因为订单请求仅仅只是被我们入队列,消费者可能还没开始处理。(即订单可能还未被创建在数据库中)
  2. 返回给用户,可用于后续的支付操作。

当用户支付时需要校验用户与对应的单号是否正确,这里我们仍用redis,以提高查询速度。所以在上面的基础上,我们需要加多一步,将订单信息写入redis。

order_info = {
            "goods_id": goods_id,
            "user_id": user_id,
            "order_id": str(order_id)
        }
try:
    # 在redis中创建这个订单
    create_order(order_info)
    enter_order_queue(order_info)
    res["status"] = True
    res["msg"] = "抢购成功,请在15分钟之内付款!"
    res["order_id"] = str(order_id)
    return jsonify(res)

订单的结构这里采用字典,提高检索效率。插入如:

redis_conn.hset("order:"+str(goods_id), str(order_id), str(user_id))

超时队列

正如前面所见,我们提示用户在15分钟之内支付,符合日常业务场景。在消息队列中有延迟队列的应用,符合我们的超时需求。所以我们同样用消息队列来实现这一业务需求。即我们在创建订单时,同样将订单信息传入队列中。

try:
    # redis保存订单信息
    create_order(order_info)
    # 订单队列
    enter_order_queue(order_info)
    # 超时队列
    enter_overtime_queue(order_info)

最终,当一个订单请求通过计数器后,需要经历的三个过程如上。无论是redis或是rabbitmq消息队列,都是内存操作,速度都是足够快的。不需要经过数据层即可响应用户。至此,一个订单“创建”完成。

支付请求

订单请求完成后,用户会获得订单号。用户必须在15分钟内完成支付操作。在执行支付时需要考虑:

  1. 检查用户和对应的订单号是否正确
  • create_order(order_info) 时,我们已将订单信息写入redis。可从这里取得数据做校验
  1. 检查订单是否超时
  • 如果我们设置的超时队列超过指定时间,则队列里的请求会被处理(消费)
  • 我们只需要将超时的单号写入redis即可做校验
  1. 支付成功入成交队列
  • 同理于订单队列。只需将成交的订单信息写入消息队列中,后续系统空闲时再写入数据库即可。
  • 也是为了提高用户响应速度,用户不需要等待数据库io完成后才收到结果。

代码流程为:

order_staus = check_order(order_info)    # 检查订单状态
if order_staus:                        
    if order_staus == -1:                # 人为设定 -1 表示超时
        res["msg"] = "订单已超时"
        return jsonify(res), 202
    else:
        # 支付函数
        pay()                        
        # 直接写入队列和redis
        enter_paid_queue(order_info)
        paid_order(order_info)
        res["status"] = True
        res["msg"] = "支付成功!!!!"
        return jsonify(res)

但订单通过检查、并支付完成后。我们还需要将成交的订单写入redis,记录状态(用于其他判断)。再将订单请求写入队列即可返回。全程内存操作,速度快,带来了快响应。之后,我们可以等抢购活动结束后,系统比较空闲的时间将订单同步到底层数据库,同步数据。

总览

所以两个核心的操作是:

  1. 通过rabbitmq消息队列异步拆分服务,加快了响应的速度
  2. 通过redis内存读写,减少操作时间

再总结整个框架:

  1. 用户提交订单
  • 通过redis计数器筛选
  • 成功则返回标识,然后入订单队列 + 超时队列
  • 标识与用户信息写入redis,用于后续验证支付
  • 订单队列,mysql监听,写入mysql的订单历史表
  • 超时订单队列有计时功能,一定时间内未支付,订单失效,抢购失败。写入redis(标志失败)
  • 失败直接返回
  • 订单服务结束
  1. 用户支付订单
  • 验证订单以及检查是否已超时(是否已在redis相关结构内)
  • 成功支付则入支付队列
  • mysql监听这个队列,执行库存同步操作。
  • 写入redis
  • 失败或超时直接返回
  • 支付服务结束

image.png

流程

注意

  1. 代码持续更新,完整代码:https://github.com/Sssmeb/seckilling (觉得有帮助的可以给个star)
  2. 本架构只专注于服务层的业务架构,有很多没有涉及的点(高可用,数据一致性等),一个完整的抢购系统是一个非常庞大的。
  3. 文中没有介绍mysql数据层相关的操作,一方面是为了提示大家,在高并发的情景下应该尽可能的避免这类的磁盘io操作。另一方面,mysql数据层相关操作是在消息队列 消费者进行操作的,这里不详解操作。只注重整体架构。具体操作见代码。

大家好,我是老表

觉得本文不错的话,转发、留言、点赞,是对我最大的支持。

欢迎关注微信公众号:简说Python关注后回复:1024,可以领取精选编程学习电子书籍。


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
28天前
|
机器学习/深度学习 人工智能 算法
基于Python深度学习的眼疾识别系统实现~人工智能+卷积网络算法
眼疾识别系统,本系统使用Python作为主要开发语言,基于TensorFlow搭建卷积神经网络算法,并收集了4种常见的眼疾图像数据集(白内障、糖尿病性视网膜病变、青光眼和正常眼睛) 再使用通过搭建的算法模型对数据集进行训练得到一个识别精度较高的模型,然后保存为为本地h5格式文件。最后使用Django框架搭建了一个Web网页平台可视化操作界面,实现用户上传一张眼疾图片识别其名称。
91 4
基于Python深度学习的眼疾识别系统实现~人工智能+卷积网络算法
|
2月前
|
机器学习/深度学习 人工智能 算法
猫狗宠物识别系统Python+TensorFlow+人工智能+深度学习+卷积网络算法
宠物识别系统使用Python和TensorFlow搭建卷积神经网络,基于37种常见猫狗数据集训练高精度模型,并保存为h5格式。通过Django框架搭建Web平台,用户上传宠物图片即可识别其名称,提供便捷的宠物识别服务。
314 55
|
1月前
|
安全 前端开发 数据库
Python 语言结合 Flask 框架来实现一个基础的代购商品管理、用户下单等功能的简易系统
这是一个使用 Python 和 Flask 框架实现的简易代购系统示例,涵盖商品管理、用户注册登录、订单创建及查看等功能。通过 SQLAlchemy 进行数据库操作,支持添加商品、展示详情、库存管理等。用户可注册登录并下单,系统会检查库存并记录订单。此代码仅为参考,实际应用需进一步完善,如增强安全性、集成支付接口、优化界面等。
|
3月前
|
机器学习/深度学习 数据采集 供应链
使用Python实现智能食品安全追溯系统的深度学习模型
使用Python实现智能食品安全追溯系统的深度学习模型
83 4
|
2月前
|
存储 缓存 监控
局域网屏幕监控系统中的Python数据结构与算法实现
局域网屏幕监控系统用于实时捕获和监控局域网内多台设备的屏幕内容。本文介绍了一种基于Python双端队列(Deque)实现的滑动窗口数据缓存机制,以处理连续的屏幕帧数据流。通过固定长度的窗口,高效增删数据,确保低延迟显示和存储。该算法适用于数据压缩、异常检测等场景,保证系统在高负载下稳定运行。 本文转载自:https://www.vipshare.com
128 66
|
2月前
|
机器学习/深度学习 人工智能 算法
【宠物识别系统】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+图像识别
宠物识别系统,本系统使用Python作为主要开发语言,基于TensorFlow搭建卷积神经网络算法,并收集了37种常见的猫狗宠物种类数据集【'阿比西尼亚猫(Abyssinian)', '孟加拉猫(Bengal)', '暹罗猫(Birman)', '孟买猫(Bombay)', '英国短毛猫(British Shorthair)', '埃及猫(Egyptian Mau)', '缅因猫(Maine Coon)', '波斯猫(Persian)', '布偶猫(Ragdoll)', '俄罗斯蓝猫(Russian Blue)', '暹罗猫(Siamese)', '斯芬克斯猫(Sphynx)', '美国斗牛犬
207 29
【宠物识别系统】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+图像识别
|
25天前
|
机器学习/深度学习 算法 前端开发
基于Python深度学习果蔬识别系统实现
本项目基于Python和TensorFlow,使用ResNet卷积神经网络模型,对12种常见果蔬(如土豆、苹果等)的图像数据集进行训练,构建了一个高精度的果蔬识别系统。系统通过Django框架搭建Web端可视化界面,用户可上传图片并自动识别果蔬种类。该项目旨在提高农业生产效率,广泛应用于食品安全、智能农业等领域。CNN凭借其强大的特征提取能力,在图像分类任务中表现出色,为实现高效的自动化果蔬识别提供了技术支持。
基于Python深度学习果蔬识别系统实现
|
28天前
|
Python
[oeasy]python057_如何删除print函数_dunder_builtins_系统内建模块
本文介绍了如何删除Python中的`print`函数,并探讨了系统内建模块`__builtins__`的作用。主要内容包括: 1. **回忆上次内容**:上次提到使用下划线避免命名冲突。 2. **双下划线变量**:解释了双下划线(如`__name__`、`__doc__`、`__builtins__`)是系统定义的标识符,具有特殊含义。
29 3
|
2月前
|
机器学习/深度学习 算法 前端开发
基于Python深度学习的果蔬识别系统实现
果蔬识别系统,主要开发语言为Python,基于TensorFlow搭建ResNet卷积神经网络算法模型,通过对12种常见的果蔬('土豆', '圣女果', '大白菜', '大葱', '梨', '胡萝卜', '芒果', '苹果', '西红柿', '韭菜', '香蕉', '黄瓜')图像数据集进行训练,最后得到一个识别精度较高的模型文件。再基于Django框架搭建Web网页端可视化操作界面,以下为项目实现介绍。
59 4
基于Python深度学习的果蔬识别系统实现
|
2月前
|
存储 算法 Python
文件管理系统中基于 Python 语言的二叉树查找算法探秘
在数字化时代,文件管理系统至关重要。本文探讨了二叉树查找算法在文件管理中的应用,并通过Python代码展示了其实现过程。二叉树是一种非线性数据结构,每个节点最多有两个子节点。通过文件名的字典序构建和查找二叉树,能高效地管理和检索文件。相较于顺序查找,二叉树查找每次比较可排除一半子树,极大提升了查找效率,尤其适用于海量文件管理。Python代码示例包括定义节点类、插入和查找函数,展示了如何快速定位目标文件。二叉树查找算法为文件管理系统的优化提供了有效途径。
60 5

热门文章

最新文章