中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何使用nodejs設計一個秒殺系統的方法

發布時間:2021-04-21 11:37:43 來源:億速云 閱讀:275 作者:小新 欄目:web開發

小編給大家分享一下如何使用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設計一個秒殺系統的方法”有了一定的了解,如果想了解更多相關知識,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

鄂伦春自治旗| 阿勒泰市| 灵宝市| 湟中县| 东莞市| 佛冈县| 淅川县| 神木县| 成武县| 响水县| 普安县| 施甸县| 江源县| 修文县| 泸溪县| 高清| 万源市| 托里县| 新龙县| 阜平县| 新干县| 富源县| 拉萨市| 隆德县| 凉山| 香河县| 延长县| 喀喇沁旗| 肇源县| 曲周县| 祁门县| 蓬莱市| 阳曲县| 伊宁县| 张家口市| 拜泉县| 屏东县| 阳山县| 华安县| 塔城市| 界首市|