目錄
為啥要一點一點?
如何才能一點一點?
資料的流轉過程
資料從哪裡來—source
流的種類
暫停模式
背壓問題
什麼是背壓
什麼是背壓處理
如何處理背壓
首頁 web前端 js教程 深入淺析Node中的Stream(流)

深入淺析Node中的Stream(流)

Jan 29, 2023 pm 07:46 PM
前端 node.js

什麼是流?如何理解流?以下這篇文章就帶大家深入了解Nodejs中的串流(Stream),希望對大家有幫助!

深入淺析Node中的Stream(流)

stream 是一個抽象的資料接口,它繼承了EventEmitter,它能夠發送/接受數據,本質就是讓資料流動起來,如下圖:深入淺析Node中的Stream(流)

流不是Node 中獨有的概念,是作業系統最基本的操作方式,在Linux 中| 是Stream,只是Node 層面對其做了封裝,提供了對應的API

為啥要一點一點?

先用下面的程式碼建立一個文件,大概在400MB 左右【相關教學推薦:nodejs影片教學

Untitled 1.png

當我們使用readFile 去讀取的時候,如下程式碼

Untitled 2.png

正常啟動服務時,佔用10MB 左右的記憶體

Untitled 3.png

使用curl http://127.0.0.1:8000發起請求時,記憶體變成了420MB 左右,和我們建立的檔案大小差不多

Untitled 4.png

#改為使用使用stream 的寫法,程式碼如下

Untitled 5.png

再次發起請求時,發現記憶體只佔用了35MB 左右,相較於readFile 大幅減少

Untitled 6.png

如果我們不採用流的模式,等待大檔案載入完成在操作,會有以下的問題:

  • 記憶體暫用過多,導致系統崩潰
  • CPU 運算速度有限制,且服務於多個程序,大文件加載過大且時間久

總結來說就是,一次性讀取大文件,內存和網路都吃不消

如何才能一點一點?

我們讀取檔案的時候,可以採用讀取完成之後在輸出資料

Untitled 7.png

上述說到stream 繼承了EventEmitter 可以是實現監聽數據。首先將讀取資料改為串流讀取,使用on("data", ()⇒{}) 接收數據,最後透過on("end", ()⇒{} ) 最後的結果

Untitled 8.png

有資料傳遞過來的時候就會觸發data 事件,接收這段資料做處理,最後等待所有的資料全部傳遞完成之後觸發end 事件。

資料的流轉過程

資料從哪裡來—source

資料是從一個地方流向另一個地方,先看看數據的來源。

  • http 請求,請求介面來的資料

    Untitled 9.png

  • #console 控制台,標準輸入stdin

    Untitled 10.png

  • file 文件,讀取文件內容,例如上面的範例

##連接的管道— pipe

在source 和dest 中有一個連接的管道pipe,基本語法為

source.pipe(dest) ,source 和dest 透過pipe 連接,讓資料從source 流向dest

我們不需要向上面的程式碼那樣手動監聽data/end 事件.

pipe 使用時有嚴格的要求,source 必須是可讀流,dest 必須是可寫流

??? 流動的資料到底是什麼?程式碼中的 chunk 是什麼?

到哪裡去—dest

stream 常見的三種輸出方式

  • console 控制台,標準輸出stdout

    Untitled 11.png

  • http 請求,介面請求中的response

    Untitled 12.png

  • #file 文件,寫入檔案

    Untitled 13.png

流的種類

Untitled 14.png

#可讀流Readable Streams

可讀流是對提供資料的來源(source)的抽象

所有的Readable 都實作了stream.Readable 類別定義的介面

Untitled 15.png

? 讀取檔案流建立

fs.createReadStream 建立一個Readable 物件

Untitled 16.png

## 讀取模式

  • ##可讀流有兩種模式,
流動模式(flowing mode)

Untitled 17.png暫停模式(pause mode)

,這個決定了chunk 資料的流動方式:自動流動和手工流動

在ReadableStream 中有一個_readableState 屬性,在其中有一個flowing 的屬性來判斷流的模式,他有三種狀態值:
ture:表示為流動模式

false:表示為暫停模式

    null:初始狀態

  • #可以使用熱水器模型來模擬資料的流動。熱水器水箱(buffer 緩存區)儲存著熱水(需要的資料),當我們打開水龍頭的時候,熱水就會從水箱中不斷流出來,自來水也會不斷的流入水箱,這就是流動模式。當我們關閉水龍頭時,水箱會暫停進水,水龍頭會暫停出水,這就是暫停模式。

  • 流動模式
  • 資料會自動地從底層讀取,形成流動現象,並透過事件提供給應用程式。

    Untitled 18.png

  • 監聽data 事件即可進入該模式
當 data 事件被加入後,可寫流中有資料後會將資料推到該事件回呼函數中,需要自己去消費資料塊,如果不處理則該資料會遺失
呼叫 stream.pipe 方法將資料傳送至Writeable

  • 呼叫stream. resume 方法

    Untitled 19.png

暫停模式
  • 資料會堆積在內部緩衝器中,必須明確調用stream.read() 讀取資料塊

  • 監聽readable 事件 可寫流在資料準備好後會觸發此事件回調,此時需要在回呼函數中使用 stream.read() 來主動消費資料。 readable 事件顯示流有新的動態:要麼有新的數據,要麼流已經讀取所有資料

    Untitled 20.png

  • 兩種模式之間如何進行轉換呢

可讀流在建立完成之後處於初始狀態  //TODO:和網路上的分享不一致

暫停模式切換到流動模式

- 监听 data 事件
- 调用 stream.resume 方法
- 调用 stream.pipe 方法将数据发送到 Writable
登入後複製

Untitled 21.png

流動模式切換到暫停模式

- 移除 data 事件
- 调用 stream.pause 方法
- 调用 stream.unpipe 移除管道目标
登入後複製
Untitled 22.png

Untitled 23.png實作原理

  • 建立可讀流的時候,需要繼承Readable 對象,並且實作_read 方法

    ####建立一個自訂可讀流##### ##########當我們呼叫read 方法時,整體的流程如下:###############doRead######流中維護了一個緩存,當呼叫read 方法的時候來判斷是否需要向底層請求資料###

    當快取區長度為0或小於highWaterMark 這個值得時候就會呼叫_read 去底層取得資料原始碼連結

    Untitled 24.png

Untitled 25.png

可寫流Writeable Stream

可寫流是對資料寫入目的地的一種抽象,是用來消費上游流過來的數據,透過可寫流把資料寫入設備,常見的寫入流就是本機磁碟的寫入

  • #可寫入流的特性Untitled 26.png

  • 透過write 寫入資料

    Untitled 27.pngUntitled 28.png

  • #透過end 寫資料並且關閉流,end = write close

    Untitled 29.png

    #當寫入資料達到highWaterMark 的大小時,會觸發drain 事件

呼叫ws.write( chunk) 傳回false,表示目前緩衝區資料大於或等於highWaterMark 的值,就會觸發drain 事件。其實是起到一個警示作用,我們依舊可以寫入數據,只是未處理的數據會一直積壓在可寫流的內部緩衝區

中,直到積壓沾滿Node.js 緩衝區後,才會被強行中斷

自訂可寫流Untitled 30.png

    所有的Writeable 都實作了stream.Writeable 類別定義的介面
  • #只需要實作_write 方法就能夠將資料寫入底層
  • #透過呼叫呼叫writable.write 方法將資料寫入流中,會調用_write 方法將資料寫入底層
  • 當_write 資料成功後,需要呼叫next 方法去處理下一個資料

必須呼叫writable.end(data)來結束可寫流,data 是可選的。此後,不能再呼叫write 新增數據,否則會報錯

在end 方法呼叫後,當所有底層的寫入操作均完成時,會觸發finish 事件

雙工流Duplex StreamUntitled 31.png

雙工流,既可讀,也可寫入。實際上繼承了Readable 和Writable 的一種流,那它既可以當做可讀流來用又可以當做可寫流來用

自訂的雙工流需要實作Readable 的_read 方法和Writable 的_write 方法Untitled 32.png

net 模組可以用來建立socket,socket 在NodeJS 中是一個典型的Duplex,看一個TCP 用戶端的範例

#client 是一個Duplex,可寫流用於向伺服器發送訊息,可讀流用於接受伺服器訊息,兩個流內的資料並沒有直接的關係

轉換流Transform StreamUntitled 33.png

上述的例子中,可讀流中的資料(0/1)和可寫流中的資料('F','B','B')是隔離的,兩者並沒有產生關係,但對於Transform 來說在可寫端寫入的資料經過變換後會自動添加到可讀端。

Transform 繼承於Duplex,並且已經實作了_write 和_read 方法,只需要實作_tranform 方法可以Untitled 34.png

gulp 基於Stream 的自動化建置工具,看一段官網的範例程式碼

###less → less 轉為css → 執行css 壓縮→ 壓縮後的css#######其實less() 和minifyCss() 都是對輸入的資料做了一些處理,然後交給了輸出資料#########Duplex 和Transform 的選擇###

和上面的範例對比起來,我們發現一個串流同時面向生產者和消費者服務的時候我們會選擇Duplex,當只是對資料做一些轉換工作的時候我們便會選擇使用Tranform

背壓問題

什麼是背壓

背壓問題來自於生產者消費者模式中,消費者處理速度過慢

比如說,我們下載過程,處理速度為3Mb/s,而壓縮過程,處理速度為1Mb/s,這樣的話,很快緩衝區隊列就會形成堆積

要麼導致整個過程記憶體消耗增加,要麼導致整個緩衝區慢,部分資料遺失

Untitled 35.png

什麼是背壓處理

#背壓處理可以理解為一個向上」喊話」的過程

當壓縮處理發現自己的緩衝區資料擠壓超過閾值的時候,就對下載處理“喊話”,我忙不過來了,不要再發了

下載處理收到訊息就暫停向下發送資料

Untitled 36.png

如何處理背壓

#我們有不同的函數將資料從一個進程傳入另一個進程。在Node.js 中,有一個內建函數稱為 .pipe(),同樣地最終,在這個進程的基本層面上我們有二個互不相關的元件:資料的_源頭_,和_消費者_

當 .pipe() 被來源呼叫之後,它通知消費者有資料需要傳輸。管道函數為事件觸發建立了合適的積壓封裝

在資料快取超出了highWaterMark 或寫入的列隊處於繁忙狀態,.write() 會返回 false

#當 false 返回之後,積壓系統介入了。它將暫停從任何發送資料的資料流中進入的 Readable。一旦資料流清空了,drain 事件將被觸發,消耗進來的資料流

一旦佇列全部處理完畢,積壓機制將允許資料再次發送。在使用中的記憶體空間將自我釋放,同時準備接收下一次的批次資料

Untitled 37.png

我們可以看到pipe 的背壓處理:

  • #將資料依照chunk劃分,寫入
  • 當chunk過大,或是佇列忙碌時,暫停讀取
  • 當佇列為空時,繼續讀取資料

更多node相關知識,請造訪:nodejs 教學

以上是深入淺析Node中的Stream(流)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

PHP與Vue:完美搭檔的前端開發利器 PHP與Vue:完美搭檔的前端開發利器 Mar 16, 2024 pm 12:09 PM

PHP與Vue:完美搭檔的前端開發利器在當今網路快速發展的時代,前端開發變得愈發重要。隨著使用者對網站和應用的體驗要求越來越高,前端開發人員需要使用更有效率和靈活的工具來創建響應式和互動式的介面。 PHP和Vue.js作為前端開發領域的兩個重要技術,搭配起來可以稱得上是完美的利器。本文將探討PHP和Vue的結合,以及詳細的程式碼範例,幫助讀者更好地理解和應用這兩

如何使用 Go 語言進行前端開發? 如何使用 Go 語言進行前端開發? Jun 10, 2023 pm 05:00 PM

隨著網路技術的發展,前端開發變得日益重要。尤其是行動端設備的普及,更需要高效率、穩定、安全又易於維護的前端開發技術。而作為一門快速發展的程式語言,Go語言已經被越來越多的開發者所使用。那麼,使用Go語言進行前端開發行得通嗎?接下來,本文將為你詳細說明如何使用Go語言進行前端開發。先來看看為什麼要使用Go語言進行前端開發。很多人認為Go語言是一門

C#開發經驗分享:前端與後端協同開發技巧 C#開發經驗分享:前端與後端協同開發技巧 Nov 23, 2023 am 10:13 AM

身為C#開發者,我們的開發工作通常包括前端和後端的開發,而隨著技術的發展和專案的複雜性提高,前端與後端協同開發也變得越來越重要和複雜。本文將分享一些前端與後端協同開發的技巧,以幫助C#開發者更有效率地完成開發工作。確定好介面規範前後端的協同開發離不開API介面的交互。要確保前後端協同開發順利進行,最重要的是定義好介面規格。接口規範涉及到接口的命

Django是前端還是後端?一探究竟! Django是前端還是後端?一探究竟! Jan 19, 2024 am 08:37 AM

Django是一個由Python編寫的web應用框架,它強調快速開發和乾淨方法。儘管Django是web框架,但要回答Django是前端還是後端這個問題,需要深入理解前後端的概念。前端是指使用者直接和互動的介面,後端是指伺服器端的程序,他們透過HTTP協定進行資料的互動。在前端和後端分離的情況下,前後端程式可以獨立開發,分別實現業務邏輯和互動效果,資料的交

前端怎麼實現即時通訊 前端怎麼實現即時通訊 Oct 09, 2023 pm 02:47 PM

實作即時通訊的方法有WebSocket、Long Polling、Server-Sent Events、WebRTC等等。詳細介紹:1、WebSocket,它可以在客戶端和伺服器之間建立持久連接,實現即時的雙向通信,前端可以使用WebSocket API來創建WebSocket連接,並透過發送和接收訊息來實現即時通訊;2、Long Polling,是一種模擬即時通訊的技術等等

Go語言前端技術探秘:前端開發新視野 Go語言前端技術探秘:前端開發新視野 Mar 28, 2024 pm 01:06 PM

Go語言作為一種快速、高效的程式語言,在後端開發領域廣受歡迎。然而,很少有人將Go語言與前端開發聯繫起來。事實上,使用Go語言進行前端開發不僅可以提高效率,還能為開發者帶來全新的視野。本文將探討使用Go語言進行前端開發的可能性,並提供具體的程式碼範例,幫助讀者更了解這一領域。在傳統的前端開發中,通常會使用JavaScript、HTML和CSS來建立使用者介面

前端面試官常問的問題 前端面試官常問的問題 Mar 19, 2024 pm 02:24 PM

在前端開發面試中,常見問題涵蓋廣泛,包括HTML/CSS基礎、JavaScript基礎、框架和函式庫、專案經驗、演算法和資料結構、效能最佳化、跨域請求、前端工程化、設計模式以及新技術和趨勢。面試官的問題旨在評估候選人的技術技能、專案經驗以及對行業趨勢的理解。因此,應試者應充分準備這些方面,以展現自己的能力和專業知識。

Django:前端和後端開發都能搞定的神奇框架! Django:前端和後端開發都能搞定的神奇框架! Jan 19, 2024 am 08:52 AM

Django:前端和後端開發都能搞定的神奇框架! Django是一個高效、可擴展的網路應用程式框架。它能夠支援多種Web開發模式,包括MVC和MTV,可以輕鬆地開發出高品質的Web應用程式。 Django不僅支援後端開發,還能夠快速建構出前端的介面,透過模板語言,實現靈活的視圖展示。 Django把前端開發和後端開發融合成了一種無縫的整合,讓開發人員不必專門學習

See all articles