如何使用Node.js分配70个请求/秒的唯一优惠券代码

当我们启动我们的交易(我们每天多次启动20多笔交易)时,我运行一个优惠券网站,看到50-70个请求/秒。 当交易生效时,我们的用户按下一个button来为特定产品索要优惠券,该优惠券通过ajax https请求提供唯一的优惠券代码。 每张优惠券只能兑换一次。

我的问题是,在这样的时间stream量这么高,相同的优惠券可以分配给多个用户。 这是不好的,因为只有其中一个实际上可以兑换优惠券,而另一个用户体验不佳。

我将所有优惠券信息存储在由IBM Bluemix托pipe的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服务器以及它们的function没有太多的了解。

一如既往,感谢您的帮助!

问题在于Node的非阻塞/asynchronous性质。 从同时请求调用相同的函数不会等待对方完成。 很多请求进来并且访问全局代码数组。

您多次发出相同的代码,因为计数器会同时增加多个请求,所以可能发生多个请求看到相同的计数器状态。

一种pipe理并发问题的方法是一次只允许一次访问(在你的情况下为getUserACoupon ),这样消费优惠券的执行部分就是同步的或者相互排斥的 。 一种实现这一点的方法是locking机制,当一个请求获得对锁的访问时,进一步的请求等待,直到锁被释放。 在伪代码中,它可能看起来像这样:

 wait until lock exists create lock if any left, consume one coupon remove lock 

但是这种方法违背了Node的非阻塞性质,并且还引入了当多个请求等待时释放谁的问题。

更好的方法更可能是一个队列系统。 它应该能够工作,这样代码在请求时就不会被消耗,而是作为一个可调用的对象放入队列中,等待启动。 你可以阅读队列的长度,并停止接受新的请求(“售罄”),但是,这将仍然是并发的全局队列/计数器,所以你可能会得到比优惠券更多的排队项目,但这是不是问题,因为队列将被同步处理,所以可以准确地确定何时分配优惠券的数量,只要给予“卖完”(如果有的话),更重要的是,确保每个代码只服务一次。

使用时态 ,可以很容易地创build一个线性的,延迟的任务列表:

 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); } } }]); }); 

这里的一个关键点是时间执行顺序地加上延迟的任务,所以如果延迟多于任务运行所需的时间,则不超过一个任务将一次访问计数器/代码arrays。

你也可以使用定时队列处理来实现你自己的基于这个逻辑的解决scheme,但时间似乎值得一试。

你有没有考虑排队的HTTP请求,因为他们进来,以维持这个问题的顺序。 如何排队在Nodejs HTTP获取请求,以控制他们的速度?