Case kho còn 1 mặt hàng nhưng có nhiều user mua hàng cùng một lúc

 

-Lần này là chủ đề về kho còn 1 mặt hàng nhưng có nhiều user  mua hàng cùng một lúc .

Thực tế trường hợp này thường xuyên xảy ra trong các trang tmđt . Ví dụ ở shopee , lazada , tiki ...
vào thời điểm có khuyến mãi thì lượng user truy cập rất đông dẫn đến số lượng request order mặt hàng lớn . giả sử sản phẩm iphone13 mới ra mắt và được bán . lúc này rất nhiều người dùng muốn đặt hàng dẫn đến mặt hàng này hết nhanh .
và rất có thể xảy ra trường hợp trong kho còn đúng 1 chiếc nhưng lại có nhiều người đặt mua vào cùng 1 thời điểm .  Vậy thì sản phẩm này sẽ vào tay ai 

Còn 1 Trường hợp nữa trong thực tế cũng hay xảy ra đó là chuyện đăng kí tín chỉ ở đại học 
anh em đến mùa đăng kí đều tranh nhau slot lớp học ngon hơn để học 😏 
Tức là vào 1 thời điểm lượng request đồng thời lên server rất lớn 
vậy rất có thể 1 lớp học nào đó chỉ còn đúng 1 slot cho 1 bạn sinh viên đăng kí học nhưng lại có tới 10 bạn cùng bấm vào và đăng ký vậy thì ai sẽ là người chiến thắng trong các cuộc tranh đoạt này ...

Sau đây là các thứ cần để giải quyết vấn đề này :
- npm install redis 3.1.2 (các bản cấp cao hơn có thể dính lỗi client closed)



- redis server (link github : https://github.com/microsoftarchive/redis/releases)

 chỉ cần tải về và cài đặt - ở đây tôi sử dụng hệ điều hành window còn ai sử dụng hđh linux , ubuntu , macos gì thì đều có file để cài đặt riêng !

sau khi cài đặt chỉ cần kiểm tra xem redis-server đã được connect hay chưa ở redis-cli như này


chỉ cần ping -> pong là đã oke rồi !


- redis-commander



Tạo 1 cấu trúc folder đơn giản nhất để làm rõ vấn đề này :



Trong đó file model.redis.js là file của các tác vụ và lệnh trong redis :

"use strict";

const redis = require("redis");
const client = redis.createClient();

const get = async (key) => {
  return new Promise((resolve, reject) => {
    client.get(key, (err, result) => {
      if (err) {
        return reject(err);
      }
      resolve(result);
    });
  });
};

const set = async (key, count) => {
  return new Promise((resolve, reject) => {
    client.set(key, count, (err, result) => {
      if (err) {
        return reject(err);
      }
      resolve(result);
    });
  });
};

const incrby = async (key, count) => {
  return new Promise((resolve, reject) => {
    client.incrby(key, count, (err, result) => {
      if (err) {
        return reject(err);
      }
      resolve(result);
    });
  });
};

const decrby = async (key, count) => {
  return new Promise((resolve, reject) => {
    client.decrby(key, count, (err, result) => {
      if (err) {
        return reject(err);
      }
      resolve(result);
    });
  });
};

const exists = async (key) => {
  return new Promise((resolve, reject) => {
    client.exists(key, (err, result) => {
      if (err) {
        return reject(err);
      }
      resolve(result);
    });
  });
};

const setnx = async (key, count) => {
  return new Promise((resolve, reject) => {
    client.setnx(key, count, (err, result) => {
      if (err) {
        return reject(err);
      }
      resolve(result);
    });
  });
};

module.exports = {
  get,
  set,
  incrby,
  exists,
  setnx,
  decrby,
};


tạo file redis.v1.js:  Đây là file logic xử lí vấn đề và tôi đã giải thích logic trong code :

const express = require("express");
const app = express();
const { get, set, incrby, exists, setnx, decrby } = require("./model.redis");

app.get("/order", async (req, res) => {
  const time = new Date().getTime();
  console.log("time request:::::::::::::", time);
  const slTonkho = 10;
  const keyName = "iphone13";
  //Giả sử mỗi lần mua hàng thì số lượng bán ra chỉ là 1 sản phẩm
  const slMua = 1;
  // số lượng sp đã bán nếu chưa bán đc gì thì set = 0 ,còn nếu đã bán ít nhất 1sp thì update +1 mỗi lần user order thành công
  const getKey = await exists(keyName);
  if (!getKey) {
    //set = 0
    await set(keyName, 0);
  }
  // lấy số lượng đã bán ra
  let slBanra = await get(keyName);
  console.log(`trước khi user order thành công thì số lượng đã bán là`, slBanra);
  // nếu số lượng đã bán ra + sl mua  > số lượng tồn kho thì  return false
  if (+slBanra + slMua > slTonkho) {
    console.log("Hết Hàng");
    return;
  }
  //nếu user order thành công
  slBanra = await incrby(keyName, slMua); //atom redis
  console.log(`sau khi user order thành công thì số lượng đã bán là`, slBanra);
  if (slBanra > slTonkho) {
    await set("oversell", slBanra - slTonkho);
  }
  return res.json({
    status: "success",
    msg: "ok",
    time,
  });
});

app.listen(3000, () => {
  console.log(`server running in port 3000`);
});


Oke ! thế là đã có 2 file giờ cần có thêm tool để xử lí việc có nhiều request gọi đến server 
tool này là : apache benchmark
(tìm hiểu thêm ở trang chủ : https://httpd.apache.org/docs/2.4/programs/ab.html)

để tải cũng như cài đặt môi trường cho tool này có thểm xem tại : (https://www.youtube.com/watch?v=hUZso9TpEes&t=45s&ab_channel=CodingLikeDavid)

sau khi tải xong vào cmd gõ: ab --help sẽ có danh sách các lệnh mà tool này hỗ trợ :




Đầu tiên chạy chương trình với lệnh : node redis.v1.js dể run chương trình .

sau đó kiểm tra xem đã có redis nào tồn tại trước đó chưa cứ xóa đi cho chắc chắn sạch sẽ 
đỡ lo gặp lỗi:


sau đó là chạy lệnh : ab -n 1 -c 1 http://localhost:3000/order

đây là lệnh gọi 1 requets lên server vào 1 thời điểm nhất định - tức là có 1 user đặt hàng :
và kết quả ở cửa sổ lệnh là :


ở cửa sổ lệnh còn lại cũng là cửa sổ chúng ta run chương trình :


Chạy lệnh trên liên tục 10 lần trở lên sẽ cho kết quả :


tuy nhiên đây mới chỉ chạy lần lượt từng request - còn bây giờ thử chạy đồng thời nhiều request 1 lúc sẽ như sau :

Câu lệnh tôi sử dụng để test trong ví dụ này  là :
ab -n 40 -c 20 http://localhost:3000/order

  • -n 40: Tùy chọn -n xác định tổng số yêu cầu (requests) sẽ được gửi đi. Trong trường hợp này, giá trị là 40, điều này có nghĩa là sẽ gửi đi tổng cộng 40 yêu cầu.
  • -c 20: Tùy chọn -c xác định số lượng yêu cầu đồng thời sẽ được gửi đi. Trong trường hợp này, giá trị là 20, điều này có nghĩa là sẽ gửi 20 yêu cầu cùng một lúc và sau đó gửi tiếp 20 yêu cầu khác cho đến khi đạt được tổng số yêu cầu 40.

 => tức là sẽ có 40 request đc gửi đến router đặt hàng . và mỗi 1 lần sẽ có 20 request đồng thời được gửi đi .

kết quả :


Nhìn là biết có vấn đề rồi .

vào thử redis-cli 


thấy số lượng bán quá mất 7 thì có nghĩa sai mẹ nó rồi .

Thế nên tạo ra file : redis.v2.js như sau :

const express = require("express");
const app = express();
const { get, set, incrby, exists, setnx, decrby } = require("./model.redis");

app.get("/order", async (req, res) => {
  const time = new Date().getTime();
  console.log("time request:::::::::::::", time);

  const slTonkho = 10;
  const keyName = "iphone13";

  //Giả sử mỗi lần mua hàng thì số lượng bán ra chỉ là 1 sản phẩm

  const slMua = 1;

  // số lượng sp đã bán nếu chưa bán đc gì thì set = 0 ,
  //còn nếu đã bán ít nhất 1sp thì update + 1 mỗi lần user order thành công

  const getKey = await exists(keyName);
  if (!getKey) {
    //set = 0
    //setnx tức là nếu có 2 hay nhiều người truy cập mua hàng cùng 1 lúc
    // thì khi sử dụng setnx sẽ không được đặt lại
    await setnx(keyName, 0);
  }
  // lấy số lượng đã bán ra

  let slBanra = await get(keyName);
  console.log(
    `trước khi user order thành công thì số lượng đã bán là`,
    slBanra
  );
  // nếu số lượng đã bán ra + sl mua  > số lượng tồn kho thì  return false
  slBanra = await incrby(keyName, slMua);
  if (slBanra > slTonkho) {
    console.log("hethang");
    return res.json({
      status: "error",
      msg: "het hang",
      time,
    });
  }
  //nếu user order thành công
  console.log(`sau khi user order thành công thì số lượng đã bán là`, slBanra);
  if (slBanra > slTonkho) {
    await set("oversell", slBanra - slTonkho);
  }
  return res.json({
    status: "success",
    msg: "ok",
    time,
  });
});

app.listen(3000, () => {
  console.log(`server running in port 3000`);
});

Chạy thử như lần 1:




Ảo ma vcl - Mình đến đoạn này mình đéo hiểu bán ra 40 sp là quá cmn 30sp rồi
vậy mà chạy ra oversell = null làm gì nhỉ ??? 😵😵😵😵😵😵😵









































Nhận xét

related

Tìm hiểu về Rabbit-mq

Lợi ích của Promise so với async-await

Socket.io - MVC

Mạng máy tính

Redis - Basic