问题
我在Redis中有一个名为stocks
的键,它的值是1000。假设300个客户同时请求购买(每个客户购买100只股票)。最后,只有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
函数有点困惑。据我所知,它只是创建一个新的连接(你可以在这里看到),当你想在独占连接上运行命令时,它很有用,比如watch
命令。
谁能解释一下executeIsolated
在我的例子中的确切作用?
1条答案
按热度按时间pgccezyw1#
问题是,当有并发请求时,不能保证第n个请求的
SET
将在第n +1个请求的GET
之前运行。例如,如果有2个并发请求,命令应该按以下顺序执行:字符串
但它们可能会按以下顺序执行:
型
为了修复它,你应该使用一个Redis function(从redis 7.0开始可用)或Lua Script,看起来像这样:
型
关于为什么使用
executeIsolated
“修复”这个问题-可能有两个原因:1.池大小为1,这实际上创建了一个队列
1.池中没有“空闲”连接,创建新连接所需的时间比执行
GET
所需的时间长。