Node.js 根本沒有這樣搞性能優化的?

1、使用最新版本的 Node.js

僅僅是簡單的升級 Node.js 版本就可以輕松地獲得性能提升,因為幾乎任何新版本的 Node.js 都會比老版本性能更好,為什么?

Node.js 每個版本的性能提升主要來自于兩個方面:

  • V8 的版本更新;
  • Node.js 內部代碼的更新優化。

例如最新的 V8 7.1 中,就優化了某些情形下閉包的逃逸分析,讓 Array 的一些方法得到了性能提升:

Node.js 的內部代碼,隨著版本的升級,也會有明顯的優化,比如下面這個圖就是?require?的性能隨著 Node.js 版本升級的變化:

每個提交到 Node.js 的 PR 都會在 review 的時候考慮會不會對當前性能造成衰退。同時也有專門的 benchmarking 團隊來監控性能變化,你可以在這里看到 Node.js 的每個版本的性能變化:

https://benchmarking.nodejs.org/

所以,你可以完全對新版本 Node.js 的性能放心,如果發現了任何在新版本下的性能衰退,歡迎提交一個 issue。

如何選擇 Node.js 的版本?

這里就要科普一下 Node.js 的版本策略:

  • Node.js 的版本主要分為 Current 和 LTS;
  • Current 就是當前最新的、依然處于開發中的 Node.js 版本;
  • LTS 就是穩定的、會長期維護的版本;
  • Node.js 每六個月(每年的四月和十月)會發布一次大版本升級,大版本會帶來一些不兼容的升級;
  • 每年四月發布的版本(版本號為偶數,如 v10)是 LTS 版本,即長期支持的版本,社區會從發布當年的十月開始,繼續維護 18 + 12 個月(Active LTS + Maintaince LTS);
  • 每年十月發布的版本(版本號為奇數,例如現在的 v11)只有 8 個月的維護期。

舉個例子,現在(2018年11月),Node.js Current 的版本是 v11,LTS 版本是 v10 和 v8。更老的 v6 處于 Maintenace LTS,從明年四月起就不再維護了。去年十月發布的 v9 版本在今年六月結束了維護。

對于生產環境而言,Node.js 官方推薦使用最新的 LTS 版本,現在是 v10.13.0。

2、使用 fast-json-stringify 加速 JSON 序列化

在 JavaScript 中,生成 JSON 字符串是非常方便的:

<pre>const json = JSON.stringify(obj)</pre>

但很少人會想到這里竟然也存在性能優化的空間,那就是使用 JSON Schema 來加速序列化。

在 JSON 序列化時,我們需要識別大量的字段類型,比如對于 string 類型,我們就需要在兩邊加上?"?,對于數組類型,我們需要遍歷數組,把每個對象序列化后,用?,?隔開,然后在兩邊加上?[?和?]?,諸如此類等等。

但 如果已經提前通過 Schema 知道每個字段的類型,那么就不需要遍歷、識別字段類型 ,而可以直接用序列化對應的字段,這就大大減少了計算開銷,這就是 fast-json-stringfy 的原理。

根據項目中的跑分,在某些情況下甚至可以比?JSON.stringify?快接近 10 倍!

一個簡單的示例:

<pre>const fastJson = require('fast-json-stringify')
const stringify = fastJson({title: 'Example Schema',type: 'object',properties: {name: { type: 'string' },age: { type: 'integer' },books: {type: 'array',items: {type: 'string',uniqueItems: true}}}
})console.log(stringify({name: 'Starkwang',age: 23,books: ['C++ Primier', '響け!ユーフォニアム~']
}))
//=> {"name":"Starkwang","age":23,"books":["C++ Primier","響け!ユーフォニアム~"]}</pre>

在 Node.js 的中間件業務中,通常會有很多數據使用 JSON 進行,并且這些 JSON 的結構是非常相似的(如果你使用了 TypeScript,更是這樣),這種場景就非常適合使用 JSON Schema 來優化。

3、提升 Promise 的性能

Promise 是解決回調嵌套地獄的靈丹妙藥,特別是當自從 async/await 全面普及之后,它們的組合無疑成為了 JavaScript 異步編程的終極解決方案,現在大量的項目都已經開始使用這種模式。

但是優雅的語法后面也隱藏著性能損耗,我們可以使用 github 上一個已有的跑分項目 進行測試,以下是測試結果:

<pre>file                               time(ms)  memory(MB)
callbacks-baseline.js                   380       70.83
promises-bluebird.js                    554       97.23
promises-bluebird-generator.js          585       97.05
async-bluebird.js                       593      105.43
promises-es2015-util.promisify.js      1203      219.04
promises-es2015-native.js              1257      227.03
async-es2017-native.js                 1312      231.08
async-es2017-util.promisify.js         1550      228.74Platform info:
Darwin 18.0.0 x64
Node.JS 11.1.0
V8 7.0.276.32-node.7
Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz × 4</pre>

我們可以從結果中看到,原生 async/await + Promise 的性能比 callback 要差很多,并且內存占用也高得多。對于大量異步邏輯的中間件項目而言,這里的性能開銷還是不能忽視的。

通過對比可以發現,性能損耗主要來自于 Promise 對象自身的實現,V8 原生實現的 Promise 比 bluebird 這樣第三方實現的 Promise 庫要慢很多。而 async/await 語法并不會帶來太多的性能損失。

所以對于大量異步邏輯、輕量計算的中間件項目而言,可以在代碼中把全局的 Promise 換為 bluebird 的實現:

<pre>global.Promise = require('bluebird');</pre>

4、正確地編寫異步代碼

使用 async/await 之后,項目的異步代碼會非常好看:

<pre>const foo = await doSomethingAsync();
const bar = await doSomethingElseAsync();</pre>

但因此,有時我們也會忘記使用 Promise 給我們帶來的其它能力,比如?Promise.all()?的并行能力:

<pre>// bad
async function getUserInfo(id) {const profile = await getUserProfile(id);const repo = await getUserRepo(id)return { profile, repo }
}// good
async function getUserInfo(id) {const [profile, repo] = await Promise.all([getUserProfile(id),getUserRepo(id)])return { profile, repo }
}</pre>

還有比如?Promise.any()?(此方法不在ES6 Promise標準中,也可以使用標準的?Promise.race()?代替),我們可以用它輕松實現更加可靠快速的調用:

<pre>async function getServiceIP(name) {// 從 DNS 和 ZooKeeper 獲取服務 IP,哪個先成功返回用哪個// 與 Promise.race 不同的是,這里只有當兩個調用都 reject 時,才會拋出錯誤return await Promise.any([getIPFromDNS(name),getIPFromZooKeeper(name)])
}</pre>

5、優化 V8 GC

關于 V8 的垃圾回收機制,已經有很多類似的文章了,這里就不再重復介紹。推薦兩篇文章:

  • 解讀 V8 GC Log(一): Node.js 應用背景與 GC 基礎知識
  • 解讀 V8 GC Log(二): 堆內外內存的劃分與 GC 算法

我們在日常開發代碼的時候,比較容易踩到下面幾個坑:

坑一:使用大對象作為緩存,導致老生代(Old Space)的垃圾回收變慢

示例:

<pre>const cache = {}
async function getUserInfo(id) {if (!cache[id]) {cache[id] = await getUserInfoFromDatabase(id)}return cache[id]
}</pre>

這里我們使用了一個變量?cache?作為緩存,加速用戶信息的查詢,進行了很多次查詢后,?cache?對象會進入老生代,并且會變得無比龐大,而老生代是使用三色標記 + DFS 的方式進行 GC 的,一個大對象會直接導致 GC 花費的時間增長(而且也有內存泄漏的風險)。

解決方法就是:

  • 使用 Redis 這樣的外部緩存,實際上像 Redis 這樣的內存型數據庫非常適合這種場景;
  • 限制本地緩存對象的大小,比如使用 FIFO、TTL 之類的機制來清理對象中的緩存。

坑二:新生代空間不足,導致頻繁 GC

這個坑會比較隱蔽。

Node.js 默認給新生代分配的內存是 64MB(64位的機器,后同),但因為新生代 GC 使用的是 Scavenge 算法,所以實際能使用的內存只有一半,即 32MB。

當業務代碼頻繁地產生大量的小對象時,這個空間很容易就會被占滿,從而觸發 GC。雖然新生代的 GC 比老生代要快得多,但頻繁的 GC 依然會很大地影響性能。極端的情況下,GC 甚至可以占用全部計算時間的 30% 左右。

解決方法就是,在啟動 Node.js 時,修改新生代的內存上限,減少 GC 的次數:

<pre>node --max-semi-space-size=128 app.js</pre>

當然有人肯定會問,新生代的內存是不是越大越好呢?

隨著內存的增大,GC 的次數減少,但每次 GC 所需要的時間也會增加,所以并不是越大越好,具體數值需要對業務進行壓測 profile 才能確定分配多少新生代內存最好。

但一般根據經驗而言,?分配 64MB 或者 128MB 是比較合理的?。

6、正確地使用 Stream

Stream 是 Node.js 最基本的概念之一,Node.js 內部的大部分與 IO 相關的模塊,比如 http、net、fs、repl,都是建立在各種 Stream 之上的。

下面這個經典的例子應該大部分人都知道,對于大文件,我們不需要把它完全讀入內存,而是使用 Stream 流式地把它發送出去:

<pre>const http = require('http');
const fs = require('fs');// bad
http.createServer(function (req, res) {fs.readFile(__dirname + '/data.txt', function (err, data) {res.end(data);});
});// good
http.createServer(function (req, res) {const stream = fs.createReadStream(__dirname + '/data.txt');stream.pipe(res);
});</pre>

在業務代碼中合理地使用 Stream 能很大程度地提升性能,當然是但實際的業務中我們很可能會忽略這一點,比如采用 React 服務器端渲染的項目,我們就可以用?renderToNodeStream?:

<pre>const ReactDOMServer require('react-dom/server')
const http = require('http')
const fs = require('fs')
const app = require('./app')// bad
const server = http.createServer((req, res) => {const body = ReactDOMServer.renderToString(app)res.end(body)
});// good
const server = http.createServer(function (req, res) {const stream = ReactDOMServer.renderToNodeStream(app)stream.pipe(res)
})server.listen(8000)</pre>

使用 pipeline 管理 stream

在過去的 Node.js 中,處理 stream 是非常麻煩的,舉個例子:

<pre>source.pipe(a).pipe(b).pipe(c).pipe(dest)</pre>

一旦其中 source、a、b、c、dest 中,有任何一個 stream 出錯或者關閉,會導致整個管道停止,此時我們需要手工銷毀所有的 stream,在代碼層面這是非常麻煩的。

所以社區出現了 pump 這樣的庫來自動控制 stream 的銷毀。而 Node.js v10.0 加入了一個新的特性: stream.pipeline ,可以替代 pump 幫助我們更好的管理 stream。

一個官方的例子:

<pre>const { pipeline } = require('stream');
const fs = require('fs');
const zlib = require('zlib');pipeline(fs.createReadStream('archive.tar'),zlib.createGzip(),fs.createWriteStream('archive.tar.gz'),(err) => {if (err) {console.error('Pipeline failed', err);} else {console.log('Pipeline succeeded');}}
);</pre>

實現自己的高性能 Stream

在業務中你可能也會自己實現一個 Stream,可讀、可寫、或者雙向流,可以參考文檔:

*   implementing Readable streams
*   implementing Writable streams

Stream 雖然很神奇,但自己實現 Stream 也可能會存在隱藏的性能問題,比如:

<pre>class MyReadable extends Readable {_read(size) {while (null !== (chunk = getNextChunk())) {this.push(chunk);}}
}</pre>

當我們調用?new MyReadable().pipe(xxx)?時,會把?getNextChunk()?所得到的 chunk 都 push 出去,直到讀取結束。但如果此時管道的下一步處理速度較慢,就會導致數據堆積在內存中,導致內存占用變大,GC 速度降低。

而正確的做法應該是,根據?this.push()?返回值選擇正確的行為,當返回值為?false?時,說明此時堆積的 chunk 已經滿了,應該停止讀入。

<pre>class MyReadable extends Readable {_read(size) {while (null !== (chunk = getNextChunk())) {if (!this.push(chunk)) {return false  }}}
}</pre>

這個問題在 Node.js 官方的一篇文章中有詳細的介紹: Backpressuring in Streams

7、C++ 擴展一定比 JavaScript 快嗎?

Node.js 非常適合 IO 密集型的應用,而對于計算密集的業務,很多人都會想到用編寫 C++ Addon 的方式來優化性能。但實際上 C++ 擴展并不是靈丹妙藥,V8 的性能也沒有想象的那么差。

比如,我在今年九月份的時候把 Node.js 的?net.isIPv6()?從 C++ 遷移到了 JS 的實現,讓大多數的測試用例都獲得了 10%- 250% 不等的性能提升( 具體PR可以看這里 )。

JavaScript 在 V8 上跑得比 C++ 擴展還快,這種情況多半發生在與字符串、正則表達式相關的場景,因為 V8 內部使用的正則表達式引擎是 irregexp ,這個正則表達式引擎比 boost 中自帶的引擎(?boost::regex?)要快得多。

還有一處值得注意的就是,Node.js 的 C++ 擴展在進行類型轉換的時候,可能會消耗非常多的性能,如果不注意 C++ 代碼的細節,性能會很大地下降。

這里有一篇文章對比了相同算法下 C++ 和 JS 的性能(需翻墻): How to get a performance boost using Node.js native addons 。其中值得注意的結論就是,C++ 代碼在對參數中的字符串進行轉換后(?String::Utf8Value?轉為?std::string?),性能甚至不如 JS 實現的一半。只有在使用 NAN 提供的類型封裝后,才獲得了比 JS 更高的性能。

換句話說,C++ 是否比 JavaScript 更加高效需要具體問題具體分析,某些情況下,C++ 擴展不一定就會比原生 JavaScript 更高效。如果你對自己的 C++ 水平不是那么有信心,其實還是建議用 JavaScript 來實現,因為 V8 的性能比你想象的要好得多。

8、使用 node-clinic 快速定位性能問題

說了這么多,有沒有什么可以開箱即用,五分鐘見效的呢?當然有。

node-clinic 是 NearForm 開源的一款 Node.js 性能診斷工具,可以非常快速地定位性能問題。

<pre>npm i -g clinic
npm i -g autocannon</pre>

使用的時候,先開啟服務進程:

<pre>clinic doctor -- node server.js</pre>

然后我們可以用任何壓測工具跑一次壓測,比如使用同一個作者的 autocannon (當然你也可以使用 ab、curl 這樣的工具來進行壓測。):

<pre>autocannon http://localhost:3000</pre>

壓測完畢后,我們 ctrl + c 關閉 clinic 開啟的進程,就會自動生成報告。比如下面就是我們一個中間件服務的性能報告:

我們可以從 CPU 的使用曲線看出,這個中間件服務的性能瓶頸不在自身內部的計算,而在于 I/O 速度太慢。clinic 也在上面告訴我們檢測到了潛在的 I/O 問題。

下面我們使用?clinic bubbleprof?來檢測 I/O 問題:

<pre>clinic bubbleprof -- node server.js</pre>

再次進行壓測后,我們得到了新的報告:

這個報告中,我們可以看到,?http.Server?在整個程序運行期間,96% 的時間都處于 pending 狀態,點開后,我們會發現調用棧中存在大量的 empty frame,也就是說,由于網絡 I/O 的限制,CPU 存在大量的空轉,這在中間件業務中非常常見,也為我們指明了優化方向不在服務內部,而在服務器的網關和依賴的服務相應速度上。

想知道如何讀懂?clinic bubbleprof?生成的報告,可以看這里: https://clinicjs.org/bubblepr…

同樣,clinic 也可以檢測到服務內部的計算性能問題,下面我們做一些“破壞”,讓這個服務的性能瓶頸出現在 CPU 計算上。

我們在某個中間件中加入了空轉一億次這樣非常消耗 CPU 的“破壞性”代碼:

<pre>function sleep() {let n = 0while (n++ < 10e7) {empty()}
}
function empty() { }module.exports = (ctx, next) => {sleep()// ......return next()
}</pre>

然后使用?clinic doctor?,重復上面的步驟,生成性能報告:

這就是一個非常典型的?同步計算阻塞了異步隊列?的“病例”,即主線程上進行了大量的計算,導致 JavaScript 的異步回調沒法及時觸發,Event Loop 的延遲極高。

對于這樣的應用,我們可以繼續使用?clinic flame?來確定到底是哪里出現了密集計算:

<pre>clinic flame -- node app.js</pre>

壓測后,我們得到了火焰圖(這里把空轉次數減少到了100萬次,讓火焰圖看起來不至于那么極端):

從這張圖里,我們可以明顯看到頂部的那個大白條,它代表了?sleep?函數空轉所消耗的 CPU 時間。根據這樣的火焰圖,我們可以非常輕松地看出 CPU 資源的消耗情況,從而定位代碼中哪里有密集的計算,找到性能瓶頸。

為了學習工作與休閑娛樂互不沖突,現新建圈【碼農茶水鋪】用于程序員生活,愛好,交友,求職招聘,吐槽等話題交流,希望各位大神工作之余到茶水鋪來喝茶聊天。群號:582735936

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/537258.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/537258.shtml
英文地址,請注明出處:http://en.pswp.cn/news/537258.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

可交付成果、核實的可交付成果、驗收的可交付成果?

①可交付成果。指的是在某一過程、階段或項目完成時&#xff0c;產出的任何獨特并可核實的產品、成果或服務。可交付成果可能是有形的&#xff0c;也可能是無形的。【研發完成】 ②核實的可交付成果。是指已經完成&#xff0c;并經過“控制質量”過程檢查為正確的可交付成果。…

安裝oracle到create inventory時卡住了怎么辦_win10系統安裝教程(官方工具)

Hi&#xff0c;大家好。對于小白用戶&#xff0c;裝系統是比較頭疼的事&#xff0c;所以今天寫一個簡單易懂的裝系統教程。使用微軟官方提供的工具制作U盤啟動盤&#xff0c;操作簡單&#xff0c;系統純凈&#xff0c;強烈建議小白用戶使用。缺點是該工具功能單一&#xff0c;并…

Microsoft Project 排計劃的步驟

Microsoft Project 排計劃的步驟&#xff1a; 第一步&#xff1a;設置項目信息&#xff0c;開始日期&#xff0c;選擇日歷&#xff1b; 第二步&#xff1a;編制WBS 第三步&#xff1a;設置前置任務 第四步&#xff1a;設置WBS每個工期 第五步&#xff1a;設置資源名稱&#xff…

自建CDN Xnign產品指標

Xnign-X1 Xnign-X1 性能參數參考值L7 HTTP RPS &#xff08;128并發請求&#xff09;250W QPSL7 HTTP CPS &#xff08;128并發請求&#xff09;110W QPSL7 HTTP RPS &#xff08;100W并發請求&#xff09;180W QPSL7 HTTP CPS &#xff08;100W并發請求&#xff09;60W QPSL7 …

python表達式的值是 y 和n是什么意思_python中^是什么意思

展開全部在Python" // "表示整數除法。Python其它表達式e69da5e887aa3231313335323631343130323136353331333431353432&#xff1a;Python的表達式寫法與C/C類似。只是在某些寫法有所差別。主要的算術運算符與C/C類似。, -, *, /, //, **, ~, %分別表示加法或者取正、…

Linux的啟動流程簡析(以Debian為例)

Linux的啟動流程簡析(以Debian為例) 正文&#xff1a;前面的文章探討BIOS和主引導記錄的作用。那篇文章不涉及操作系統&#xff0c;只與主板的板載程序有關。今天&#xff0c;我想接著往下寫&#xff0c;探討操作系統接管硬件以后發生的事情&#xff0c;也就是操作系統的啟動流…

一階電路中的時間常數_精確移相電路的設計舉例

移相電路就是對輸入信號(一般是正弦波)進行相位控制&#xff0c;而不改變其幅度&#xff0c;本推文以移相電路為例&#xff0c;展示模擬電路的反饋設計技巧與方法&#xff1a;一、全通濾波器實現移相以上是兩種移相電路 的原理&#xff0c;其輸出幅度保持不變&#xff0c;移動的…

工作績效數據、工作績效信息、工作績效報告

目錄 概念 舉例 概念 工作績效數據&#xff1a;觀察&#xff0c;測量&#xff0c;采集到的原始數據 工作績效信息&#xff1a;對“工作績效數據”進行加工分析 工作績效報告&#xff1a;對“工作績效信息”進行白話 舉例 以去健身房為例 1&#xff09;工作績效數據&…

tickcount()修改成小時分鐘_銀行核心系統24小時機制實現總結

本文共2268字 | 建議閱讀時間&#xff1a;5分鐘作者&#xff1a;張廣在核心系統的設計實現中&#xff0c;24小時機制向來是一個重點難點。早期的銀行只有柜面一個業務辦理渠道&#xff0c;因此當時的綜合業務系統&#xff0c;跟隨網點的營業時間&#xff0c;分為日起&#xff0…

美國將嘗試區塊鏈領域和加密貨幣相結合

首期隱匿性研究綜述有三篇涉及以太坊Casper缺陷問題的論文評論&#xff0c;比特幣通用貨幣理想的內在限制存在兩個問題&#xff1a;以太坊Casper的缺陷問題、比特幣UTXO解決方案的發展。 美國新聞報道&#xff0c;加密貨幣與區塊鏈跨領域研究同儕評論這項一項大膽嘗試&#xff…

python 人氣高的項目_給大家推薦:五個Python小項目,Github上的人氣很高的

deepfake 的深度學習技術&#xff0c;這款工具本來的用途是用來識別和交換圖片、視頻中人物臉部圖像的工具 。該項目有多個入口&#xff0c;你需要做的事&#xff1a;● 收集照片● 從原始照片中提取面部圖像● 在照片上訓練模型● 使用模型轉換源代碼3.神經網絡庫 kerashttps:…

WBS結果輸出表

目錄??????? 概述 模板 案例 擴展閱讀 概述 花了很大力氣分解的WBS&#xff0c;要趁熱打鐵&#xff0c;把每個活動落實到人。 模板 推薦一個模板 1&#xff1a;修改成項目名 2&#xff1a;填寫項目基本信息 3&#xff1a;WBS的最底層工作包 4&#xff1a;工作…

scala:對象object

Scala沒有靜態方法或字段&#xff0c;可以用對象object&#xff08;首字母小寫&#xff09;結構完成類似的功能。 object M {var count 0;def inc {count 1; count}def main(args: Array[String]){println(M.count)M.incprintln(M.count)} }對象的構造器在對象第一次被使用時…

svd奇異值分解_傳統推薦算法(一)SVD推薦(1)解讀奇異值分解

文章目錄寫在前面1. 從幾何變換到奇異值分解2. 代數角度理解奇異值與奇異向量2.1 從正交基映射推導SVD2.2 特征值分解求解奇異值和奇異向量2.2.1 求解過程2.2.2 推論2.3 SVD的另一種形式3. 幾何角度理解奇異值與奇異向量3.1 從坐標變換理解3.1.1 從例子到一般3.1.2 兩個問題3.2…

信息化項目WBS實戰總結

概述 前面花了幾個篇幅講解了WBS&#xff0c;這篇文章總結下實戰要點。 第一篇&#xff1a;項目中的WBS分解 第二篇&#xff1a;項目的可交付成果 第三篇&#xff1a;WBS工作包 第四篇&#xff1a;WBS結果輸出表 劃重點 1.WBS是對“可交付成果”的分解&#xff0c;可交付…

kafka 支持發布訂閱

概述 一般消息隊列的是實現是支持兩種模式的&#xff0c;即點對點&#xff0c;還有一種是topic發布訂閱者模式&#xff0c;比如ACTIVEMQ。KAFKA也支持這兩種模式&#xff0c;但是實現的原理不一樣。 KAFKA 的消息被讀取后&#xff0c;并不是馬上刪除&#xff0c;這樣就可以重復…

svn管理工具_主流代碼管理工具深度評測

引言 作為有十幾年IT行業代碼的從業人員&#xff0c;經歷過代碼管理工具的變遷&#xff0c;從早期的微軟的Source Code Control&#xff0c;到TFS&#xff0c;再到SVN&#xff0c;再到現在的Git。我深知代碼管理工具是代碼開發過程中非常重要的工具。市場上的代碼管理工具有很多…

假設條件和制約因素的理解

目錄 假設條件 制約因素 假設條件和制約因素都記錄在假設日志中。 假設條件 假設條件是指當前不能確定的、未經驗證但仍被視為正確、真實或確定的因素。 假設條件存在不確定性&#xff0c;影響項目規劃的所有方面&#xff1b;項目實施過程中假設條件一旦不成立就可能造成相…

深入理解Java的三種工廠模式

一、簡單工廠模式簡單工廠的定義&#xff1a;提供一個創建對象實例的功能&#xff0c;而無須關心其具體實現。被創建實例的類型可以是接口、抽象類&#xff0c;也可以是具體的類實現汽車接口public interface Car {String getName();}奔馳類public class Benz implements Car {…

項目管理PMBOK中的八大會議

目錄 一、項目啟動會 initiating meeting 二、項目開踢會議 kick-off meeting 三/四&#xff1a;焦點小組會議&引導式研討會 五、規劃會議與分析 六、狀態審查會 七、投標人會議 八、項目經驗總結會 一、項目啟動會 initiating meeting 1、召開時間&#xff1a;是啟…