詳解如何在NodeJS應(yīng)用程序中處理多個API請求
Redis緩存
如果你有一組數(shù)據(jù)在加載應(yīng)用程序時經(jīng)常需要獲取,你可能希望將這些數(shù)據(jù)緩存起來,而不是發(fā)送HTTP請求或運行查詢。這就是為什么我建議你使用Redis。它非常簡單易用。它基本上是另一個與主數(shù)據(jù)庫分開的數(shù)據(jù)庫,用于存儲所有緩存數(shù)據(jù)。
您需要在系統(tǒng)上安裝Redis,并可以使用以下代碼與Redis進(jìn)行交互。
user@username:/mnt/c/Users/HP$ sudo service redis-server start user@username:/mnt/c/Users/HP$sudo service redis-server-start user@username:/mnt/c/Users/HP$redis-cli user@username:/mnt/c/Users/HP$set mykey "hello" user@username:/mnt/c/Users/HP$get mykey
現(xiàn)在要在您的NodeJS應(yīng)用程序中使用它,您需要安裝redis包,并在名為redis.js的文件中創(chuàng)建一個redis客戶端的實例(您可以隨意命名)。
import redis from 'redis'
const PORT = process.env.REDIS_URL || 'redis://localhost:6379'
const client = redis.createClient({
url: PORT
})
await client.connect()
client.on('error', (error) => {
console.error('Redis client error:', error);
});
client.on('connect', (err) => {
console.log('Connected to redis')
})
export default client
完成上述步驟后,您可以通過簡單地編寫以下代碼來導(dǎo)入客戶端并設(shè)置一個鍵
await client.set('mykey', 'hello')
節(jié)點緩存
這只是一個簡單的緩存機(jī)制。在我們討論如何使用這個node-cache包之前,我們需要談?wù)劄槭裁催@與Redis緩存不同。
Node緩存是一個內(nèi)存緩存,而Redis緩存是存儲在“外部”的。這意味著,一旦您的節(jié)點應(yīng)用程序重新啟動,它將丟失其數(shù)據(jù)并且必須重新緩存。而Redis數(shù)據(jù)將存儲在網(wǎng)絡(luò)中,直到被刪除。Redis緩存也可以從另一個設(shè)備訪問,因為它基本上就像一個數(shù)據(jù)庫,但是node緩存只存在于特定的nodejs應(yīng)用程序中。除非您的nodejs應(yīng)用程序中有一個獲取路由來獲取緩存數(shù)據(jù)。
首先,安裝node-cache包并創(chuàng)建一個node-cache對象的實例來設(shè)置對象
import NodeCache from "node-cache"
const myCache = new NodeCache()
// to set one element
const success = myCache.set( "myKey", "hello");
// to set multiple elements
const obj = { my: "Special", variable: 42 };
const obj2 = { my: "other special", variable: 1337 };
const success = myCache.mset([
{key: "myKey", val: obj, ttl: 10000},
{key: "myKey2", val: obj2},
])
// to get the data
const value = myCache.get( "myKey" );
集群
通常,我們運行一個單獨的NodeJS應(yīng)用程序,它將接收任何類型的請求。想象一下,我們可以分別運行大約8個副本的應(yīng)用程序,并放置一個負(fù)載均衡器,將請求分發(fā)到可用的應(yīng)用程序。這正是集群所做的事情!
與其他不同,這是一個內(nèi)置的NodeJS包,不需要您下載任何類型的包。
需要注意的一點是,如果您運行N個NodeJS應(yīng)用程序,數(shù)據(jù)不會在這些應(yīng)用程序之間共享。它們每個都以一個進(jìn)程ID獨立運行。
讓我以一個基于express的應(yīng)用程序為例。通常我們做的是:
const app = express()
const PORT = process.env.PORT || 5000
app.listen(PORT, () => console.log(`Server is running successfully on PORT ${PORT}`))
但是在運行集群時,您需要運行N個這樣的服務(wù)器。(N代表系統(tǒng)中的CPU數(shù)量)
import cluster from 'cluster'
const numCpu = os.cpus().length
if(cluster.isPrimary){
console.log(`Primary ${process.pid} is running`)
for(let i=0; i<numCpu; i++){
cluster.fork()
}
cluster.on('exit', (worker, code, signal) => {
console.log(`${worker.process.pid} has exited`)
cluster.fork()
})
}else{
app.listen(PORT, () => console.log(`Server ${process.pid} is running successfully on PORT ${PORT}`))
}
fork()函數(shù)是觸發(fā)新工作進(jìn)程的函數(shù),如果您注意到了,它會觸發(fā)fork()函數(shù)numCpu次。主工作進(jìn)程監(jiān)聽所有連接,然后以輪詢方式將負(fù)載分配給其他工作進(jìn)程。“輪詢”只是指一種算法,它僅在一組可用資源/服務(wù)器之間分配任務(wù)。
工作線程
再次強(qiáng)調(diào),這是NodeJS提供的另一個內(nèi)置包。
首先,您需要了解NodeJS默認(rèn)是單線程的。單線程意味著您的Node應(yīng)用程序只有一個實例在運行,即主線程。這個主線程接收所有請求并按順序執(zhí)行它們。這個主線程被稱為“事件循環(huán)”。事件循環(huán)負(fù)責(zé)異步管理I/O操作,如網(wǎng)絡(luò)請求、文件操作和數(shù)據(jù)庫查詢。
現(xiàn)在這帶來了自己的優(yōu)勢和劣勢。當(dāng)單線程出現(xiàn)錯誤時,錯誤處理變得困難,因為主線程會崩潰。但如果是多線程,它確保只有發(fā)生錯誤的線程崩潰,其余線程將繼續(xù)正常工作。
這就是為什么Node.js給我們提供了一個名為worker-threads的包的選項,它可以幫助我們將單線程的Node應(yīng)用程序轉(zhuǎn)換為多線程應(yīng)用程序。讓我們看看如何在我們的應(yīng)用程序中實現(xiàn)這一點:
主線程(calc.js):
import { Worker, workerData } from "worker_threads"
const makeCalculation = async (req, res) => {
try {
const worker = new Worker('./worker.js', {
workerData: {
num: 10
}
})
worker.on('message', (message) => {
if(message.success){
res.send({message: 'Successfully calculated', success: true, ans: message.ans})
}else{
res.send({message: 'Calculation not possible', success: false})
}
})
} catch (error) {
console.log('Error 7: ', error)
}
}
export default makeCalculation;
工作線程(worker.js):
import { parentPort, workerData } from 'worker_threads';
const ans = workerData.num*10
parentPort.postMessage({message: 'Successfully calculated', success: true, ans: ans})
這樣的工作方式是,在主線程上,我們使用Worker()函數(shù)創(chuàng)建一個新的工作線程,該函數(shù)接受一個文件名作為參數(shù)。我們還向該文件傳遞數(shù)據(jù),這些數(shù)據(jù)可以通過workerData對象訪問。因此,主線程等待來自工作線程的消息事件,這樣可以很容易地避免在工作線程中出現(xiàn)任何錯誤時導(dǎo)致應(yīng)用程序崩潰。
微服務(wù)架構(gòu)

通常我們使用的是單體架構(gòu),這在一定程度上是可以的。但是在某個階段,一些項目會涉及到多個表、數(shù)據(jù)庫等,這種架構(gòu)已經(jīng)無法滿足需求。這就是微服務(wù)概念誕生的原因。
顧名思義,多個服務(wù)被創(chuàng)建來執(zhí)行特定的任務(wù),這些服務(wù)相互連接。基本上,我們正在分發(fā)應(yīng)用程序,每個應(yīng)用程序負(fù)責(zé)在特定主題上執(zhí)行一組特定的操作。

舉個例子,想象一下你要創(chuàng)建一個類似滴滴的應(yīng)用。你可能會在單個數(shù)據(jù)庫中為用戶和司機(jī)各創(chuàng)建一個表。這是普通開發(fā)者的做法。但是對于滴滴及其龐大的網(wǎng)絡(luò)來說,這種方法行不通。這就是為什么他們需要一個專門負(fù)責(zé)用戶的nodejs應(yīng)用,另一個專門負(fù)責(zé)司機(jī)的應(yīng)用。在某些情況下,它們可能會進(jìn)一步分成更多的應(yīng)用。需要注意的是,用戶和司機(jī)的整個數(shù)據(jù)庫也必須分別創(chuàng)建。
現(xiàn)在設(shè)計這樣的架構(gòu)可能會導(dǎo)致問題,如數(shù)據(jù)不一致、服務(wù)間通信、部署管理等,這些都應(yīng)在實施之前考慮到。因此,需要非常謹(jǐn)慎的設(shè)計和觀察來完成這項工作。
實現(xiàn)這一點的方法很簡單,只需創(chuàng)建兩個運行在不同端口上的不同應(yīng)用程序:
users.js
const express = require('express');
const app = express();
app.listen(3000, () => {
console.log('Users service is running on port 3000');
});
drivers.js
const express = require('express');
const app = express();
app.listen(4000, () => {
console.log('Drivers service is running on port 3000');
});
Redis Lock
正如我所說,Redis提供了許多服務(wù),鎖就是其中之一。
首先,當(dāng)您想要防止可能導(dǎo)致死鎖的情況發(fā)生時,就會使用鎖。這種情況發(fā)生在您想要運行進(jìn)程A,而該進(jìn)程需要使用另一個正在運行的進(jìn)程B返回的數(shù)據(jù)時。因此,進(jìn)程A必須等待進(jìn)程B完成后才能繼續(xù)。
為了實現(xiàn)這一點,我們使用Redis Lock。當(dāng)進(jìn)程B啟動時,它會獲取一個鎖,直到它的工作完成,它將保持鎖定。只有在執(zhí)行完成后,它才會釋放鎖。現(xiàn)在進(jìn)程A可以恢復(fù)其工作。
讓我們在代碼中看看這個,這可能會讓你更好地理解:
import redis from 'redis'
const client = redis.createClient();
function processA() {
client.set('my_lock', 'locked', 'NX', 'EX', 10, (error, result) => {
if (result === 'OK') {
console.log('Function A is executing...');
setTimeout(() => {
client.del('my_lock', (error, result) => {
console.log('Lock released by Function A.');
});
}, 5000);
} else {
console.log('Function A is waiting for the lock...');
setTimeout(functionA, 1000);
}
});
}
function processB() {
client.set('my_lock', 'locked', 'NX', 'EX', 10, (error, result) => {
if (result === 'OK') {
console.log('Function B is executing...');
setTimeout(() => {
client.del('my_lock', (error, result) => {
console.log('Lock released by Function B.');
});
}, 3000);
} else {
console.log('Function B is waiting for the lock...');
setTimeout(functionB, 1000);
}
});
}
processA();
processB();
所以這里我正在運行processA()和processB()。一旦processA()開始,它就使用set()函數(shù)設(shè)置一個鎖。
“NX”代表“不存在”。它確保只有在鎖不存在時才設(shè)置鎖。“EX”代表“過期”。10確定了鎖將被設(shè)置的秒數(shù)。
由于processA()已經(jīng)獲取了鎖,processB()中的結(jié)果將為null,因此它將無法執(zhí)行其任務(wù)并開始等待。
注意:默認(rèn)情況下,Javascript是單線程的,因此每個函數(shù)按順序執(zhí)行。這意味著在這個例子中,無論鎖是否被獲取,processB()都只會在processA()完成后執(zhí)行。因此,要完全理解Redis鎖的用例,您必須在應(yīng)用程序中擁有分布式實例。想象一下在不同的應(yīng)用程序上運行processA()和processB()。這才是您實際需要實現(xiàn)這一功能的時候。
總結(jié)一下..
我們已經(jīng)簡單了解了Redis緩存、Node緩存、集群、工作線程、微服務(wù)架構(gòu)和Redis鎖。這篇文章中寫的只是簡單的介紹和使用,如果想要深入了解,請參考官方文檔。
以上就是詳解如何在NodeJS應(yīng)用程序中處理多個API請求的詳細(xì)內(nèi)容,更多關(guān)于NodeJS中處理多個API請求的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于Node.js實現(xiàn)大文件斷點續(xù)傳的完整方案
本文介紹了Vue前端和Node.js后端實現(xiàn)大文件斷點續(xù)傳的方法,前端通過分片上傳、計算文件哈希、記錄已上傳分片索引實現(xiàn)斷點續(xù)傳;后端提供檢查和上傳分片、合并文件的接口,通過并發(fā)控制、錯誤重試等技術(shù)提高上傳效率和穩(wěn)定性,需要的朋友可以參考下2026-04-04
Node.js 利用cheerio制作簡單的網(wǎng)頁爬蟲示例
本篇文章主要介紹了Node.js 利用cheerio制作簡單的網(wǎng)頁爬蟲示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-03-03
Nodejs做文本數(shù)據(jù)處理實現(xiàn)詳解
這篇文章主要為大家介紹了Nodejs做文本數(shù)據(jù)處理實現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
nodejs個人博客開發(fā)第五步 分配數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了nodejs個人博客開發(fā)的分配數(shù)據(jù),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04
Node.js 與 Webpack 模塊化工程化入門指南教程
本文介紹了Node.js的基礎(chǔ)知識,包括什么是Node.js、如何執(zhí)行代碼、fs和path模塊的使用方法,以及簡易前端工程化實踐,隨后,文章講解了Web服務(wù)基礎(chǔ)、模塊化開發(fā)、npm包管理,以及使用Webpack進(jìn)行前端工程化和優(yōu)化,感興趣的朋友跟隨小編一起看看吧2026-03-03
electron demo項目npm install安裝失敗的解決方法
下面小編就為大家分享一篇electron demo項目npm install安裝失敗的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-02-02
Nodejs小文件拷貝復(fù)制和大文件拷貝復(fù)制方法代碼
NodeJS提供了基本的文件操作API,但是像文件拷貝復(fù)制這種高級功能就沒有提供,因此我們先拿文件拷貝程序練手,文件拷貝復(fù)制是在Node.js中常見的操作之一,它允許我們將一個文件的內(nèi)容復(fù)制到另一個文件中2023-11-11

