简书博客地址:

https://www.jianshu.com/u/8f4d80000566

大家好,我是老表

阅读文本大概需要 10 分钟

坚持学习很难,养成学习习惯更难

架构搭建是重点,代码或语言实现较简单。

本篇用python+redis+rabbitmq搭建一个秒杀系统。

用flask编写后端,只包含秒杀相关程序,省略具体的业务接口。

项目会持续更新,完整代码见github:

https://github.com/Sssmeb/seckilling

(如果觉得有帮助的话可以点个star~~~~ )

篇幅有限,不会介绍redis或rabbitmq的基本操作。

如果没学过相关知识的只需先了解以下两点,也可以看懂本架构。

redis是内存型数据库,读写速度远快于mysql这类磁盘型数据库,常用来作缓存。

rabbitmq消息队列,可以理解为生产者消费者模型,用队列来存储任务,生产与消费解耦。

前言

在介绍架构之前,我们需要先知道秒杀系统面临的难点是什么。

首先在普通的系统中,最大的瓶颈是在于底层的数据库端。

因为底层数据库(比如常见的mysql)是磁盘存储的,所以读写IO较慢,而且连接数有限。

而在秒杀业务场景,最大的特点是瞬时的高并发,即在短时间内会有大量的请求到来。

如果让所有请求都打到底层数据库上,很大可能数据库会直接崩掉,即使数据库能承受住大量的连接请求,但大量的请求读写都会导致大量的锁冲突,导致响应速度大大减慢。

而响应速度对于用户体验来说,无疑是十分重要的。

所以在这里,需要明确第一个目标:

让尽可能少、尽可能有效的请求打到底层数据库。

当我们回头再考虑这个业务场景,其实绝大部分的请求都不应该打到底层数据库。

因为一般商品库存可能只有抢购用户数的百分之一,甚至更少。

所以我们需要一些机制、策略,提前将无效的请求返回。

而站在整个网站设计的角度,第二个目标:

越上层越容易实现,越有效。

这里的层指:

页面层

网络层

应用层

服务层

数据层

例如在前端页面层,如果不做处理,用户在点击抢购按钮以后,见网页没有响应,可能会再点击3-4次甚至更多,这样可能会导致最终有80%的请求都是重复无效的。

但只需要前端在设计时,将点击后的按钮置灰,防止用户多次点击发送请求。

即简单又有效。

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

页面层(简单的实现可以屏蔽 90%的请求)按钮置灰,禁止用户重复提交

验证码

网络层

通过ip限制一定时间内的请求次数

应用层

一个页面最占用资源、带宽的是cs js 图片等静态资源

避免所有请求都到服务器的硬盘上取

动静分离,压缩缓存处理(CDN nginx)

根据uid限频,页面缓存技术(web服务器 nginx)

反向代理 + 负载均衡 (nginx)

服务层

微服务

redis

消息队列 削峰 异步处理

数据层

读写分离

分库分表

集群

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

基础架构

架构.png

核心:服务异步拆分,减少耦合,使用缓存,加快响应。

避免同步的请求执行,如:

请求→订单→支付→修改库存→结束返回,这种模型在高并发场景下,阻塞多,响应慢,服务器压力大,不可取。

这里实现的架构是:

1. 请求→返回      2. 支付→返回     3. 修改库存

这种服务拆分归功于 消息队列。

核心思想是,将接收到的请求 存储到队列中就可以响应用户了,后端在队列中取出请求再做后续操作即可。

简单理解就是,我们将请求记录下来,晚点空闲了再处理。

基础数据存储

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

mysql中存储商品信息、订单信息

redis存入商品信息、设置计数器、存储成功订单的数据结构等

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生成了一个唯一标识作为订单号,并且返回给用户。

主要的作用是:

标识订单。因为订单请求仅仅只是被我们入队列,消费者可能还没开始处理。(即订单可能还未被创建在数据库中)

返回给用户,可用于后续的支付操作。

当用户支付时需要校验用户与对应的单号是否正确,这里我们仍用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分钟内完成支付操作。

在执行支付时需要考虑:

检查用户和对应的订单号是否正确

create_order(order_info) 时,我们已将订单信息写入redis。可从这里取得数据做校验

检查订单是否超时

如果我们设置的超时队列超过指定时间,则队列里的请求会被处理(消费)

我们只需要将超时的单号写入redis即可做校验

支付成功入成交队列

同理于订单队列。只需将成交的订单信息写入消息队列中,后续系统空闲时再写入数据库即可。

也是为了提高用户响应速度,用户不需要等待数据库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,记录状态(用于其他判断)。

再将订单请求写入队列即可返回。

全程内存操作,速度快,带来了快响应。

之后,我们可以等抢购活动结束后,系统比较空闲的时间将订单同步到底层数据库,同步数据。

总览

所以两个核心的操作是:

通过rabbitmq消息队列异步拆分服务,加快了响应的速度

通过redis内存读写,减少操作时间

再总结整个框架:

用户提交订单

通过redis计数器筛选

成功则返回标识,然后入订单队列 + 超时队列

标识与用户信息写入redis,用于后续验证支付

订单队列,mysql监听,写入mysql的订单历史表

超时订单队列有计时功能,一定时间内未支付,订单失效,抢购失败。写入redis(标志失败)

失败直接返回

订单服务结束

用户支付订单

验证订单以及检查是否已超时(是否已在redis相关结构内)

成功支付则入支付队列

mysql监听这个队列,执行库存同步操作。

写入redis

失败或超时直接返回

支付服务结束

流程

注意

代码持续更新,完整代码:https://github.com/Sssmeb/seckilling (觉得有帮助的可以给个star)

本架构只专注于服务层的业务架构,有很多没有涉及的点(高可用,数据一致性等),一个完整的抢购系统是一个非常庞大的。

文中没有介绍mysql数据层相关的操作,一方面是为了提示大家,在高并发的情景下应该尽可能的避免这类的磁盘io操作。另一方面,mysql数据层相关操作是在消息队列 消费者进行操作的,这里不详解操作。只注重整体架构。具体操作见代码。

用python写秒杀程序_马上双十一,教你用Python实现秒杀系统相关推荐

  1. python写机器人程序_用Python写的一个多线程机器人聊天程序

    本人是从事php开发的, 近来想通过php实现即时通讯(兼容windows).后来发现实现起来特别麻烦, 就想到python.听说这家伙在什么地方都能发挥作用.所以想用python来做通讯模块...所 ...

  2. python写电脑程序_【初学者教程】在电脑上安装Python,写第一个程序

    欢迎来到Python的世界 1.存在Python 2和Python 3两个版本,我该用哪个? 强烈建议用Python 3.2020年1月1日起官方就不再维护Python 2了,2已经过时. 2.下载P ...

  3. python写采集程序_用python写的一个wordpress的采集程序

    在学习python的过程中,经过不断的尝试及努力,终于完成了第一个像样的python程序,虽然还有很多需要优化的地方,但是目前基本上实现了我所要求的功能,先贴一下程序代码: 具体代码如下: #! /u ...

  4. python写windows程序_【Python学习】Python 写Windows Service服务程序

    如下遇到自己编写的服务无法启动 需要添加环境变量(标红的) C:\Python27\Scripts;C:\Python27\;C:\Python27\chromedriver.exe;C:\Pytho ...

  5. python判断素数程序_使用面向对象方法检查素数的Python程序

    python判断素数程序 This program will check whether a given number is Prime or Not, in this program we will ...

  6. 吃鸡是python写的吗_吃鸡游戏也是用Python写的?学了Python,120个月年终奖向你招手~...

    原标题:吃鸡游戏也是用Python写的?学了Python,120个月年终奖向你招手~ 吃鸡游戏火爆全球,已经把腾讯的<王者荣耀>都比下去了,有传言,腾讯给"王者荣耀"开 ...

  7. 吃鸡是python写的吗_吃鸡手游竟然是Python写的?

    软妹子也要学Python 听起来这么高大上的Python是不是很难学?并没有. 北邮在2010年就出版过一本<和孩子一起学Python>,Python真的是容易入门到可以作为亲子娱乐项目的 ...

  8. 用python写投票程序_大话python最终篇,web.py 开发的投票程序demo

    概述 开发语言         python Web开发框架  web.py 前端开发框架   vuejs+elementui 数据库              mysql 设计思路 首先是数据库设计 ...

  9. python写新年快乐程序_新年快乐! python实现绚烂的烟花绽放效果

    新年快乐! python实现绚烂的烟花绽放效果 来源:中文源码网    浏览: 次    日期:2019年11月5日 [下载文档:  新年快乐! python实现绚烂的烟花绽放效果.txt ] (友情 ...

最新文章

  1. 一个班37人考进清华北大,老师发来一则短信,家长都沉默了
  2. WebRTC各种资料集合
  3. 深入浅出python机器学习_6.3.1_随机森林实例——要不要和相亲对象进一步发展
  4. ITK:可变长度向量
  5. 微信小程序家庭记账本开发进度二
  6. 用c语言get统计字母个数,请问这个用c怎么做:输入一串字符,分别统计其中数字和字母的个数...
  7. 滚动一定的高度底色递增
  8. 事件 ID 3001错误的解决方法
  9. python画椭圆形_手残党福音:用Python画出机器人Dev
  10. DNN的Portal在站点(服务器)之间的迁移
  11. 雷林鹏分享:PHP 表单验证
  12. 2432功率计使用说明_Quarq功率计的安装、使用和维护
  13. MVC页面重定向'页面跳转
  14. 一定能成功的Android NDK环境配置教程
  15. 通用PHM集成开发环境PIDE
  16. CSS进阶(15)—— CSS世界的层叠规则(上)
  17. PRIMARY KEY与identity(1,1)的比较
  18. 我爱淘冲刺阶段站立会议2每天任务4
  19. Lumerical官方案例、FDTD时域有限差分法仿真学习(八)——光纤布拉格光栅(Fiber Bragg gratings)
  20. 今日头条,企鹅号,大鱼号,百家号,做搬运视频还赚钱吗?

热门文章

  1. ArcEngine中的地图缩放相关的常用操作以及固定比例放大缩小
  2. java对文件的处理(读取,写入,复制,加密和解密)
  3. 推进浙江创新发展,区块链赋能智慧农业解决方案
  4. GOOGLE | COT(chain of thought)开山之作,利用思维链提升复杂问题推理能力
  5. 极致真实的CE游戏引擎
  6. ubuntu下载好了输入法怎么切换
  7. linux access() rpm,Unix/LINUX rpm 命令示例
  8. 《计算机网络》day04-计算机网络的功能特性
  9. 一体化Mbus物联网主机上线问题总结
  10. Java温度转换器(华氏温度,摄氏温度,k氏温度)