天天看點

Node.js自學筆記

Node.JS學習筆記 REPL:Read-Evaluate-Print-Loop 輸入- 求值 - 輸出- 循環 即互動式指令行解析器

一、事件循環機制,編寫健壯的node程式

node 與js一樣都是單線程方式運作,即同一時間隻能處理一件事情;就如同郵差送信,每封信都是一個事件,他有一堆信需要按照順序去送,每封信都會通過相應的路徑進行投遞。路徑就是對應事件的回調函數(通常不止一條路徑)。 我們從簡單的情形入手想,對比一下郵差的行為和一般程式的做法。假設web伺服器(HTTP)被請求要從資料庫中讀取一些資料,然後傳回給使用者。在這種情況下,我們隻要處理很少的事件。首先,使用者的請求多是要Web伺服器傳回一個網頁。處理這個初始請求的回調函數(回調函數A)會現從請求的對象中确定它要從資料庫讀取什麼内容,然後向資料庫發起具體的請求,并傳入一個函數(回調函數B)供請求完成時使用。處理完請求後,回調函數A結束并傳回當資料庫找到需要的内容後,在觸發相應的事件。事件循環隊列則調用回調函數B,讓他把資料發送給使用者。

1、 為什麼說Node更加高效?

以往如PHP等Web平台所使用的方法。就類似于在快餐店點餐服務員先招待你,待你點完後才服務下一個客人,他輸入了你的單子後,可以收款,為你倒飲料等。但是他還不知道廚房要多久才能把你的漢堡做好。在傳統的Web服務架構下,每個服務程式(線程),每次隻能服務一個請求。唯一增加處理能力的方法就是加入更多的線程。很顯然這樣的做法并不是那麼的高效,服務員在等待廚房做菜時浪費了很多時間。而Node使用的方式是類似于餐館的更加高效的模式。你點完餐後,服務員會給你一個号碼,在菜做好後通知你,你可以稱之為回調号碼。需要注意的是與郵差的例子一樣,Node絕不會同時處理兩個事件。 Node.js程式本身需要把每一個回調函數都寫得運作迅速,防止把事件循環給堵塞住。 這意味着在編寫Node.js伺服器程式的時候需要遵循以下兩個政策:

  • ①在設定完成以後,所有的操作都是事件驅動的。
  • ②如果Node.js需要長時間處理資料,就需要考慮把它配置設定給web worker去處理。

事件驅動方法配合事件循環工作起來非常高效,但編寫容易閱讀和了解的事件驅動代碼也同樣重要。如果我們使用匿名函數作為事件回調,這會導緻幾點不便。

  • ①我們無法控制代碼在哪裡使用。
  • ②匿名函數隻有在被使用的地方才存活,而不是在綁定事件回調時存活,這回影響調試。
  • ③如果所有的東西都是匿名事件,當異常發生時,就很難分辨出是哪個回調函數導緻了問題。
2、模式

①無序的并行I/O

在Node.js中最好實作的。事實上,Node中所有的I/O操作預設都是無序并行的,因為Node的所有I/O都是異步非阻塞的。我們操作I/O時,隻要扔出請求然後等待結果就行了。所有的請求可能按我們操作的順序執行,也可能不是。我們指的無序,并不是指亂序,而是指順序沒有保證。

例:

簡單地調用I/O請求并制定回調函數就會建立無序并行I/O操作。在未來的某一時刻,所有的這些回調函數都會被處罰,但哪一個先被觸發是未知的。而且如果某一個請求傳回了錯誤而非資料,也不會影響其他請求。 ②順序串行I/O 在這個模式裡,我們希望按順序執行一些I/O任務。每一個任務都必須在上一個任務完成後才能開始。在Node裡,這意味着使用嵌套回調,這樣可以在每個人物的回調函數裡發起下一個任務。

例:

server.on('request', function (req, res) {
    //從memcached裡擷取session資訊 memcached.getSession(req, function (session) { //從db擷取資訊 db.get(session.user, function (userData) { //其他的Web服務調用 ws.get(req, function (wsData) { //渲染頁面 page = pageRender(req,session,userData,wsData); //輸出相應内容 res.write(page); }) }) }) })
           

雖然嵌入回調函數很容易穿件順序串行的I/O,但它的代碼看起來很像“金字塔”。這樣的代碼很難閱讀和了解,也難以維護。比如,掃一眼上述執行個體并不能看清楚memcached.getSession請求完成後發起db.get請求,等db.get完成後由發起ws.get請求,等等。要讓代碼可讀又不會破壞順序串行模式,有幾種方法。 第一,我們可以繼續使用内聯函數聲明,彈藥給它們增加名字,這樣容易調試,而且還表明了該回調函數的目的。

例:

server.on('request', getMemCached (req, res) {
    //從memcached裡擷取session資訊
    memcached.getSession(req, getDbInfo (session) {  
        //從db擷取資訊  
        db.get(session.user, getWsInfo (userData) { //其他的Web服務調用 ws.get(req, render (wsData) { //渲染頁面 page = pageRender(req,session,userData,wsData); //輸出相應内容 res.write(page); }) }) }) })
           

另一種方法需要改變代碼風格,用提前聲明的函數代替匿名函數灬命名函數。這回把金字塔拆散,改為按執行順序展示,并且代碼呗拆分成更加可控的小塊。如下例

例:

var render = function (wsData) {
    page = pageRender(req,session,userData,wsData);
}
var getWsInfo = function (userData) { ws.get(req, render) } var getDbInfo = function (session) { db.get(session.user, getWsInfo) } var getMemCached = function (req, res) { memcached.getSession(req,getDbInfo) }
           

同樣也可以采用展開的重構方法,但需要建立一個把原始請求都包含的共享作用域,用一個閉包把所有的回調函數都包含進去。這樣,所有與初始請求相關的回調函數都被封裝起來,并通過閉包内的變量共享狀态。如下例:

server.on('request',function(req,res){
    var render = function(wsData){ page = pageRender(req,session,userData,wsData); }; var getWsInfo = function(userData){ ws.get(req,render); }; var getDbInfo = function(ssession){ db.get(session.user,getWsInfo); } var getMemCached = function(req,res){ memcached.getSession(req,getDbInfo); } })
           

采用這樣的方法,不但代碼組織更有邏輯性,而且利用展開的方法避免了多層嵌套的困繞。

在JavaScript中,對象是以引用的方式傳遞。意思是,當你調用某個Function(someobject)時,對someobject的任何修改,都會影響你目前函數作用域中所有對someobject的引用。這樣會存在潛在的風險,因為回調函數使用的對象被别人修改了,它難以确定是什麼時候修改的,因為運作的次序是非線性的。

一般簡單的做法就是,用某個東西來表示狀态,然後把它在所有需要依賴此狀态的函數間傳遞。這就需要所有依賴此狀态的函數通過統一的接口來互相傳遞。這也是Connect(以及Express)中間件的形式都是function(req,res,next)的原因。

在函數中傳遞修改後的内容,代碼如下:

var AwesomeClass = function(){
    this.awesomeProp = 'awesome!'; this.awesomeFunc = function(text){ console.log(text + ' is awesome!') } } var awesomeObject = new AwesomeClass() function middleware(func){ oldFunc = func.awesomeFunc; func.awesomeFunc = function(text){ text = text + 'really'; oldFunc(text) } } function anotherMiddleware(func){ func.anotherProp = 'super duper' } function caller(input){ input.awesomeFunc(input.anotherProp) } middleware(awesomeObiect); anotherMiddleware(awesomeObject); caller(awesomeObject)
           

3、編寫産品代碼

Node現在有一些限制,比如規定了最大的JavaScript堆棧的大小。這會影響你的部署方式,因為在使用Node的易程式設計,單線程模型來部署時,需要考慮如何充分的利用機器的CPU和記憶體。

(1)差錯處理

JavaScript包含了try/catch功能,但這個方法隻有當錯誤發生在内鍊位置時才有用。使用Node的非阻塞I/O時,你給函數傳遞了一個回調函數,這意味着回調函數被時間出發調用時,是不在try/catch代碼塊中的。我們需要為異步運作情景提供差錯處理的方法。

在Node中,我們利用error事件來處理此問題。這是一個特殊的事件,當錯誤發生時他就會出發。這讓參與I/O的子產品出發另外一個事件給負責處理錯誤的回調函數。error事件讓我們能夠處理所有使用的子產品中可能出現的問題。

var http = require('http');
var opts = {
    host:'jdsaflkjgdfahjrh.net', port:
           
(2)使用多處理器

Node是單線程的,是以隻能利用一個處理器來工作,但是多數伺服器都有“多核”處理器,一個多核處理器就包含了幾個處理器。要充分的發揮Node的作用,需要把這些處理器都利用起來。

Node提供了一個cluster子產品,可以把任務配置設定給自己成,就是說Node把目前程序複制了一份給另一個程序(在Windows上,它其實是另外一個線程)。通過clusterAPI,就可以把工作配置設定給Node程序,并分布在伺服器所有可用的處理器上,這能充分的利用資源。如下例:

var cluster = require('cluster');
var http = require('http'); var numCPUs = require('os').cpus().length; if(cluster.isMaster){ //建立工作程序 for (var i = 
           

在上面例子中,我們使用了Node的一些核心子產品來把工作平均配置設定給所有可用的CPU。其中從os子產品中,我們可以輕松得到系統CPU的數量。

cluster工作的原理是每一個Node程序要麼是主程序,要麼成為工作程序,當一個主程序調用cluster。fork()方法時,它會建立與主程序一模一樣的子程序,除了兩個讓每個程序可以自己檢查自己是父/子程序的屬性以外。在主程序中(Node運作時直接調用的那個腳本),cluster.isMaster會傳回true,而cluster.isWorker會傳回false。而在子程序,cluster.isMaster傳回false,且cluster.isWorker傳回true。

通過上面的那個例子我們可以知道,主腳本為每個CPU建立了一個工作程序。每個紫禁城建立了一個HTTP伺服器,這是cluster另一個獨特的地方。在使用cluster的迪凡使用listen()監聽一個socket的時候,多個程序可以同時監聽同一個socket。如果通過調用nodemyscript.js的方法氣動多個Node程序,會導緻出錯。cluster提供了跨平台時讓多個程序共享socket的方法。即使多個子程序在共享一個端口上的連結,其中一個堵塞了,也不會影響其他工作程序的新連接配接。

cluster還可以檢查子程序的健康狀态,當子程序死亡時主程序會用console.log()輸出死亡提醒。

//出現死亡程序後重新開啟新的程序
if(cluster.isMaster){
    //建立工作程序
    for (var i = 
           

這個簡單的改造讓主程序會不停的把死掉的程序重新開機,進而保證所有的CPU都有我們的伺服器在運作。

然而這隻是對運作狀态的基本檢查。由于工作程序可以傳遞消息給主程序,是以可能讓每個工作程序報告自己的狀态,如記憶體使用量。這讓主程序可以察覺哪些工作程序變得不穩定,确認哪些工作程序沒有當機,或者長時間運作的事件堵塞。

//通過消息傳遞來監控工作程序狀态
var cluster = require('cluster');
var http = require('http'); var numCPUs = require('os').cpus().length; var rssWarn = (
           

在這個例子裡,工作程序報告自己的記憶體使用量,當子程序使用了過多的記憶體時,主程序會發送一條警告到日志中去。

如果我們識别了一個長時間運作的回調函數,我們也沒有辦法主動關閉它。因為我們發送給該程序的任何通知都會加到事件隊列裡,是以他需要等待已經在長時間運作的回調函數結束後才會被處理。是以,雖然我們能夠讓主程序識别僵屍程序,機關一的補救方法就是殺掉這個工作程序,而這會丢失他正在執行的工作。

//殺死僵屍程序
var cluster = require('cluster');
var http = require('http'); var numCPUs = require('os').cpus().length; var rssWarn = (
           

轉載于:https://www.cnblogs.com/liuyangdiv/p/6425802.html