我运行一个优惠券网站,当我们启动我们的交易时,它会看到每秒 50-70 个请求(我们每天多次启动 20 多个交易)。当交易生效时,我们的用户按下按钮来领取特定产品的优惠券,该优惠券通过 ajax https 请求提供唯一的优惠券代码。每张优惠券只能兑换一次。
我的问题是,在这些时候流量如此之大,同一张优惠券可以分发给多个用户。这很糟糕,因为只有其中一个人能够真正兑换优惠券,而另一个人的用户体验很差。
我将所有优惠券信息存储在 IBM Bluemix 托管的 node.js 服务器内存上的对象中。我想这可以让我快速处理请求。
我如何存储优惠券信息:
global.coupons = {};
//the number of coupons given for each product
global.given = {};
/* Setting the coupon information */
//....I query my database for the products to be given today
for(var i = 0; i < results.length; i++){
var product = results[i];
//add only the coupons to give today to the array
var originalCoups = product.get('coupons');
var numToTake = product.get('toGivePerDay');
if(product.get('givenToday') > 0){
numToTake = numToTake - product.get('givenToday');
}
// Example coupon array [["VVXM-Q577J2-XRGHCC","VVLE-JJR364-5G5Q6B"]]
var couponArray = originalCoups[0].splice(product.get('given'), numToTake);
//set promo info
global.coupons[product.id] = couponArray;
global.given[product.id] = 0;
}
处理优惠券请求:
app.post('/getCoupon', urlencodedParser, function(req, res){
if (!req.body) return res.status(400).send("Bad Request");
if (!req.body.category) return res.status(200).send("Please Refresh the Page.");
//Go grab a coupon
var coupon = getUserACoupon(req.body.objectId);
res.type('text/plain');
res.status(200).send(coupon);
if(coupon != "Sold Out!" && coupon != "Bad Request: Object does not exist."){
//Update user & product analytics
setStatsAfterCouponsSent(req.body.objectId, req.body.sellerProduct, req.body.userEmail, req.body.purchaseProfileId, coupon, req.body.category);
}
});
//getCoupon logic
function getUserACoupon(objectId){
var coupToReturn;
// coupon array for the requseted product
var coupsArray = global.coupons[objectId];
if(typeof coupsArray != 'undefined'){
// grab the number of coupons already given for this product and increase by one
var num = global.given[objectId];
global.given[objectId] = num+1;
if(num < coupsArray.length){
if(coupsArray[num] != '' && typeof coupsArray[num] != 'undefined' && coupsArray[num] != 'undefined'){
coupToReturn = coupsArray[num];
}else{
console.log("Error with the coupon for "+objectId + " the num is " + num);
coupToReturn = "Sold Out!";
wasSoldOut(objectId);
}
}else{
console.log("Sold out "+objectId+" with num " + num);
coupToReturn = "Sold Out!";
wasSoldOut(objectId);
}
}else{
coupToReturn = "Bad Request: Object does not exist.";
wasSoldOut(objectId);
}
return coupToReturn;
}
我对 node.js 服务器以及它们的运行方式没有太多了解。
一如既往,感谢您的帮助!
最佳答案
问题在于 Node.js 的非阻塞/异步特性。同时请求对同一函数的调用不会相互等待完成。很多请求进来并同时访问全局代码数组。
您多次给出相同的代码,因为 counter is incremented by multiple requests concurrently因此可能会发生多个请求看到相同的计数器状态。
管理并发问题的一种方法是一次只允许一次访问(在您的情况下是 getUserACoupon
),以便使用优惠券的执行部分同步 或互斥。实现此目的的一种方法是锁定机制,因此当一个请求获得对锁的访问权时,进一步的请求会等到锁被释放。在伪代码中,它可能看起来像这样:
wait until lock exists
create lock
if any left, consume one coupon
remove lock
但是这种方法违背了 Node 的非阻塞特性,并且还引入了如果多个请求在等待时谁获得锁的问题。
一种更好的方法更有可能是队列系统。它应该可以工作,以便在请求时不会消耗代码,而是将其作为可调用对象放入队列中,等待启动。您可以读取队列的长度并停止接受新请求(“售罄”),但是,这仍然会在全局队列/计数器上并发,因此您最终可能会得到比优惠券更多的排队元素,但这是不是问题,因为队列将同步处理,因此可以准确确定何时达到分配的优惠券数量,如果有的话,只需将“售罄”给其他人,更重要的是,确保每个代码只提供一次。
使用 temporal ,创建一个线性的、延迟的任务列表可能很容易:
var temporal = require("temporal");
global.queues = {};
app.post('/getCoupon', urlencodedParser, function(req, res){
if (!req.body) return res.status(400).send("Bad Request");
if (!req.body.category) return res.status(200).send("Please Refresh the Page.");
// Create global queue at first request or return to it.
var queue;
if( !global.queues[req.body.objectId] ) {
queue = global.queues[req.body.objectId] = temporal.queue([]);
}
else {
queue = global.queues[req.body.objectId];
}
// Prevent queuing after limit
// This will be still concurrent access so in case of large
// number of requests a few more may end up queued
if( global.given[objectId] >= global.coupons[objectId].length ) {
res.type('text/plain');
res.status(200).send("Sold out!");
return;
}
queue.add([{
delay: 200,
task: function() {
//Go grab a coupon
var coupon = getUserACoupon(req.body.objectId);
res.type('text/plain');
res.status(200).send(coupon);
if(coupon != "Sold Out!" && coupon != "Bad Request: Object does not exist."){
//Update user & product analytics
setStatsAfterCouponsSent(req.body.objectId, req.body.sellerProduct, req.body.userEmail, req.body.purchaseProfileId, coupon, req.body.category);
}
}
}]);
});
这里的一个关键点是时间顺序执行任务会增加延迟,因此如果延迟超过任务运行所需的时间,则不会有超过一个任务一次访问计数器/代码数组。
您也可以使用定时队列处理基于此逻辑实现自己的解决方案,但时间似乎值得一试。
关于javascript - 如何使用 Node.js 以 70 个请求/秒的速度分发唯一优惠券代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29707818/