Недавно я получил запрос, который очень прост: запустить сервер, вызвать предоставленный интерфейс при получении запроса, а затем вернуть результат. Из-за проблем с производительностью этого интерфейса, и в то же время количество запросов не может превышать определенного числа, поэтому необходимо ограничить ток в сервисе.
Требование текущего лимита состоит в том, чтобы ограничить количество одновременных выполнений, и после превышения этого числа его необходимо буферизовать в очереди.
промежуточное ПО koa не вызывает next
Первоначальная идея состояла в том, чтобы сделать подсчет в промежуточном программном обеспечении koa, когда их больше 6.next
функция кэшируется. Когда выполняемая задача завершается, вызовитеnext
Продолжайте с другими запросами.
Позже выяснилось, что в промежуточном программном обеспечении koa он не выполнялся.next
Запрос функции не остановится, но не вызовет последующее промежуточное ПО и не вернет содержимое напрямую.
const Koa = require('koa');
const app = new Koa();
app.use((ctx, next) => {
console.log('middleware 1');
setTimeout(() => {
next();
}, 3000);
ctx.body = 'hello';
});
app.use((ctx, next) => {
console.log('middleware 2');
});
app.listen(8989);
Приведенный выше код сначала выводит «промежуточное ПО 1» в консоли => браузер получает «привет» => консоль печатает «промежуточное ПО 2».
Еще одна вещь, которую следует отметить, это то, что запрос завершился (finish
), его следующий метод все еще может быть вызван, и промежуточное ПО продолжит работу (но модификация ctx не вступит в силу, поскольку запрос уже возвращен). Аналогично, закрытые запросы (close
) делает то же самое.
Используйте await, чтобы сделать запрос ожидающим
Задерживатьnext
Выполнение функции не достигает своей цели. Следующее, что приходит на ум, это использоватьawait
Сделать текущий запрос ожидающим.await
Функция возвращаетPromise
, мы сделаем этоPromise
серединаresolve
Функции хранятся в очереди и вызываются лениво.
const Koa = require('koa');
const app = new Koa();
const queue = [];
app.use(async (ctx, next) => {
setTimeout(() => {
queue.shift()();
}, 3000);
await delay();
ctx.body = 'hello';
});
function delay() {
return new Promise((resolve, reject) => {
queue.push(resolve);
});
}
app.listen(8989);
Приведенный выше код, вdelay
функция возвращаетPromise
,Promise
изresolve
Функция хранится в очереди. После установки тайминга 3 с выполните очередь вresolve
функция, чтобы запрос продолжался.
Дроссель для маршрутизации или для запросов?
После того, как базовый принцип ограничения тока реализован, возникает следующий вопрос: где должен быть записан код ограничения тока? По сути, есть две локации:
Ограничить ток на интерфейсах
Из-за наших потребностей текущее ограничение связано с тем, что производительность запрашиваемого интерфейса ограничена. Таким образом, мы можем ограничить ток для этого запроса отдельно:
async function requestSomeApi() {
// 如果已经超过了最大并发
if (counter > maxAllowedRequest) {
await delay();
}
counter++;
const result = await request('http://some.api');
counter--;
queue.shift()();
return result;
}
Ниже также приведена более удобная для повторного использования версия.
async function limitWrapper(func, maxAllowedRequest) {
const queue = [];
const counter = 0;
return async function () {
if (counter > maxAllowedRequest) {
await new Promise((resolve, reject) => {
queue.push(resolve);
});
}
counter++;
const result = await func();
counter--;
queue.shift()();
return result;
}
}
Дросселирование для маршрутизации
Этот способ заключается в написании промежуточного программного обеспечения koa и ограничении тока в промежуточном программном обеспечении:
async function limiter(ctx, next) => {
// 如果超过了最大并发数目
if (counter >= maxAllowedRequest) {
// 如果当前队列中已经过长
await new Promise((resolve, reject) => {
queue.push(resolve);
});
}
store.counter++;
await next();
store.counter--;
queue.shift()();
};
Затем используйте это промежуточное ПО в маршрутизаторе для разных маршрутов:
router.use('/api', rateLimiter);
Сравнивать
Реализовал текущее ограничение для интерфейса и почувствовал, что логика была немного запутанной, поэтомуПереключено на текущее регулирование для маршрутизации, все работает отлично.
Пока я не получил другой запрос, я должен был трижды запросить этот интерфейс, чтобы вернуть массив результатов трех запросов. Теперь возникает проблема, мы не можем вызвать интерфейс напрямую, потому что нам нужно ограничить ток. Также невозможно напрямую вызвать функцию интерфейса запроса, потому что наш текущий лимит основан на маршрутизации. тогда что нам делать? Нам нужно только запросить этот маршрут и запросить самих себя. . .
Моменты, на которые следует обратить внимание
- Прослушайте событие закрытия и удалите запрос из очереди
Запросы, уже сохраненные в очереди, могут быть отменены пользователем. Как упоминалось ранее, даже если запрос будет отменен в koa, последующее промежуточное ПО все равно будет работать, то есть будет выполняться интерфейс, который необходимо ограничить, что приведет к потерям.
может контролироватьclose
События для достижения этой цели нам нужно пометить каждый запрос хеш-значением:
ctx.res.on('close', () => {
const index = queue.findIndex(item => item.hash === hash);
if (index > -1) {
queue.splice(index, 1);
}
});
- установить тайм-аут
Чтобы пользователи не ждали слишком долго, необходимо установить тайм-аут, который легко реализовать в koa:
const server = app.listen(config.port);
server.timeout = DEFAULT_TIMEOUT;
- Текущая очередь слишком длинная
Если текущая очередь слишком длинная, время ожидания даже присоединения к очереди истечет. Поэтому нам также нужно обработать случай, когда очередь слишком длинная:
if (queue.length > maxAllowedRequest) {
ctx.body = 'error message';
return;
}