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

标签 javascript ajax node.js object

我运行一个优惠券网站,当我们启动我们的交易时,它会看到每秒 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/

相关文章:

javascript - 使用 math.random 函数的多个输出

javascript - jQuery .text() 方法重复自身。如何让它只运行一次?

javascript - 阻止 Chrome 中不包含特定模式的请求 URL

javascript - ExpressJS 错误处理不起作用

node.js - Node : Error in installing npm package

node.js - 如何在 Express.JS-App 中包含 Mongoose/MongoDB(ES2015 方式)

javascript - 我的 Node js 项目中的全局变量出现问题

javascript - 查找大写字母的简单正则表达式返回小写字母

ajax - JSF 语言切换器和 ajax 更新

java - 在 java 和 jquery 中重定向 ajax 调用