我的编程空间,编程开发者的网络收藏夹
学习永远不晚

如何使用nodejs设计一个秒杀系统的方法

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

如何使用nodejs设计一个秒杀系统的方法

小编给大家分享一下如何使用nodejs设计一个秒杀系统的方法,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!

js的作用是什么

1、能够嵌入动态文本于HTML页面。2、对浏览器事件做出响应。3、读写HTML元素。4、在数据被提交到服务器之前验证数据。5、检测访客的浏览器信息。6、控制cookies,包括创建和修改等。7、基于Node.js技术进行服务器端编程。

对于前端来说,“并发”场景很少遇到,本文将从常见的的秒杀场景,来讲讲一个真实线上的node应用遇到“并发”将会用到什么技术。本文示例代码数据库基于MongoDB,缓存基于Redis

场景一:领券


规则:一个用户只能领取一张券。

首先我们的思路是,用一个records表来保存用户的领券记录,用户领券时在该表查询是否已领取。

records结构如下

new Schema({  // 用户id  userId: {    type: String,    required: true,  },});

业务流程也很简单:

如何使用nodejs设计一个秒杀系统的方法

MongoDB实现

示例代码如下:

  async grantCoupon(userId: string) {    const record = await this.recordsModel.findOne({      userId,    });    if (record) {      return false;    } else {      this.grantCoupon();      this.recordModel.create({        userId,      });    }  }

postman测试一下,好像没问题。然后我们考虑并发场景,比如“用户”并不会乖乖的点一下按钮等待发券,而是快速点击,又或者使用工具并发请求领券接口,我们的程序会出问题么?(并发问题前端可以用loading来规避,但是接口必要拦截住,防止黑客攻击)

结果是,用户可能会领取到多张券。问题就出在查询records新增领券记录,这两步是分开进行的,也就是存在一个时间点:查询到用户A无领券记录,发券后A用户又请求一次接口,此时records表数据插入操作还未完成,导致重复发放问题。

解决也很容易,就是如何让查询和插入语句一起执行,消除中间的异步过程。mongoose为我们提供了findOneAndUpdate,即查找并修改,下面看一下改写后的语句:

async grantCoupon(userId: string) {  const record = await this.recordModel.findOneAndUpdate({    userId,  }, {    $setOnInsert: {      userId,    },  }, {    new: false,    upsert: true,  });  if (! record) {    this.grantCoupon();  }}

实际上这是一个mongo的原子操作,第一个参数是查询语句,查询userId的条目,第二个参数$setOnInsert表示新增的时候插入的字段,第三个参数upsert=true表示如果查询的条目不存在,将新建它,new=false表示返回查询的条目而不是修改后的条目。那我们只用判断查询的record不存在,就执行发放逻辑,而插入语句是和查询语句一起执行的。即使此时有并发请求进来,下一次查询是在上次插入语句之后了。

原子(atomic),本意是指“不能被进一步分割的粒子”。原子操作意味着“不可被中断的一个或一系列操作”,两个原子操作不可能同时作用于同一个变量。

Redis实现

不止MongoDB,redis也很适合这种逻辑,下面用redis实现一下:

async grantCoupon(userId: string) {  const result = await this.redis.setnx(userId, 'true');  if (result === 1) {    this.grantCoupon();  }}

同样setnx是redis的一个原子操作,表示:如果key没有值,则将值设置进去,如果已有值就不做处理,提示失败。这里只是演示并发处理,实际线上服务还需要考虑:

  • key值不能与其他应用冲突使用,如应用名称+功能名称+userId

  • 服务下线后redis的key需要清理,或者直接在setnx第三个参数加上过期时间

  • redis数据只在内存中,发券记录需要入库保存

场景二:库存限制


规则:券总库存一定,单个用户不限领取数量

有了上面的示例,类似并发也很好实现,直接上代码

MongoDB实现

使用stocks表来记录券的发放数量,当然我们需要一个couponId字段去标识这条记录

表结构:

new Schema({    couponId: {    type: String,    required: true,  },    count: {    type: Number,    default: 0,  },});

发放逻辑:

async grantCoupon(userId: string) {  const couponId = 'coupon-1'; // 券标识  const total = 100; // 总库存  const result = await this.stockModel.findOneAndUpdate({    couponId,  }, {    $inc: {      count: 1,    },    $setOnInsert: {      couponId,    },  }, {    new: true, // 返回modify后结果    upsert: true, // 不存在则新增  });  if (result.count <= total) {    this.grantCoupon();  }}

Redis实现

incr: 原子操作,将key的值+1,如果值不存在,将初始化为0;

async grantCoupon(userId: string) {  const total = 100; // 总库存  const result = await this.redis.incr('coupon-1');  if (result <= total) {    this.grantCoupon();  }}

思考一个问题,库存全部消耗完后,count字段还会增加么?应该如何优化?

场景三:用户领券限制+库存限制


规则:一个用户只能领一张券,总库存有限制

解析

单独去解决“一个用户只能领一张”或“总库存限制”,我们都可以用原子操作去处理,当有两个条件,那是否可以实现一个,类似原子操作将“一个用户只能领一张”和“总库存限制”合并操作,或者说是更类似于数据库的“事务”

数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成

mongoDB已经从4.0开始支持事务,但这里作为演示,我们还是使用代码逻辑来控制并发

业务逻辑:

如何使用nodejs设计一个秒杀系统的方法

代码:

async grantCoupon(userId: string) {  const couponId = 'coupon-1';// 券标识  const totalStock = 100;// 总库存  // 查询用户是否已领过券  const recordByFind = await this.recordModel.findOne({    couponId,    userId,  });  if (recordByFind) {    return '每位用户只能领一张';  }  // 查询已发放数量  const grantedCount = await this.stockModel.findOne({    couponId,  });  if (grantedCount >= totalStock) {    return '超过库存限制';  }  // 原子操作:已发放数量+1,并返回+1后的结果  const result = await this.stockModel.findOneAndUpdate({    couponId,  }, {    $inc: {      count: 1,    },    $setOnInsert: {      couponId,    },  }, {    new: true, // 返回modify后结果    upsert: true, // 如果不存在就新增  });  // 根据+1后的的结果判断是否超出库存  if (result.count > totalStock) {    // 超出后执行-1操作,保证数据库中记录的已发放数量准确。    this.stockModel.findOneAndUpdate({      couponId,    }, {      $inc: {        count: -1,      },    });    return '超过库存限制';  }  // 原子操作:records表新增用户领券记录,并返回新增前的查询结果  const recordBeforeModify = await this.recordModel.findOneAndUpdate({    couponId,    userId,  }, {    $setOnInsert: {      userId,    },  }, {    new: false, // 返回modify后结果    upsert: true, // 如果不存在就新增  });  if (recordBeforeModify) {    // 超出后执行-1操作,保证数据库中记录的已发放数量准确。    this.stockModel.findOneAndUpdate({      couponId,    }, {      $inc: {        count: -1,      },    });    return '每位用户只能领一张';  }  // 上述条件都满足,才执行发放操作  this.grantCoupon();}

其实我们可以舍去前两部查询records记录和查询库存数量,结果并不会出问题。从数据库优化来说,显然更改比查询更耗时,而且库存有限,最终库存消耗完,后面请求都会在前两步逻辑中走完。

  • 什么情况下会走到第3步的左分支?

场景举例:库存仅剩1个,此时用户A和用户B同时请求,此时A稍快一点,库存+1后=100,B库存+1=101;

  • 什么情况下会走到第4步的左分支?

场景举例:A用户同时发出两个请求,库存+1后均小于100,则稍快的一次请求会成功,另一个会查询到已有领券记录

  • 思考:什么情况下会出现,先请求的用户没抢到券,反而靠后的用户能抢到券?

库存还剩4个,A用户发起大量请求,最终导致数据库记录的已发放库存大于100,-1操作还全部执行完成,而此时B、C、D用户也同时请求,则会返回超出库存,待到库存回滚操作完成,E、F、G用户后续请求的反而显示还有库存,成功抢到券,当然这只是理论上可能存在的情况。

看完了这篇文章,相信你对“如何使用nodejs设计一个秒杀系统的方法”有了一定的了解,如果想了解更多相关知识,欢迎关注编程网行业资讯频道,感谢各位的阅读!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

如何使用nodejs设计一个秒杀系统的方法

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

如何使用nodejs设计一个秒杀系统的方法

小编给大家分享一下如何使用nodejs设计一个秒杀系统的方法,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!js的作用是什么1、能够嵌入动态文本于HTML页面。2、对浏览器事件做出响应。3、读写HTML元素。4、在数据被提交
2023-06-14

如何设计一个几十万在线用户弹幕系统需求方案

这篇文章主要介绍了为大家如何设计一个几十万在线用户弹幕系统的需求实现方案详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪<BR>
2023-05-19

如何设计一个支持在线答题中的实时互动的系统

随着互联网的发展,在线学习已经成为一种普遍的学习方式。在线答题平台的出现,让学习变得更加灵活和便捷。然而,目前大部分在线答题平台只是提供了简单的答题功能,并没有实现实时互动的功能。为了满足学生们对于更加丰富多样化的学习体验的需求,我们需要设
2023-10-21

如何使用MySQL构建一个灵活可扩展的会计系统表结构?

如何使用MySQL构建一个灵活可扩展的会计系统表结构引言会计系统是企业管理中不可或缺的组成部分。构建一个灵活可扩展的会计系统表结构是非常重要的,它能够适应企业发展的需求,并方便数据的管理和查询。本文将介绍如何使用MySQL构建一个灵活可扩展
如何使用MySQL构建一个灵活可扩展的会计系统表结构?
2023-10-31

如何使用MongoDB开发一个简单的区块链系统

如何使用MongoDB开发一个简单的区块链系统区块链技术近年来备受关注,因其去中心化、安全性高等特点,被广泛用于加密货币、合约管理等领域。本文将介绍如何使用MongoDB开发一个简单的区块链系统,并提供相应的代码示例。1.安装和配置Mong
2023-10-22

如何使用MongoDB开发一个简单的物联网系统

如何使用MongoDB开发一个简单的物联网系统摘要:物联网系统是当前技术领域的热门话题,它将物理设备与互联网连接起来,使得设备之间可以实现数据的交互与共享。本文将介绍如何使用MongoDB开发一个简单的物联网系统,并提供代码示例供读者参考。
2023-10-22

如何使用MySQL构建一个可追溯的会计系统表结构以满足审计要求?

如何使用MySQL构建一个可追溯的会计系统表结构以满足审计要求?在现代商业运作中,会计系统扮演着至关重要的角色。随着商业交易的增加,会计记录和审计要求也变得越来越复杂。在这样的情况下,建立一个可追溯的、灵活的会计系统表结构是至关重要的。本文
如何使用MySQL构建一个可追溯的会计系统表结构以满足审计要求?
2023-10-31

如何在MySQL中设计一个安全的会计系统表结构以保护敏感信息?

如何在MySQL中设计一个安全的会计系统表结构以保护敏感信息?随着信息安全的重要性日益凸显,设计一个安全的会计系统表结构以保护敏感信息变得至关重要。MySQL作为一种常用的关系型数据库管理系统,为我们提供了一些安全性控制手段,并且可以通过合
如何在MySQL中设计一个安全的会计系统表结构以保护敏感信息?
2023-10-31

如何设计一个高性能的MySQL表结构来实现推荐系统功能?

如何设计一个高性能的MySQL表结构来实现推荐系统功能?推荐系统是很多互联网平台的重要组成部分,它通过分析用户的行为和偏好,提供个性化的推荐内容。在推荐系统的实现中,数据库扮演着关键角色,因此设计一个高性能的MySQL表结构非常重要。本文将
如何设计一个高性能的MySQL表结构来实现推荐系统功能?
2023-10-31

如何使用MongoDB开发一个简单的智能家居系统

如何使用MongoDB开发一个简单的智能家居系统智能家居系统已经成为了现代家庭生活的一部分。借助智能家居系统,我们可以通过手机或者其他设备远程控制家里的各个设备,例如灯光、电器、门锁等等。本文将介绍如何使用MongoDB来开发一个简单的智能
2023-10-22

如何使用C++实现一个简单的文件管理系统?

如何使用C++实现一个简单的文件管理系统?概述:文件管理系统是计算机中非常重要的一个功能模块,它负责对计算机中的文件进行创建、修改、删除等操作。本文将介绍如何使用C++编程语言实现一个简单的文件管理系统,通过该系统,可以实现对文件的基本管理
如何使用C++实现一个简单的文件管理系统?
2023-11-02

如何使用C++编写一个简单的人事管理系统?

如何使用C++编写一个简单的人事管理系统?人事管理系统是一个用于管理和维护组织内人力资源相关信息的软件。它可以帮助组织进行员工管理、薪资核算、考勤统计、福利发放等工作。本文将介绍如何使用C++编写一个简单的人事管理系统,帮助读者理解人事管理
如何使用C++编写一个简单的人事管理系统?
2023-11-02

如何使用C++编写一个简单的物流管理系统?

如何使用C++编写一个简单的物流管理系统?简介:物流管理系统是现代物流业中非常重要的一环,它能够帮助企业高效地管理运输、仓储、配送等物流环节。本文将介绍如何使用C++编写一个简单的物流管理系统,帮助读者了解C++的基本语法和面向对象的编程思
如何使用C++编写一个简单的物流管理系统?
2023-11-04

如何使用C++编写一个简单的酒店预订系统?

酒店预订系统是一种重要的信息管理系统,它可以帮助酒店实现更高效的管理和更良好的服务。如果你想学习如何使用C++来编写一个简单的酒店预订系统,那么本文将为您提供一个基本的框架和详细的实现步骤。酒店预订系统的功能需求在开发酒店预订系统之前,我们
如何使用C++编写一个简单的酒店预订系统?
2023-11-03

如何使用C++编写一个简单的餐厅预订系统?

如何使用C++编写一个简单的餐厅预订系统?餐饮行业是一个快节奏的行业,餐厅经常需要面对繁忙的预订情况。为了有效管理预订,提高服务质量,很多餐厅都会使用电子预订系统。本文将介绍如何使用C++编写一个简单的餐厅预订系统。首先,我们需要定义餐厅预
如何使用C++编写一个简单的餐厅预订系统?
2023-11-02

如何使用C++编写一个简单的网上商城系统?

如何使用C++编写一个简单的网上商城系统?随着互联网的发展,电子商务已经成为人们购物的主要方式之一。为了满足用户的购物需求,开发一个简单实用的网上商城系统是非常有必要的。本文将介绍如何使用C++编写一个简单的网上商城系统。一、需求分析在开始
如何使用C++编写一个简单的网上商城系统?
2023-11-02

如何使用MySQL和Ruby实现一个简单的投票系统

如何使用MySQL和Ruby实现一个简单的投票系统投票系统是一种常见的在线应用程序,用于收集用户对某个问题或主题的意见。在本文中,将介绍如何使用MySQL数据库和Ruby编程语言来实现一个简单的投票系统。首先,我们需要准备环境。确保已经安装
2023-10-22

如何使用MySQL和Python实现一个简单的博客系统

MySQL和Python简单博客系统本教程提供了逐步说明,指导你使用MySQL数据库和Python脚本创建一个简单的博客系统。包含步骤:数据库设计:创建两个表(posts和users)存储博客数据。Python脚本:涵盖建立数据库连接,执行查询(添加、获取、更新和删除帖子),以及创建和验证用户。此系统提供了访问和操作博客数据的基本功能,适合初学者或希望构建简单博客应用程序的开发人员。
如何使用MySQL和Python实现一个简单的博客系统
2024-04-09

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录