问题

我在Redis中有一个名为stocks的键,它的值是stocks0.假设300个客户同时请求购买(每个客户购买stocks只股票).最后,只有10个客户可以购买.

解决方案

我知道它不会起作用,但假设我的buy函数是这样的:

/**
 * @param {import("redis").RedisClientType} instance
 * @param {string} clientId
 * @param {number} n
 */
async function buy(instance, clientId, n) {
  // --- (1) ----
  // Get current number of stocks
  let stocks = await instance.GET("stocks");
  stocks = parseInt(stocks);

  // --- (2) ----
  // Validate if the stocks remaining are enough to be bought
  if (stocks < n) {
    console.log("error: company does not have enough stocks");
    return new Error("error: company does not have enough stocks");
  }

  // --- (3) ----
  // Update the current stocks of the company and log who has bought stocks
  await instance.INCRBY("stocks", -n);
  console.log("client @%s has bought %s stocks successfully!", clientId, n);
}

为了测试它,我编写了一个调用buy函数300次的函数:

const redis = require("redis");
const crypto = require("crypto");
const { buy } = require("./buy");

async function main(customers = 300) {
  const instance = await redis
    .createClient({ url: "redis://localhost:6379" })
    .connect();

  // --- (1) ----
  // set stocks
  await instance.SET("stocks", 1000);

  // --- (2) ----
  // buy 100 stocks concurrentlly for each customer
  let pool = [];
  for (let i = 0; i < customers; i++) {
    let userId = crypto.randomBytes(4).toString("hex");
    pool.push(buy_v3(instance, userId, 100));
  }
  await Promise.all(pool);

  // --- (3) ----
  // Get the remaining stocks
  let shares = await instance.GET("stocks");
  console.log("the number of free shares the company has is: %s", shares);

  await instance.disconnect();
}
main();

输出:

...
error: company does not have enough stocks
error: company does not have enough stocks
error: company does not have enough stocks
error: company does not have enough stocks
the number of free stocks the company has is: -29000

正如我所说的,它不起作用,但为了解决这个问题,我使用了以下方法:

/**
 * @param {import("redis").RedisClientType} instance
 * @param {string} clientId
 * @param {number} n
 */
async function buy(instance, clientId, n) {
  try {
    await instance.executeIsolated(async (client) => {
      // --- (1) ----
      // Get current number of stocks
      let stocks = await client.GET("stocks");
      stocks = parseInt(stocks);

      // --- (2) ----
      // Validate if the stocks remaining are enough to be bought
      if (stocks < n) {
        throw new Error("error: company does not have enough stocks");
      }

      // --- (3) ----
      // Update the current stocks of the company
      await client.INCRBY("stocks", -n);
    });

    console.log("client @%s has bought %s stocks successfully!", clientId, n);
  } catch (err) {
    console.log(err.message);
  }
}

如果你再次测试它,你会看到类似这样的东西:

...
error: company does not have enough stocks
error: company does not have enough stocks
error: company does not have enough stocks
error: company does not have enough stocks
the number of free stocks the company has is: 0

这意味着它工作起来没有任何问题.

问题

上面的解决方案运行得很好,但我对executeIsolated函数有点困惑.据我所知,它只是创建一个新的连接(您可以看到here),当您想要在独占连接(如watch命令)上运行命令时,它很有用.

谁能解释一下,在我的 case 中,executeIsolated的确切作用是什么?

推荐答案

问题是,当有并发请求时,不能保证第n个请求的第SET个将在第n+1个请求的第GET个请求之前运行.例如,如果有2个并发请求,则应按以下顺序执行命令:

> GET stocks
"100"
> INCRBY stocks -100
(integer) 0
> GET stocks
"0"

但他们可能会按如下顺序执行:

> GET stocks
"100"
> GET stocks
"100"
> INCRBY stocks -100
(integer) 0
> INCRBY stocks -100
(integer) -100

为了解决这个问题,你应该使用Redis function(从Redis 7.0开始提供)或Lua Script,看起来像这样:

local stocks = redis.call('GET', KEYS[1])
if stocks < ARGS[1] then
  return redis.error_reply('company does not have enough stocks')
end

redis.call('SET', KEYS[1], stocks - ARGS[1])
return redis.status_reply('OK')

关于为什么使用executeIsolated来"修复"这个问题--可能有两个原因:

  1. 池大小为1,这实际上创建了一个队列
  2. 池中没有"空闲"连接,创建新连接所需的时间比执行GET.

希望这是有道理的..

Node.js相关问答推荐

MongoDB:更新批量操作中许多不能正常工作的内容

带有apache Couch-db和Nano的推荐引擎:过滤特定用户的视图

对于具有重叠列的组合键,在冲突&q;上没有唯一或排除约束匹配错误

控制台显示一个长对象,我可以';每当我发布更新Mongoose数据库的请求时,我都不知道错误是什么,

验证器功能在mongoose 中不起作用

在 Docker 容器内创建一个 cron 作业(job)来执行 run.js 文件中的函数

MongoDB - mongoose :如何查询这个? 填充()不起作用.它显示空

错误 node :错误:绑定消息提供 16 个参数,但准备语句需要 15 个

为什么当我使用waitForSelector时 Puppeteer 导致我的测试套件挂起 30 秒,即使我在页面和浏览器上调用关闭?

只要我在后端正确验证所有内容,就可以将这些数据存储在本地存储中吗?

在 Node JS 中使用 url 链接而不是文件路径

为什么 $or 在带有正则表达式的mongoose 中不能正常工作

NestJS TypeORM 可选查询不起作用

如何正确使用 package.json 中的关键字属性?

Node JS:自动 Select `http.get`与`https.get`

如何将使用 Gulp 的 node 部署到 heroku

适用于 Windows 7 的 NodeJS

AngularJS +sails.js

Node.js 17.0.1 Gatsby 错误-数字信封 routine ::不支持 ... ERR_OSSL_EVP_UNSUPPORTED

如何阻止 babel 将this转换为undefined(并插入use strict)