hexo-leancloud-counter-security 插件 Too many requests 错误

原文链接

详见我的博客原文,原文链接

概述

这个插件吧,有点儿不完美,给他改了改,不完美的地方就是,leancloud那边儿没法短时间内接受太多请求。原因是我用的是免费版的,有限制,而hexo-leancloud-counter-security 插件每次发太多无用请求,总是会报429错误,我就给源代码改了改。

症状

如果你懒得看解释的话,直接去看修改代码一节。
每次进行hexo d的时候会概率性的出现如下错误:

ERROR Too many requests. [429 POST https://xtppdvlr.api.lncld.net/1.1/classes/Counter]
Error: Too many requests. [429 POST https://xtppdvlr.api.lncld.net/1.1/classes/Counter]
    at E:\code\blog\node_modules\leancloud-storage\dist\node\request.js:163:17
    at tryCatch (E:\code\blog\node_modules\es6-promise\dist\es6-promise.js:410:12)
    at invokeCallback (E:\code\blog\node_modules\es6-promise\dist\es6-promise.js:425:13)
    at publish (E:\code\blog\node_modules\es6-promise\dist\es6-promise.js:399:7)
    at publishRejection (E:\code\blog\node_modules\es6-promise\dist\es6-promise.js:340:3)
    at flush (E:\code\blog\node_modules\es6-promise\dist\es6-promise.js:128:5)
    at _combinedTickCallback (internal/process/next_tick.js:131:7)
    at process._tickCallback (internal/process/next_tick.js:180:9)

官方解释如下:

信息 - Too many requests.
含义 - 超过应用的流控限制,即超过每个应用同一时刻最多可使用的工作线程数,或者说同一时刻最多可以同时处理的数据请求。通过 控制台 > 存储 > API 统计 > API 性能 > 总览 可以查看应用产生的请求统计数据,如平均工作线程、平均响应时间等。使用 LeanCloud 商用版或企业版 的用户,如有需要,可以联系我们来调整工作线程数。

原因

我查看了源代码,node_modules\hexo-leancloud-counter-security\index.js这个就是源代码。发现每次进行hexo d的时候,他对每个博文的title和url,向leancloud发送一次查询请求,如果发现leancloud那边儿没有该条记录的话,那么再发送一条插入请求。
原逻辑如下:

_.forEach(urls, function (x) {
    var query = new AV.Query('Counter');
    query.equalTo('url', x.url);
    query.count().then(function (count) {
        if (count === 0) {
            var counter = new Counter();
            counter.set('url', x.url);
            counter.set('title', x.title);
            counter.set('time', 0);
            counter.save().then(function (obj) {
                log.info(x.title + ' is saved as: ' + obj.id);
            }, function (error) {
                log.error(error);
            });
        }
    }, function (error) {
        log.error(error);
    });
});

也就是说,每一次hexo d的时候最少的查询次数等于你的博文个数。如果你的leancloud的应用的处理能力不够强大的时候,对于这种高强度的请求,当然会出现Too Many Requests的错误代码。

改进思路

我们要做的就是较少不必要的请求咯。
本地记录一个title和url的json数组,每次查询这个数组,看看哪些是真正的需要查询的,然后再去查询leancloud。其实可以这样理解,这个本地的数组存储就是leancloud的远程数据库表。
因为筛除了一些记录,所以每次hexo d时的请求数量仅仅是相比上一次hexo d时候的增量。

修改代码

如果你遇到了问题,看看问题解决一节。
修改的是node_modules\hexo-leancloud-counter-security\index.js这个文件
hexo-leancloud-counter-security版本是1.3.2
一共修改下面几处,在代码中已经做了标记。
代码70-84
代码87-102
代码113-117
代码120-123
代码126-132
代码135-138
代码140-142
代码225-268
改完了之后别着急,打开博客配置文件找到skip_render:这一项,然后加上leancloud_memo.json。不会加的看修改博客配置文件一节。

'use strict';

var _regenerator = require('babel-runtime/regenerator');

var _regenerator2 = _interopRequireDefault(_regenerator);

var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');

var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);

var _stringify = require('babel-runtime/core-js/json/stringify');

var _stringify2 = _interopRequireDefault(_stringify);

var sync = function () {
    var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee() {
        var log, config, APP_ID, APP_KEY, publicDir, UrlsFile, urls, currentUser, userName, passWord, Counter;
        return _regenerator2.default.wrap(function _callee$(_context) {
            while (1) {
                switch (_context.prev = _context.next) {
                    case 0:
                        log = this.log;
                        config = this.config;

                        if (!config.leancloud_counter_security.enable_sync) {
                            _context.next = 19;
                            break;
                        }

                        APP_ID = config.leancloud_counter_security.app_id;
                        APP_KEY = config.leancloud_counter_security.app_key;
                        publicDir = this.public_dir;
                        UrlsFile = pathFn.join(publicDir, 'leancloud_counter_security_urls.json');
                        urls = JSON.parse(fs.readFileSync(UrlsFile, 'utf8'));


                        AV.init({
                            appId: APP_ID,
                            appKey: APP_KEY
                        });

                        currentUser = AV.User.current();

                        if (currentUser) {
                            _context.next = 16;
                            break;
                        }

                        userName = config.leancloud_counter_security.username;
                        passWord = config.leancloud_counter_security.password;

                        if (!userName) {
                            userName = readlineSync.question('Enter your username: ');
                            passWord = readlineSync.question('Enter your password: ', { hideEchoBack: true });
                        } else if (!passWord) {
                            passWord = readlineSync.question('Enter your password: ', { hideEchoBack: true });
                        }
                        _context.next = 16;
                        return AV.User.logIn(userName, passWord).then(function (loginedUser) {
                            log.info('Logined as: ' + loginedUser.getUsername());
                        }, function (error) {
                            log.error(error);
                        });

                    case 16:

                        log.info('Now syncing your posts list to leancloud counter...');
                        Counter = AV.Object.extend('Counter');

                        //----add----
                        urls.sort(cmp);

                        var memoFile = pathFn.join(publicDir, "leancloud_memo.json");
                        if(!fs.existsSync(memoFile)){
                            fs.writeFileSync(memoFile, "[\n]");
                        }
                        var memoData = fs.readFileSync(memoFile, "utf-8").split("\n");
                        var memoIdx = 1;

                        var newData = [];
                        var cnt = 0;
                        var limit = 0;
                        var env = this;
                        //----end----

                        _.forEach(urls, function (x) {
                            //----add----
                            var y = {};
                            y.title = "";
                            y.url = "";

                            var flag = false;
                            while(true){
                                if(memoData[memoIdx] == ']') break;
                                y = JSON.parse(memoData[memoIdx].substring(0, memoData[memoIdx].length-1));
                                if(y.url > x.url) break;
                                if(y.url == x.url && y.title == x.title){
                                    flag = true;
                                    break;
                                }
                                memoIdx++;
                            }
                            if(!flag) {
                                log.info("Dealing with record of " + x.title);
                                limit++;
                                //----end----
                                var query = new AV.Query('Counter');
                                query.equalTo('url', x.url);
                                query.count().then(function (count) {
                                    if (count === 0) {
                                        var counter = new Counter();
                                        counter.set('url', x.url);
                                        counter.set('title', x.title);
                                        counter.set('time', 0);
                                        counter.save().then(function (obj) {
                                            log.info(x.title + ' is saved as: ' + obj.id);
                                            //----add----
                                            newData.push(x);
                                            cnt++;
                                            postOperation(env, cnt, limit, newData, memoData);
                                            //----end----
                                        }, function (error) {
                                            log.error(error);
                                            //----add----
                                            cnt++;
                                            postOperation(env, cnt, limit, newData, memoData);
                                            //----end----
                                        });
                                    }
                                    //----add----
                                    else{
                                        newData.push(x);
                                        cnt++;
                                        postOperation(env, cnt, limit, newData, memoData);
                                    }
                                    //----end----
                                }, function (error) {
                                    log.error(error);
                                    //----add----
                                    cnt++;
                                    postOperation(env, cnt, limit, newData, memoData);
                                    //----end----
                                });
                            //----add----
                            }
                            //----end----
                        });

                    case 19:
                    case 'end':
                        return _context.stop();
                }
            }
        }, _callee, this);
    }));

    return function sync() {
        return _ref.apply(this, arguments);
    };
}();

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var AV = require('leancloud-storage');
var _ = require('lodash');
var readlineSync = require('readline-sync');
var packageInfo = require('./package.json');
var pathFn = require('path');
var fs = require('fs');

function generate_post_list(locals) {
    var config = this.config;
    if (config.leancloud_counter_security.enable_sync) {
        var urlsPath = 'leancloud_counter_security_urls.json';
        var urls = [].concat(locals.posts.toArray()).filter(function (x) {
            return x.published;
        }).map(function (x) {
            return { title: x.title, url: '/' + x.path };
        });
        return {
            path: urlsPath,
            data: (0, _stringify2.default)(urls)
        };
    }
}

hexo.extend.generator.register('leancloud_counter_security_generator', generate_post_list);

hexo.extend.deployer.register('leancloud_counter_security_sync', sync);

var commandOptions = {
    desc: packageInfo.description,
    usage: ' <argument>',
    'arguments': [{
        'name': 'register | r <username> <password>',
        'desc': 'Register a new user.'
    }]
};

function commandFunc(args) {
    var log = this.log;
    var config = this.config;

    if (args._.length !== 3) {
        log.error('Too Few or Many Arguments.');
    } else if (args._[0] === 'register' || args._[0] === 'r') {
        var APP_ID = config.leancloud_counter_security.app_id;
        var APP_KEY = config.leancloud_counter_security.app_key;
        AV.init({
            appId: APP_ID,
            appKey: APP_KEY
        });

        var user = new AV.User();
        user.setUsername(String(args._[1]));
        user.setPassword(String(args._[2]));
        user.signUp().then(function (loginedUser) {
            log.info(loginedUser.getUsername() + ' is successfully signed up');
        }, function (error) {
            log.error(error);
        });
    } else {
        log.error('Unknown Command.');
    }
}

hexo.extend.console.register('lc-counter', 'hexo-leancloud-counter-security', commandOptions, commandFunc);

//----add----
function cmp(x, y){
    if(x.url < y.url)
        return -1;
    else if(x.url == y.url)
        return 0;
    else return 1;
}

var postOperation = function (env, cnt, limit, newData, memoData){
    if(cnt == limit){
        var log = env.log;
        newData.sort(cmp);
        var sourceDir = env.source_dir;
        var publicDir = env.public_dir;
        var memoFile = pathFn.join(sourceDir, "leancloud_memo.json");
        fs.writeFileSync(memoFile, "[\n");

        var memoIdx = 1;
        for(var i = 0; newData[i]; i++){
            while(true){
                if(memoData[memoIdx] == ']') break;
                var y = JSON.parse(memoData[memoIdx].substring(0, memoData[memoIdx].length-1));
                if(y.url > newData[i].url) break;

                fs.writeFileSync(memoFile, memoData[memoIdx] + "\n", {'flag':'a'});
                memoIdx++;
            }
            fs.writeFileSync(memoFile, "{\"title\":\"" + newData[i].title + "\",\"url\":\"" + newData[i].url + "\"},\n", {'flag':'a'});
        }
        while(memoData[memoIdx] != ']'){
            fs.writeFileSync(memoFile, memoData[memoIdx] + "\n", {'flag':'a'});
            memoIdx++;
        }
        fs.writeFileSync(memoFile, memoData[memoIdx], {'flag':'a'});

        var srcFile = pathFn.join(sourceDir, "leancloud_memo.json");
        var destFile = pathFn.join(publicDir, "leancloud_memo.json");
        var readStream = fs.createReadStream(srcFile);
        var writeStream = fs.createWriteStream(destFile);
        readStream.pipe(writeStream);
        console.log("leancloud_memo.json successfully updated.");
    }
}

修改博客配置文件

我们需要将leancloud_memo.json排除,否则的话会被渲染,我那个反正就是被渲染了。
博客配置文件中找到这个,然后加一项,如下:

skip_render: leancloud_memo.json

如果有多个项的话,可以这么加:

skip_render: 
  - 404.html
  - README.md
  - leancloud_memo.json

关键逻辑

维护memoData和urls数组有序,能够在O(2n)的复杂度内判断出有哪些博文(包括改了题目的,改了文件名的)不在表中。
最后的memoData和newData也是有序的,产生的新的文件也是有序的。
详见代码,不做过多解释。

额外代价

分析一下,额外引入的代价。
时间上的代价:
* 对urls数组进行排序。O(nlogn)
* urls数组和memoData数组比较以确定某个记录需要向leancloud查询。O(2n)
* 排序newData数组。O(nlogn)
* 将memoData和newData数组整合成一个新的数组。O(2n)
* 将memoData字符串split成数组一次。
* 读取memoData一次。
* 写memoData n次。
* 拷贝memoData一次。

空间上的代价:
* 引入newData数组,O(n)
* 引入memoData数组,O(n)
* 引入一个文件leancloud_memo.json

n是博文的数量,一般的话假设有1万篇,这个额外代价感觉还可以。

最后一行不加注释

为什么不在上面代码最后艺一行加上//—-end—-?因为加上注释以后会报错。报错如下:

ERROR Plugin load failed: hexo-leancloud-counter-security
E:\code\blog\node_modules\hexo-leancloud-counter-security\index.js:213
}
^

SyntaxError: Unexpected end of input
    at createScript (vm.js:80:10)
    at Object.runInThisContext (vm.js:139:10)
    at fs.readFile.then.script (E:\code\blog\node_modules\hexo\lib\hexo\index.js:230:19)
    at tryCatcher (E:\code\blog\node_modules\bluebird\js\release\util.js:16:23)
    at Promise._settlePromiseFromHandler (E:\code\blog\node_modules\bluebird\js\release\promise.js:512:31)
    at Promise._settlePromise (E:\code\blog\node_modules\bluebird\js\release\promise.js:569:18)
    at Promise._settlePromise0 (E:\code\blog\node_modules\bluebird\js\release\promise.js:614:10)
    at Promise._settlePromises (E:\code\blog\node_modules\bluebird\js\release\promise.js:693:18)
    at Promise._fulfill (E:\code\blog\node_modules\bluebird\js\release\promise.js:638:18)
    at Promise._resolveCallback (E:\code\blog\node_modules\bluebird\js\release\promise.js:432:57)
    at Promise._settlePromiseFromHandler (E:\code\blog\node_modules\bluebird\js\release\promise.js:524:17)
    at Promise._settlePromise (E:\code\blog\node_modules\bluebird\js\release\promise.js:569:18)
    at Promise._settlePromise0 (E:\code\blog\node_modules\bluebird\js\release\promise.js:614:10)
    at Promise._settlePromises (E:\code\blog\node_modules\bluebird\js\release\promise.js:693:18)
    at Promise._fulfill (E:\code\blog\node_modules\bluebird\js\release\promise.js:638:18)
    at E:\code\blog\node_modules\bluebird\js\release\nodeback.js:42:21
    at E:\code\blog\node_modules\graceful-fs\graceful-fs.js:78:16
    at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:511:3)
ERROR Deployer not found: leancloud_counter_security_sync

问题解决

如果你在hexo d的时候,发现leancloud那边儿没有添加上记录。排除了各种错误之后,还是没有的话。
你可以把source文件夹下的leancloud_memo.json文件删除,然后重新hexo clean & hexo g & hexo d就好了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值