Javascript-форум (https://javascript.ru/forum/)
-   Node.JS (https://javascript.ru/forum/node-js-io-js/)
-   -   Помогите с алгоритмом (https://javascript.ru/forum/node-js-io-js/70796-pomogite-s-algoritmom.html)

Signal 03.10.2017 13:46

Помогите с алгоритмом
 
собственно задача, есть некий сайт с которым работаю по API
разрешено делать не более 8 запросов в секунду
есть у меня несколько API ключей

вот никак не соображу как делать запросы по каждому API ключу и так, чтобы не более 8 в секунду
загрузка работает скачалось и забылось

база для запросов содержит около 10 тысяч записей

сейчас это реализовано тупо формирую блоки по API.length+(API.length*8) но бывает так, что 1 API уже отработал 8 запросов и ждет пока отработает весь блок

помогите с алгоритмом, что-то уже башка не соображает
заранее благодарен вкусным морковным соком

Alexandroppolus 03.10.2017 14:53

Цитата:

Сообщение от Signal
база для запросов содержит около 10 тысяч записей

Тебе надо сделать 10000 запросов, ограничение не более 8 в секунду на один ключ, у тебя есть N ключей, и надо придумать как организовать запросы? я правильно понял?

Signal 03.10.2017 15:13

Цитата:

Сообщение от Alexandroppolus (Сообщение 466402)
Тебе надо сделать 10000 запросов, ограничение не более 8 в секунду на один ключ, у тебя есть N ключей, и надо придумать как организовать запросы? я правильно понял?

да, все правильно

Alexandroppolus 03.10.2017 16:36

Signal,
какие параметры передаются в каждый запрос? эти параметры уже есть в коде? или они приходят от предыдущих запросов?

можно ли слать запросы одновременно (в пределах ограничений)? т.е. можно ли просто взять и отправить с одного ключа сразу 8 запросов?

Signal 03.10.2017 17:08

да с каждого ключа 8 запросов в секунду можно делать (одновременно)
посылается только 1 параметр в запросе

Alexandroppolus 04.10.2017 13:08

Навскидку видится такой вариант (сотворено на коленке, не дебажил):

function request(key, param, callback) { ... } // делает запрос в API

function APIClient(key, data) {
	this._key = key;
	this._data = data;
	this.nextTime = 0;
	this.loading = false;
	this._finish = this._finish.bind(this);
}

APIClient.prototype.start = function() {
	if (this._data.params.length) {
		var param = this._data.params.pop();
		request(this._key, param, this._finish);
		this._data.requests++;
		this.loading = true;
		this.param = param;
		this.nextTime = Date.now() + 1000; // следующий запрос через секунду, не ранее
	}
};
APIClient.prototype._finish = function(error) {
	if (error) {
		// запрос был с ошибкой, будем пробовать ещё раз для этого параметра
		this._data.params.push(this.param);
	}
	this.loading = false;
	this.param = null;
	this._data.requests--;
	if (!this._data.requests && !this._data.params.length && this._data.timer) {
		clearInterval(this._data.timer);
		this._data.timer = null;
		this._data.callback();
	}
};

function loadAll(keys, limit, params, callback) {
	var data = {
		params: params,
		requests: 0,
		timer: null,
		callback: callback
	};
	var clients = [];
	for (var i = 0; i < keys.length; ++i) {
		for (var j = 0; j < limit; ++j) {
			clients.push(new APIClient(keys[i], data));
		}
	}
	data.timer = setInterval(function () {
		var time = Date.now();
		for (var i = 0; i < clients.length; ++i) {
			var client = clients[i];
			if (time >= client.nextTime && !client.loading) {
				client.start();
			}
		}
	}, 300);
}

// запуск
loadAll([...], 8, [...], function() {
    ...
});


для каждого ключа создаем по 8 клиентов, все складываем в массив. Каждый клиент делает запрос не чаще раза в секунду. Отмечает время старта запроса (точнее, время следующего старта). И есть один общий таймер, который проверяет всех клиентов и стартует тех, которые уже успели "отдохнуть". Последний клиент, завершившись, уничтожает таймер и вызывает общий колбэк.

при такой схеме никто никого не ждет.

если требуется минутный интервал между финишем предыдущего запроса и стартом нового, то установку nextTime перенеси в _finish

EmperioAf 06.10.2017 14:13

const queue = require('async').queue;
const request = require('request');

/** Class control free keys. */
function ApiKeys(apiKeys) {
    this.apiKeys = apiKeys;
    this.locked = {};
}

/**
 * Get a free key and lock it
 * @return {string} Key.
 */
ApiKeys.prototype.getKey = function () {
    for (const key of this.apiKeys) {
        if (!this.locked[key]) {
            this.locked[key] = true;
            return key;
        }
    }
}

/**
 * Method free key for next request
 * @param {string} key - Locked key.
 */
ApiKeys.prototype.freeKey = function (key) {
    this.locked[key] = false;
}

const API_KEYS = ['key1', 'key2', 'key3', 'key4', 'key5', 'key6', 'ke7', 'key8'];
const MAX_WORKERS_IN_QUEUE = API_KEYS.length;

const apiKeys = new ApiKeys(API_KEYS);


const requestQueue = queue(function (task, callback) {
    const key = apiKeys.getKey();
    task.options.url += '&key=' + key;
    request(task.options, (err, res, body) => {
        apiKeys.freeKey(key);
        callback(err, res, body);
    })
}, MAX_WORKERS_IN_QUEUE);


const requestUrls = [];
for (const url of requestUrls) {
    const options = {
        url: url,
        method: 'GET'
    };
    requestQueue.push({ options: options }, function (err, res, body) {
        // process response here
    });
}
requestQueue.drain = function () {
    console.log('all items have been processed');
};

Signal 09.10.2017 17:03

Alexandroppolus,
Благодарствую!!! работает, с меня вкусный морковный сок!!!

EmperioAf,
Тоже благодарю попробую и твой вариант, таких задач приходится делать много разных по ходу работы, тоже его опробую.


Часовой пояс GMT +3, время: 03:26.