淺談Koa服務(wù)限流方法實(shí)踐
最近接了一個(gè)需求,很簡單,就是起一個(gè)server,收到請求時(shí)調(diào)用某個(gè)提供好的接口,然后把結(jié)果返回。因?yàn)檫@個(gè)接口的性能問題,同時(shí)在請求的不能超過特定數(shù)目,要在服務(wù)中進(jìn)行限流。
限流的要求是,限制同時(shí)執(zhí)行的數(shù)目,超出這個(gè)數(shù)目后要在一個(gè)隊(duì)列中進(jìn)行緩存。
koa 中間件不調(diào)用 next
最初的想法是在 koa 中間件中進(jìn)行計(jì)數(shù),超過6個(gè)時(shí)將next函數(shù)緩存下來。等正在進(jìn)行中的任務(wù)結(jié)束時(shí),調(diào)用next繼續(xù)其他請求。
之后發(fā)現(xiàn) koa 中間件中,不執(zhí)行next函數(shù)請求并不會停下,而是不再調(diào)用之后的中間件,直接返回內(nèi)容。
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);
以上代碼首先在控制臺打出 ‘middleware 1' => 瀏覽器收到 ‘hello' => 控制臺打出 ‘middleware 2'。
這里還有一個(gè)要注意的地方,就是一個(gè)請求已經(jīng)結(jié)束(finish)后,他的next方法還是可以繼續(xù)調(diào)用,之后的middleware還是繼續(xù)運(yùn)行的(但是對ctx的修改不會生效,因?yàn)檎埱笠呀?jīng)返回了)。同樣,關(guān)閉的請求(close)也是同樣的表現(xiàn)。
使用 await 讓請求進(jìn)行等待
延遲next函數(shù)執(zhí)行不能達(dá)到目的。接下來自然想到的就是使用await讓當(dāng)前請求等待。await的函數(shù)返回一個(gè)Promise,我們將這個(gè)Promise中的resolve函數(shù)存儲到隊(duì)列中,延遲調(diào)用。
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函數(shù)中返回一個(gè)Promise,Promise的resolve函數(shù)存入隊(duì)列中。設(shè)置定時(shí)3s后執(zhí)行隊(duì)列中的resolve函數(shù),使請求繼續(xù)執(zhí)行。
針對路由進(jìn)行限流,還是針對請求進(jìn)行限流?
限流的基本原理實(shí)現(xiàn)后,下面一個(gè)問題就是限流代碼該寫在哪里?基本上,有兩個(gè)位置:
針對接口進(jìn)行限流
由于我們的需求中,限流是因?yàn)橐埱蠼涌诘男阅苡邢蕖K晕覀兛梢詥为?dú)針對這個(gè)請求進(jìn)行限流:
async function requestSomeApi() {
// 如果已經(jīng)超過了最大并發(fā)
if (counter > maxAllowedRequest) {
await delay();
}
counter++;
const result = await request('http://some.api');
counter--;
queue.shift()();
return result;
}
下面還有一個(gè)方便復(fù)用的版本。
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;
}
}
針對路由進(jìn)行限流
這種方式是寫一個(gè)koa中間件,在中間件中進(jìn)行限流:
async function limiter(ctx, next) => {
// 如果超過了最大并發(fā)數(shù)目
if (counter >= maxAllowedRequest) {
// 如果當(dāng)前隊(duì)列中已經(jīng)過長
await new Promise((resolve, reject) => {
queue.push(resolve);
});
}
store.counter++;
await next();
store.counter--;
queue.shift()();
};
之后針對不同路由在router中使用這個(gè)中間件就好了:
router.use('/api', rateLimiter);
比較
實(shí)現(xiàn)了針對接口進(jìn)行限流,覺得邏輯有些亂,于是改用了針對路由進(jìn)行限流,一切運(yùn)行的很完美。
直到我又接了個(gè)需求,是要請求三次這個(gè)接口返回三次請求的結(jié)果數(shù)組?,F(xiàn)在問題來了,我們不能直接調(diào)用接口,因?yàn)橐蘖?。也不能直接調(diào)用請求接口的函數(shù)因?yàn)槲覀兊南蘖魇且月酚蔀閱挝坏摹D窃趺崔k呢?我們只有請求這個(gè)路由了,自己請求自己。。。
需要注意的地方
監(jiān)聽close事件,將請求從隊(duì)列中移出
已經(jīng)存儲在隊(duì)列中的請求,有可能遇到用戶取消的情況。前面說過koa中即使請求取消,之后的中間件還是會運(yùn)行,也就是還會執(zhí)行需要限流的接口,造成浪費(fèi)。
可以監(jiān)聽close事件來達(dá)到這個(gè)目的,每個(gè)請求我們需要用hash值來標(biāo)記:
ctx.res.on('close', () => {
const index = queue.findIndex(item => item.hash === hash);
if (index > -1) {
queue.splice(index, 1);
}
});
設(shè)置超時(shí)時(shí)間
為了防止用戶等待過長時(shí)間,需要設(shè)置超時(shí)時(shí)間,這在koa中很容易實(shí)現(xiàn):
const server = app.listen(config.port); server.timeout = DEFAULT_TIMEOUT;
當(dāng)前隊(duì)列已經(jīng)過長
如果當(dāng)前隊(duì)列已經(jīng)過長了,即使加入隊(duì)列中也會超時(shí)。因此我們還需要處理隊(duì)列過長的情況:
if (queue.length > maxAllowedRequest) {
ctx.body = 'error message';
return;
}
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
node自定義安裝更改npm全局模塊默認(rèn)安裝路徑的步驟
有段時(shí)間沒用npm了,新建個(gè)項(xiàng)目,需要改變npm全局包默認(rèn)安裝的路徑,本文就來介紹一下node自定義安裝更改npm全局模塊默認(rèn)安裝路徑的步驟,感興趣的可以了解下2021-09-09
Node.js環(huán)境下Koa2添加travis ci持續(xù)集成工具的方法
這篇文章主要給大家介紹了在Node.js環(huán)境下Koa2添加travis ci持續(xù)集成工具的方法,文中介紹的非常詳細(xì),對大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編一起來學(xué)習(xí)學(xué)習(xí)吧。2017-06-06
node實(shí)現(xiàn)定時(shí)發(fā)送郵件的示例代碼
本篇文章主要介紹了node實(shí)現(xiàn)定時(shí)發(fā)送郵件的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-08-08
Nodejs中使用puppeteer控制瀏覽器中視頻播放功能
本項(xiàng)目主要功能為在瀏覽器中自動播放視頻,并且實(shí)現(xiàn)音量控制,快進(jìn)快退,全屏控制,播放暫??刂频裙δ?。對Nodejs中使用puppeteer控制瀏覽器中視頻播放功能感興趣的朋友跟隨小編一起看看吧2019-08-08
Node.js使用Streams來處理文件讀寫操作的示例代碼
在Node.js中,Streams 提供了一種高效的方式來處理文件的讀寫操作,特別是對于大文件或數(shù)據(jù)流,Streams 允許你以流的方式讀寫數(shù)據(jù),這意味著數(shù)據(jù)可以分塊處理,本文介紹了在Node.js中如何使用Streams來處理文件讀寫操作,需要的朋友可以參考下2024-09-09
整理幾個(gè)關(guān)鍵節(jié)點(diǎn)深入理解nodejs
這篇文章主要介紹了整理幾個(gè)關(guān)鍵節(jié)點(diǎn)深入理解nodejs,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,需要的小伙伴可以參考一下,需要的小伙伴可以參考一下2022-07-07

