Javascript 如何使用Node.js以每秒70个请求的速度分发唯一优惠券代码

Javascript 如何使用Node.js以每秒70个请求的速度分发唯一优惠券代码,javascript,ajax,node.js,object,Javascript,Ajax,Node.js,Object,我经营一个优惠券网站,当我们推出交易时,每秒可以看到50-70个请求(我们每天多次一次推出20多个交易)。当交易生效时,我们的用户按下按钮为特定产品申请优惠券,这将通过ajax https请求提供唯一的优惠券代码。每张优惠券只能兑换一次 我的问题是,在这样的高流量的时候,同一张优惠券可以分发给多个用户。这是不好的,因为只有其中一家能够实际兑换优惠券,而另一家的用户体验很差 我将所有优惠券信息存储在IBM Bluemix托管的node.js服务器上内存中的对象中。我想这可以让我快速处理请求 如何存

我经营一个优惠券网站,当我们推出交易时,每秒可以看到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;
}
global.toups={};
//每种产品的优惠券数量
global.given={};
/*设置优惠券信息*/
//..我查询我的数据库,查找今天要提供的产品
对于(var i=0;i0){
numToTake=numToTake-product.get('givenToday');
}
//示例优惠券阵列[[“VVXM-Q577J2-XRGCC”,“VVLE-JJR364-5G5Q6B”]]
var couponArray=originalCoups[0]。拼接(product.get('given'),numToTake);
//设置促销信息
global.coups[product.id]=couponArray;
全局给定的[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;
}
app.post('/get优惠券',urlencodedParser,函数(req,res){
如果(!req.body)返回res.status(400)。发送(“错误请求”);
如果(!req.body.category)返回res.status(200).send(“请刷新页面”);
//去拿张优惠券
var优惠券=GetUserAcoon(请求主体对象ID);
res.type('text/plain');
资源状态(200)。发送(优惠券);
if(优惠券!=“售罄!”&&优惠券!=“错误请求:对象不存在。”){
//更新用户和产品分析
SetStatsAfterCouponsSend(req.body.objectId、req.body.sellerProduct、req.body.userEmail、req.body.purchaseProfileId、优惠券、req.body.category);
}
});
//获取优惠券逻辑
函数getUserAcoon(objectId){
var耦合反转;
//所需产品的优惠券数组
var coupsArray=global.coups[objectId];
如果(耦合类型!=“未定义”){
//抓取已经为该产品提供的优惠券数量,并增加一张
var num=全局。给定[objectId];
全局。给定[objectId]=num+1;
if(num
我对node.js服务器以及它们的功能没有太多的了解


一如既往,谢谢你的帮助

您是否考虑过在http请求进入时对其进行排队,以便像本问题中那样维护它们的顺序

问题在于节点的非阻塞/异步特性。同时请求对同一函数的调用不会等待对方完成。大量请求同时进入并访问全局代码数组

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

管理并发问题的一种方法是一次只允许一次访问(在您的情况下,访问
getuseracoon
),以便消费优惠券的执行部分同步互斥。实现这一点的一种方法是锁定机制,因此当一个请求获得对锁的访问权限时,进一步的请求将等待锁被释放。在伪代码中,它可能如下所示:

wait until lock exists
create lock
if any left, consume one coupon
remove lock
但这种方法违背了节点的非阻塞性,并且还引入了一个问题,即如果有多个请求在等待,当释放时谁将获得锁

一个更好的方法更可能是排队系统。它的工作原理应该是,在请求时不使用代码,而是将代码作为可调用代码放入队列中,等待启动。您可以读取队列长度并停止接受新请求(“售罄”),但是,这仍将在全局队列/计数器上同时进行,因此您可能会得到比优惠券更多的排队项目,但这不是一个问题,因为队列将同步处理,因此可以准确确定何时达到分配优惠券的数量,如果有,只需将“售罄”的优惠券发给其余的优惠券,更重要的是,确保每个代码只提供一次

使用,可以很容易地创建线性、延迟的任务列表:

var temporal = require("temporal");
global.queues = {};

这里的一个关键点是,temporal按顺序执行任务,将延迟相加,因此,如果延迟大于任务运行所需的延迟,则一次最多只能有一个任务访问计数器/代码数组


您也可以使用定时队列处理基于此逻辑实现自己的解决方案,但暂时性似乎值得一试。

Node.js只是单线程的,因此生成唯一优惠券并确保它们与过去的任何值不同应该不是问题。这个
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);

        }
      }  
    }]);
});