主頁 > 軟體設計 > 系統性能優化十大絕招

系統性能優化十大絕招

2023-03-14 07:53:10 軟體設計

上篇

 

引言:取與舍

 

軟體設計開發某種意義上是“取”與“舍”的藝術,

 

關于性能方面,就像建筑設計成抗震9度需要額外的成本一樣,高性能軟體系統也意味著更高的實作成本,有時候與其他質量屬性甚至會沖突,比如安全性、可擴展性、可觀測性等等,

 

大部分時候我們需要的是:在業務遇到瓶頸之前,利用常見的技術手段將系統優化到預期水平,

 

那么,性能優化有哪些技術方向和手段呢?

 

性能優化通常是“時間”與“空間”的互換與取舍,

 

本篇分兩個部分,在上篇,講解六種通用的“時間”與“空間”互換取舍的手段:

 

  • 索引術

  • 壓縮術

  • 快取術

  • 預取術

  • 削峰填谷術

  • 批量處理術

 

在下篇,介紹四種進階性的內容,大多與提升并行能力有關:

 

  • 八門遁甲 —— 榨干計算資源

  • 影分身術 —— 水平擴容

  • 奧義 —— 分片術

  • 秘術 —— 無鎖術

 

1、索引術

 

索引的原理是拿額外的存盤空間換取查詢時間,增加了寫入資料的開銷,但使讀取資料的時間復雜度一般從O(n)降低到O(logn)甚至O(1),

 

索引不僅在資料庫中廣泛使用,前后端的開發中也在不知不覺運用,

 

在資料集比較大時,不用索引就像從一本沒有目錄而且內容亂序的新華字典查一個字,得一頁一頁全翻一遍才能找到;

 

用索引之后,就像用拼音先在目錄中先找到要查到字在哪一頁,直接翻過去就行了,

 

書籍的目錄是典型的樹狀結構,那么軟體世界常見的索引有哪些資料結構,分別在什么場景使用呢?

 

  • 哈希表(Hash Table):哈希表的原理可以類比銀行辦業務取號,給每個人一個號(計算出的Hash值),叫某個號直接對應了某個人,索引效率是最高的O(1),消耗的存盤空間也相對更大,K-V存盤組件以及各種編程語言提供的Map/Dict等資料結構,多數底層實作是用的哈希表,

 

  • 二叉搜索樹(Binary Search Tree):有序存盤的二叉樹結構,在編程語言中廣泛使用的紅黑樹屬于二叉搜索樹,確切的說是“不完全平衡的”二叉搜索樹,從C++、Java的TreeSet、TreeMap,到Linux的CPU調度,都能看到紅黑樹的影子,Java的HashMap在發現某個Hash槽的鏈表長度大于8時也會將鏈表升級為紅黑樹,而相比于紅黑樹“更加平衡”的AVL樹反而實際用的更少,

 

  • 平衡多路搜索樹(B-Tree):這里的B指的是Balance而不是Binary,二叉樹在大量資料場景會導致查找深度很深,解決辦法就是變成多叉樹,MongoDB的索參考的就是B-Tree,

 

  • 葉節點相連的平衡多路搜索樹(B+ Tree):B+ Tree是B-Tree的變體,只有葉子節點存資料,葉子與相鄰葉子相連,MySQL的索參考的就是B+樹,Linux的一些檔案系統也使用的B+樹索引inode,其實B+樹還有一種在枝椏上再加鏈表的變體:B*樹,暫時沒想到實際應用,

 

  • 日志結構合并樹(LSM Tree):Log Structured Merge Tree,簡單理解就是像日志一樣順序寫下去,多層多塊的結構,上層寫滿壓縮合并到下層,LSM Tree其實本身是為了優化寫性能犧牲讀性能的資料結構,并不能算是索引,但在大資料存盤和一些NoSQL資料庫中用的很廣泛,因此這里也列進去了,

 

  • 字典樹(Trie Tree):又叫前綴樹,從樹根串到樹葉就是資料本身,因此樹根到枝椏就是前綴,枝椏下面的所有資料都是匹配該前綴的,這種結構能非常方便的做前綴查找或詞頻統計,典型的應用有:自動補全、URL路由,其變體基數樹(Radix Tree)在Nginx的Geo模塊處理子網掩碼前綴用了;Redis的Stream、Cluster等功能的實作也用到了基數樹(Redis中叫Rax),

 

  • 跳表(Skip List):是一種多層結構的有序鏈表,插入一個值時有一定概率“晉升”到上層形成間接的索引,跳表更適合大量并發寫的場景,不存在紅黑樹的再平衡問題,Redis強大的ZSet底層資料結構就是哈希加跳表,

 

  • 倒排索引(Inverted index):這樣翻譯不太直觀,可以叫“關鍵詞索引”,比如書籍末頁列出的術語表就是倒排索引,標識出了每個術語出現在哪些頁,這樣我們要查某個術語在哪用的,從術語表一查,翻到所在的頁數即可,倒排索引在全文索引存盤中經常用到,比如ElasticSearch非常核心的機制就是倒排索引;Prometheus的時序資料庫按標簽查詢也是在用倒排索引,

 

資料庫主鍵之爭:自增長 vs UUID,主鍵是很多資料庫非常重要的索引,尤其是MySQL這樣的RDBMS會經常面臨這個難題:是用自增長的ID還是隨機的UUID做主鍵?

 

自增長ID的性能最高,但不好做分庫分表后的全域唯一ID,自增長的規律可能泄露業務資訊;而UUID不具有可讀性且太占存盤空間,

 

爭執的結果就是找一個兼具二者的優點的折衷方案:

 

用雪花演算法生成分布式環境全域唯一的ID作為業務表主鍵,性能尚可、不那么占存盤、又能保證全域單調遞增,但引入了額外的復雜性,再次體現了取舍之道,

 

再回到資料庫中的索引,建索引要注意哪些點呢?

 

  • 定義好主鍵并盡量使用主鍵,多數資料庫中,主鍵是效率最高的聚簇索引;

 

  • 在Where或Group By、Order By、Join On條件中用到的欄位也要按需建索引或聯合索引,MySQL中搭配explain命令可以查詢DML是否利用了索引;

 

  • 類似列舉值這樣重復度太高的欄位不適合建索引(如果有位圖索引可以建),頻繁更新的列不太適合建索引;

 

  • 單列索引可以根據實際查詢的欄位升級為聯合索引,通過部分冗余達到索引覆寫,以避免回表的開銷;

 

  • 盡量減少索引冗余,比如建A、B、C三個欄位的聯合索引,Where條件查詢A、A and B、A and B and C

 

  • 都可以利用該聯合索引,就無需再給A單獨建索引了;根據資料庫特有的索引特性選擇適合的方案,比如像MongoDB,還可以建自動洗掉資料的TTL索引、不索引空值的稀疏索引、地理位置資訊的Geo索引等等,

 

資料庫之外,在代碼中也能應用索引的思維,比如對于集合中大量資料的查找,使用Set、Map、Tree這樣的資料結構,其實也是在用哈希索引或樹狀索引,比直接遍歷串列或陣列查找的性能高很多,

 

2、快取術

 

快取優化性能的原理和索引一樣,是拿額外的存盤空間換取查詢時間,快取無處不在,設想一下我們在瀏覽器打開這篇文章,會有多少層快取呢?

 

  • 首先決議DNS時,瀏覽器一層DNS快取、作業系統一層DNS快取、DNS服務器鏈上層層快取;

 

  • 發送一個GET請求這篇文章,服務端很可能早已將其快取在KV存盤組件中了;

 

  • 即使沒有擊中快取,資料庫服務器記憶體中也快取了最近查詢的資料;

 

  • 即使沒有擊中資料庫服務器的快取,資料庫從索引檔案中讀取,作業系統已經把熱點檔案的內容放置在Page Cache中了;

 

  • 即使沒有擊中作業系統的檔案快取,直接讀取檔案,大部分固態硬碟或者磁盤本身也自帶快取;

 

  • 資料取到之后服務器用模板引擎渲染出HTML,模板引擎早已決議好快取在服務端記憶體中了;

 

  • 歷經數十毫秒之后,終于服務器回傳了一個渲染后的HTML,瀏覽器端決議DOM樹,發送請求來加載靜態資源;

 

  • 需要加載的靜態資源可能因Cache-Control在瀏覽器本地磁盤和記憶體中已經快取了;

 

  • 即使本地快取到期,也可能因Etag沒變服務器告訴瀏覽器304 Not Modified繼續快取;

 

  • 即使Etag變了,靜態資源服務器也因其他用戶訪問過早已將檔案快取在記憶體中了;

 

  • 加載的JS檔案會丟到JS引擎執行,其中可能涉及的種種快取就不再展開了;

 

  • 整個程序中鏈條上涉及的所有的計算機和網路設備,執行的熱點代碼和資料很可能會載入CPU的多級高速快取,

 

這里列舉的僅僅是一部分常見的快取,就有多種多樣的形式:從廉價的磁盤到昂貴的CPU高速快取,最終目的都是用來換取寶貴的時間,

 

既然快取那么好,那么問題就來了:快取是“銀彈”嗎?

 

不,Phil Karlton 曾說過:

 

There are only two hard things in Computer Science: cache invalidation and naming things.

 

計算機科學中只有兩件困難的事情:快取失效和命名規范,

 

快取的使用除了帶來額外的復雜度以外,還面臨如何處理快取失效的問題,

 

  • 多執行緒并發編程需要用各種手段(比如Java中的synchronized volatile)防止并發更新資料,一部分原因就是防止執行緒本地快取的不一致;

 

  • 快取失效衍生的問題還有:快取穿透、快取擊穿、快取雪崩,解決用不存在的Key來穿透攻擊,需要用空值快取或布隆過濾器;解決單個快取過期后,瞬間被大量惡意查詢擊穿的問題需要做查詢互斥;解決某個時間點大量快取同時過期的雪崩問題需要添加隨機TTL;

 

  • 熱點資料如果是多級快取,在發生修改時需要清除或修改各級快取,這些操作往往不是原子操作,又會涉及各種不一致問題,

 

除了通常意義上的快取外,物件重用的池化技術,也可以看作是一種快取的變體,

 

常見的諸如JVM,V8這類運行時的常量池、資料庫連接池、HTTP連接池、執行緒池、Golang的sync.Pool物件池等等,

 

在需要某個資源時從現有的池子里直接拿一個,稍作修改或直接用于另外的用途,池化重用也是性能優化常見手段,

 

3、壓縮術

 

說完了兩個“空間換時間”的,我們再看一個“時間換空間”的辦法——壓縮,

 

壓縮的原理消耗計算的時間,換一種更緊湊的編碼方式來表示資料,

 

為什么要拿時間換空間?時間不是最寶貴的資源嗎?

 

舉一個視頻網站的例子,如果不對視頻做任何壓縮編碼,因為帶寬有限,巨大的資料量在網路傳輸的耗時會比編碼壓縮的耗時多得多,

 

對資料的壓縮雖然消耗了時間來換取更小的空間存盤,但更小的存盤空間會在另一個維度帶來更大的時間收益,

 

這個例子本質上是“作業系統內核與網路設備處理負擔 vs 壓縮解壓的CPU/GPU負擔”的權衡和取舍,

 

我們在代碼中通常用的是無損壓縮,比如下面這些場景:

 

  • HTTP協議中Accept-Encoding添加Gzip/deflate,服務端對接受壓縮的文本(JS/CSS/HTML)請求做壓縮,大部分圖片格式本身已經是壓縮的無需壓縮;

 

  • HTTP2協議的頭部HPACK壓縮;

 

  • JS/CSS檔案的混淆和壓縮(Uglify/Minify);

 

  • 一些RPC協議和訊息佇列傳輸的訊息中,采用二進制編碼和壓縮(Gzip、Snappy、LZ4等等);

 

  • 快取服務存過大的資料,通常也會事先壓縮一下再存,取的時候解壓;

 

  • 一些大檔案的存盤,或者不常用的歷史資料存盤,采用更高壓縮比的演算法存盤;

 

  • JVM的物件指標壓縮,JVM在32G以下的堆記憶體情況下默認開啟“UseCompressedOops”,用4個byte就可以表示一個物件的指標,這也是JVM盡量不要把堆記憶體設定到32G以上的原因;

 

  • MongoDB的二進制存盤的BSON相對于純文本的JSON也是一種壓縮,或者說更緊湊的編碼,但更緊湊的編碼也意味著更差的可讀性,這一點也是需要取舍的,純文本的JSON比二進制編碼要更占存盤空間但卻是REST API的主流,因為資料交換的場景下的可讀性是非常重要的,

 

資訊論告訴我們,無損壓縮的極限是資訊熵,進一步減小體積只能以損失部分資訊為代價,也就是有損壓縮,

 

圖片

 

那么,有損壓縮有哪些應用呢?

 

  • 預覽和縮略圖,低速網路下視頻降幀、降清晰度,都是對資訊的有損壓縮;

 

  • 音視頻等多媒體資料的采樣和編碼大多是有損的,比如MP3是利用傅里葉變換,有損地存盤音頻檔案;jpeg等圖片編碼也是有損的,雖然有像WAV/PCM這類無損的音頻編碼方式,但多媒體資料的采樣本身就是有損的,相當于只截取了真實世界的極小一部分資料;

 

  • 散列化,比如K-V存盤時Key過長,先對Key執行一次“傻”系列(SHA-1、SHA-256)哈希演算法變成固定長度的短Key,另外,散列化在檔案和資料驗證(MD5、CRC、HMAC)場景用的也非常多,無需耗費大量算力對比完整的資料,

 

除了有損/無損壓縮,但還有一個辦法,就是壓縮的極端——從根本上減少資料或徹底洗掉,

 

能減少的就減少:

 

  • JS打包程序“搖樹”,去掉沒有使用的檔案、函式、變數;

 

  • 開啟HTTP/2和高版本的TLS,減少了Round Trip,節省了TCP連接,自帶大量性能優化;

 

  • 減少不必要的資訊,比如Cookie的數量,去掉不必要的HTTP請求頭;

 

  • 更新采用增量更新,比如HTTP的PATCH,只傳輸變化的屬性而不是整條資料;

 

  • 縮短單行日志的長度、縮短URL、在具有可讀性情況下用短的屬性名等等;

 

  • 使用位圖和位操作,用風騷的位操作最小化存取的資料,典型的例子有:用Redis的位圖來記錄統計海量用戶登錄狀態;布隆過濾器用位圖排除不可能存在的資料;大量開關型的設定的存盤等等,

 

能洗掉的就洗掉:

 

  • 刪掉不用的資料;

  • 刪掉不用的索引;

  • 刪掉不該打的日志;

  • 刪掉不必要的通信代碼,不去發不必要的HTTP、RPC請求或呼叫,輪詢改發布訂閱;

  • 終極方案:砍掉整個功能,

 

畢竟有位叫做 Kelsey Hightower 的大佬曾經說過:

 

No code is the best way to write secure and reliable applications. Write nothing; deploy nowhere

 

不寫代碼,是撰寫安全可靠的應用程式的最佳方式,什么都不寫;哪里都不部署,

 

4、預取術

 

預取通常搭配快取一起用,其原理是在快取空間換時間基礎上更進一步,再加上一次“時間換時間”,也就是:用事先預取的耗時,換取第一次加載的時間,

 

當可以猜測出以后的某個時間很有可能會用到某種資料時,把資料預先取到需要用的地方,能大幅度提升用戶體驗或服務端回應速度,

 

是否用預取模式就像自助餐餐廳與廚師現做的區別,在自助餐餐廳可以直接拿做好的菜品,一般餐廳需要坐下來等菜品現做,

 

那么,預取在哪些實際場景會用呢?

 

  • 視頻或直播類網站,在播放前先緩沖一小段時間,就是預取資料,有的在播放時不僅預取這一條資料,甚至還會預測下一個要看的其他內容,提前把資料取到本地;

 

  • HTTP/2 Server Push,在瀏覽器請求某個資源時,服務器順帶把其他相關的資源一起推回去,HTML/JS/CSS幾乎同時到達瀏覽器端,相當于瀏覽器被動預取了資源;

 

  • 一些客戶端軟體會用常駐行程的形式,提前預取資料或執行一些代碼,這樣可以極大提高第一次使用的打開速度;

 

  • 服務端同樣也會用一些預熱機制,一方面熱點資料預取到記憶體提前形成多級快取;另一方面也是對運行環境的預熱,載入CPU高速快取、熱點函式JIT編譯成機器碼等等;

 

  • 熱點資源提前預分配到各個實體,比如:秒殺、售票的庫存性質的資料;分布式唯一ID等等

 

天上不會掉餡餅,預取也是有副作用的,

 

正如烤箱預熱需要消耗時間和額外的電費,在軟體代碼中做預取/預熱的副作用通常是啟動慢一些、占用一些閑時的計算資源、可能取到的不一定是后面需要的,

 

5、削峰填谷術

 

削峰填谷的原理也是“時間換時間”,谷時換峰時,

 

削峰填谷與預取是反過來的:預取是事先花時間做,削峰填谷是事后花時間做,就像三峽大壩可以抗住短期巨量洪水,事后雨停再慢慢開閘防水,軟體世界的“削峰填谷”是類似的,只是不是用三峽大壩實作,而是用訊息佇列、異步化等方式,

 

常見的有這幾類問題,我們分別來看每種對應的解決方案:

 

  • 針對前端、客戶端的啟動優化或首屏優化:代碼和資料等資源的延時加載、分批加載、后臺異步加載、或按需懶加載等等,

 

  • 背壓控制 - 限流、節流、去抖等等,一夫當關,萬夫莫開,從入口處削峰,防止一些惡意重復請求以及請求過于頻繁的爬蟲,甚至是一些DDoS攻擊,簡單做法有網關層根據單個IP或用戶用漏桶控制請求速率和上限;前端做按鈕的節流去抖防止重復點擊;網路層開啟TCP SYN Cookie防止惡意的SYN洪水攻擊等等,徹底杜絕爬蟲、黑客手段的惡意洪水攻擊是很難的,DDoS這類屬于網路安全范疇了,

 

  • 針對正常的業務請求洪峰,用訊息佇列暫存再異步化處理:常見的后端訊息佇列Kafka、RocketMQ甚至Redis等等都可以做緩沖層,第一層業務處理直接校驗后丟到訊息佇列中,在洪峰過去后慢慢消費訊息佇列中的訊息,執行具體的業務,另外執行程序中的耗時和耗計算資源的操作,也可以丟到訊息佇列或資料庫中,等到谷時處理,

 

  • 捋平毛刺:有時候洪峰不一定來自外界,如果系統內部大量定時任務在同一時間執行,或與業務高峰期重合,很容易在監控中看到“毛刺”——短時間負載極高,一般解決方案就是錯峰執行定時任務,或者分配到其他非核心業務系統中,把“毛刺”攤平,比如很多資料分析型任務都放在業務低谷期去執行,大量定時任務在創建時盡量加一些隨機性來分散執行時間,

 

  • 避免錯誤風暴帶來的次生洪峰:有時候網路抖動或短暫宕機,業務會出現各種例外或錯誤,這時處理不好很容易帶來次生災害,比如:很多代碼都會做錯誤重試,不加控制的大量重試甚至會導致網路抖動恢復后的瞬間,積壓的大量請求再次沖垮整個系統;還有一些代碼沒有做超時、降級等處理,可能導致大量的等待耗盡TCP連接,進而導致整個系統被沖垮,解決之道就是做限定次數、間隔指數級增長的Back-Off重試,設定超時、降級策略,

 

6、批量處理術

 

批量處理同樣可以看成“時間換時間”,其原理是減少了重復的事情,是一種對執行流程的壓縮,以個別批量操作更長的耗時為代價,在整體上換取了更多的時間,

 

批量處理的應用也非常廣泛,我們還是從前端開始講:

 

  • 打包合并的JS檔案、雪碧圖等等,將一批資源集中到一起,一次性傳輸;

 

  • 前端影片使用requestAnimationFrame在UI渲染時批量處理積壓的變化,而不是有變化立刻更新,在游戲開發中也有類似的應用;

 

  • 前后端中使用佇列暫存臨時產生的資料,積壓到一定數量再批量處理;在不影響可擴展性情況下,一個介面傳輸多種需要的資料,減少大量ajax呼叫(GraphQL在這一點就做到了極致);

 

  • 系統間通信盡量發送整批資料,比如訊息佇列的發布訂閱、存取快取服務的資料、RPC呼叫、插入或更新資料庫等等,能批量做盡可能批量做,因為這些系統間通信的I/O時間開銷已經很昂貴了;

 

  • 資料積壓到一定程度再落盤,作業系統本身的寫檔案就是這么做的,Linux的fwrite只是寫入緩沖區暫存,積壓到一定程度再fsync刷盤,在應用層,很多高性能的資料庫和K-V存盤的實作都體現了這一點:一些NoSQL的LSM Tree的第一層就是在記憶體中先積壓到一定大小再往下層合并;Redis的RDB結合AOF的落盤機制;Linux系統呼叫也提供了批量讀寫多個緩沖區檔案的系統呼叫:readv/writev;

 

  • 延遲地批量回收資源,比如JVM的Survivor Space的S0和S1區互換、Redis的Key過期的清除策略,

 

批量處理如此好用,那么問題來了,每一批放多大最合適呢?

 

這個問題其實沒有定論,有一些個人經驗可以分享,

 

  • 前端把所有檔案打包成單個JS,大部分時候并不是最優解,Webpack提供了很多分塊的機制,CSS和JS分開、JS按業務分更小的Chunk結合懶加載、一些體積大又不用在首屏用的第三方庫設定external或單獨分塊,可能整體性能更高,不一定要一批搞定所有事情,分幾個小批次反而用戶體驗的性能更好,

 

  • Redis的MGET、MSET來批量存取資料時,每批大小不宜過大,因為Redis主執行緒只有一個,如果一批太大執行期間會讓其他命令無法回應,經驗上一批50-100個Key性能是不錯的,但最好在真實環境下用真實大小的資料量化度量一下,做Benchmark測驗才能確定一批大小的最優值,

 

  • MySQL、Oracle這類RDBMS,最優的批量Insert的大小也視資料行的特性而定,我之前在2U8G的Oracle上用一些普遍的業務資料做過測驗,批量插入時每批5000-10000條資料性能是最高的,每批過大會導致DML的決議耗時過長,甚至單個SQL陳述句體積超限,單批太多反而得不償失,

 

  • 訊息佇列的發布訂閱,每批的訊息長度盡量控制在1MB以內,有些云服務商提供的訊息佇列限制了最大長度,那這個長度可能就是性能拐點,比如AWS的SQS服務對單條訊息的限制是256KB,

 

總之,多大一批可以確保單批回應時間不太長的同時讓整體性能最高,是需要在實際情況下做基準測驗的,不能一概而論,而批量處理的副作用在于:處理邏輯會更加復雜,尤其是一些涉及事務、并發的問題;需要用陣列或佇列用來存放緩沖一批資料,消耗了額外的存盤空間,

 

中篇

 

引言

 

前面我們總結了六種普適的性能優化方法,包括索引、壓縮、快取、預取、削峰填谷、批量處理,簡單講解了每種技術手段的原理和實際應用,

 

在開啟最后一篇前,我們先需要搞清楚:

 

在程式運行期間,時間和空間都耗在哪里了?

 

時間都去哪兒了?

 

人眨一次眼大約100毫秒,而現代1核CPU在一眨眼的功夫就可以執行數億條指令,

 

現代的CPU已經非常厲害了,頻率已經達到了GHz級別,也就是每秒數十億個指令周期,

 

即使一些CPU指令需要多個時鐘周期,但由于有流水線機制的存在,平均下來大約每個時鐘周期能執行1條指令,比如一個3GHz頻率的CPU核心,每秒大概可以執行20億到40億左右的指令數量,

 

程式運行還需要RAM,也可能用到持久化存盤,網路等等,隨著新的技術和工藝的出現,這些硬體也越來越厲害,比如CPU高速快取的提升、NVMe固態硬碟相對SATA盤讀寫速率和延遲的飛躍等等,這些硬體具體有多強呢?

 

有一個非常棒的網站“Latency Numbers Every Programmer Should Know”,可以直觀地查看從1990年到現在,高速快取、記憶體、硬碟、網路時間開銷的具體數值,

 

https://colin-scott.github.io/personal_website/research/interactive_latency.html

 

下圖是2020年的截圖,的確是“每個開發者應該知道的數字”,

 

圖片

 

圖片

 

這里有幾個非常關鍵的資料:

 

  • 存取一次CPU多級高速快取的時間大約1-10納秒級別;

  • 存取一次主存(RAM)的時間大概在100納秒級別;

  • 固態硬碟的一次隨機讀寫大約在10微秒到1毫秒這個數量級;

  • 網路包在局域網傳輸一個來回大約是0.5毫秒,

 

看到不同硬體之間數量級的差距,就很容易理解性能優化的一些技術手段了,

 

比如一次網路傳輸的時間,是主存訪問的5000倍,明白這點就不難理解寫for回圈發HTTP請求,為什么會被扣工資了,

 

放大到我們容易感知的時間范圍,來理解5000倍的差距:如果一次主存訪問是1天的話,一趟局域網資料傳輸就要13.7年,

 

如果要傳輸更多網路資料,每兩個網路幀之間還有固定的間隔(Interpacket Gap),在間隔期間傳輸Idle信號,資料鏈路層以此來區分兩個資料包,具體數值在鏈接Wiki中有,這里截取幾個我們熟悉的網路來感受一下:

 

  • 百兆以太網: 0.96 μs

  • 千兆以太網:96 ns

  • 萬兆以太網:9.6 ns

 

不過,單純看硬體的上限意義不大,從代碼到機器指令中間有許多層抽象,僅僅是在TCP連接上發一個位元組的資料包,從作業系統內核到網線,涉及到的基礎設施級別的軟硬體不計其數,到了應用層,單次操作耗時雖然沒有非常精確的數字,但經驗上的范圍也值得參考:

 

  • 用Memcached/Redis存取快取資料:1-5 ms

  • 執行一條簡單的資料庫查詢或更新操作:5-50ms

  • 在局域網中的TCP連接上收發一趟資料包:1-10ms;廣域網中大約10-200ms,視傳輸距離和網路節點的設備而定

  • 從用戶態切換到內核態,完成一次系統呼叫:100ns - 1 μs,視不同的系統呼叫函式和硬體水平而定,少數系統呼叫可能遠超此范圍,

 

空間都去哪兒了?

 

在計算機歷史上,非易失存盤技術的發展速度超過了摩爾定律,除了嵌入式設備、資料庫系統等等,現在大部分場景已經不太需要優化持久化存盤的空間占用了,這里主要講的是另一個相對稀缺的存盤形式 —— RAM,或者說主存/記憶體,

 

以JVM為例,在堆里面有很多我們創建的物件(Object),

 

  • 每個Object都有一個包含Mark和型別指標的Header,占12個位元組

  • 每個成員變數,根據資料型別的不同占不同的位元組數,如果是另一個物件,其物件指標占4個位元組

  • 陣列會根據宣告的大小,占用N倍于其型別Size的位元組數

  • 成員變數之間需要對齊到4位元組,每個物件之間需要對齊到8位元組

 

如果在32G以上記憶體的機器上,禁用了物件指標壓縮,物件指標會變成8位元組,包括Header中的Klass指標,這也就不難理解為什么堆記憶體超過32G,JVM的性能直線下降了,

 

舉個例子,一個有8個int型別成員的物件,需要占用48個位元組(12+32+4),如果有十萬個這樣的Object,就需要占用4.58MB的記憶體了,這個數字似乎看起來不大,而實際上一個Java服務的堆記憶體里面,各種各樣的物件占用的記憶體通常比這個數字多得多,大部分記憶體耗在char[]這類陣列或集合型資料型別上,

 

舉個例子,一個有8個int型別成員的物件,需要占用48個位元組(12+32+4),如果有十萬個這樣的Object,就需要占用4.58MB的記憶體了,這個數字似乎看起來不大,而實際上一個Java服務的堆記憶體里面,各種各樣的物件占用的記憶體通常比這個數字多得多,大部分記憶體耗在char[]這類陣列或集合型資料型別上,

 

堆記憶體之外,又是另一個世界了,

 

從作業系統行程的角度去看,也有不少耗記憶體的大戶,不管什么Runtime都逃不開這些空間開銷:每個執行緒需要分配MB級別的執行緒堆疊,運行的程式和資料會快取下來,用到的輸入輸出設備需要緩沖區……

 

代碼“寫出來”的記憶體占用,僅僅是冰山之上的部分,真正的記憶體占用比“寫出來”的要更多,到處都存在空間利用率的問題,

 

比如,即使我們在Java代碼中只是寫了 response.getWriter().print(“OK”),給瀏覽器回傳2位元組,網路協議堆疊的層層封裝,協議頭部不斷增加的額外資料,讓最侄訓傳給瀏覽器的位元組數遠超原始的2位元組,像IP協議的報頭部就至少有20個位元組,而資料鏈路層的一個以太網幀頭部至少有18位元組,

 

如果傳輸的資料過大,各層協議還有最大傳輸單元MTU的限制,IPv4一個報文最大只能有64K位元,超過此值需要分拆發送并在接收端組合,更多額外的報頭導致空間利用率降低(IPv6則提供了Jumbogram機制,最大單包4G位元,“浪費”就減少了),

 

這部分的“浪費”有多大呢?下面的鏈接有個表格,傳輸1460個位元組的載荷,經過有線到無線網路的轉換,至少再添120個位元組,**空間利用率<92.4%**,

 

https://en.wikipedia.org/wiki/Jumbo_frame

 

這種現象非常普遍,使用抽象層級越高的技術平臺,平臺提供高級能力的同時,其底層實作的“資訊密度”通常越低,

 

像Java的Object Header就是使用JVM的代價,而更進一步使用動態型別語言,要為靈活性付出空間的代價則更大,哈希表的自動擴容,強大的反射能力等等,背后也付出了空間的代價,

 

再比如,二進制資料交換協議通常比純文本協議更加節約空間,但多數廠家我們仍然用JSON、XML等純文本協議,用資訊的冗余來換取可讀性,即便是二進制的資料互動格式,也會存在資訊冗余,只能通過更好的協議和壓縮演算法,盡量去逼近壓縮的極限 —— 資訊熵,

 

小結

 

理解了時間和空間的消耗在哪后,還不能完全解釋軟體為何傾向于耗盡硬體資源,有一條定律可以解釋,正是它錘爆了摩爾定律,

 

它就是安迪-比爾定律,

 

“安迪給什么,比爾拿走什么”,

 

安迪指的是Intel前CEO安迪·葛洛夫,比爾指的是比爾·蓋茨,

 

這句話的意思就是:軟體發展比硬體還快,總能吃得下硬體,

 

20年前,在最強的計算機也不見得可以玩賽車游戲;

 

10年前,個人電腦已經可以玩畫質還可以的3D賽車游戲了;

 

現在,自動駕駛+5G云駕駛已經快成為現實,

 

在這背后,是無數的硬體技術飛躍,以及吃掉了這些硬體的各類軟體,

 

圖片

 

因此,即使現代的硬體水平已經強悍到如此境地,性能優化仍然是有必要的,

 

軟體日益復雜,抽象層級越來越高,就越需要底層基礎設施被充分優化,

 

對于大部分開發者而言,高層代碼逐步走向低代碼化、可視化,“一行代碼”能產生的影響也越來越大,寫出低效代碼則會吃掉更多的硬體資源,

 

下篇

 

引言

 

本篇也是本系列最硬核的一篇,本人技術水平有限,可能存在疏漏或錯誤之處,望斧正,仍然選取了《火影忍者》的配圖和命名方式幫助理解:

 

  • 八門遁甲 —— 榨干計算資源

  • 影分身術 —— 水平擴容

  • 奧義 —— 分片術

  • 秘術 —— 無鎖術

 

(注:這些“中二”的前綴僅是用《火影》中的一些術語,形象地描述技術方案)

 

7、八門遁甲 —— 榨干計算資源

 

讓硬體資源都在處理真正有用的邏輯計算,而不是做無關的事情或空轉,

 

從晶體管到集成電路、驅動程式、作業系統、直到高級編程語言的層層抽象,每一層抽象帶來的更強的通用性、更高的開發效率,多是以損失運行效率為代價的,

 

但我們可以在用高級編程語言寫代碼的時候,在保障可讀性、可維護性基礎上用運行效率更高、更適合運行時環境的方式去寫,減少額外的性能損耗《Effective XXX》、《More Effective XXX》、《高性能XXX》這類書籍所傳遞的知識和思想,

 

落到技術細節,下面用四個小節來說明如何減少“無用功”、避免空轉、榨干硬體,

 

1)聚焦

 

減少系統呼叫與背景關系切換,讓CPU聚焦,

 

可以看看兩個 stackoverflow 上的帖子:

 

https://stackoverflow.com/questions/21887797/what-is-the-overhead-of-a-context-switch

https://stackoverflow.com/questions/23599074/system-calls-overhead

 

大部分互聯網應用服務,耗時的部分不是計算,而是I/O,

 

減少I/O wait, 各司其職,專心干I/O,專心干計算,epoll批量撈任務,(refer: event driven)

 

利用DMA減少CPU負擔 - 零拷貝 NewI/O Redis SingleThread (even 6.0), Node.js

 

避免不必要的調度 - Context Switch

 

CPU親和性,讓CPU更加聚焦

 

2)蛻變

 

用更高效的資料結構、演算法、第三方組件,讓程式本身蛻變,

 

從邏輯短路、Map代替List遍歷、減少鎖范圍、這樣的編碼技巧,到應用FisherYates、Dijkstra這些經典演算法,注意每一行代碼細節,量變會發生質變,更何況某個演算法就足以讓系統性能產生一兩個數量級的提升,

 

3)適應

 

因地制宜,適應特定的運行環境

 

在瀏覽器中主要是優化方向是I/O、UI渲染引擎、JS執行引擎三個方面,

 

I/O越少越好,能用WebSocket的地方就不用Ajax,能用Ajax的地方就不要刷整個頁面;

 

UI渲染方面,減少重排和重繪,比如Vue、React等MVVM框架的虛擬DOM用額外的計算換取最精簡的DOM操作;

 

JS執行引擎方面,少用動態性極高的寫法,比如eval、隨意修改物件或物件原型的屬性,

 

前端的優化有個神器:Light House,在新版本Chrome已經嵌到開發者工具中了,可以一鍵生成性能優化報告,按照優化建議改就完了,

 

與瀏覽器環境頗為相似的Node.js環境:

 

https://segmentfault.com/a/1190000007621011#articleHeader11

 

Java

 

  • C1 C2 JIT編譯器

  • 堆疊上分配

 

Linux

 

  • 各種引數優化

  • 記憶體分配和GC策略

  • Linux內核引數 Brendan Gregg

  • 記憶體區塊配置(DB,JVM,V8,etc.)

 

利用語言特性和運行時環境 - 比如寫出利于JIT的代碼

 

  • 多靜態少動態 - 舍棄動態特性的靈活性 - hardcode/if-else,強型別,弱型別語言避免型別轉換 AOT/JIT vs 解釋器, 匯編,機器碼 GraalVM

 

減少記憶體的分配和回收,少對串列做增加或洗掉

 

對于RAM有限的嵌入式環境,有時候時間不是問題,反而要拿時間換空間,以節約RAM的使用,

 

4)運籌

 

把眼界放寬,跳出程式和運行環境本身,從整體上進行系統性分析最高性價比的優化方案,分析潛在的優化切入點,以及能夠調配的資源和技術,運籌帷幄,

 

其中最簡單易行的幾個辦法,就是花錢,買更好或更多的硬體基礎設施,這往往是開發人員容易忽視的,這里提供一些妙招:

 

  • 服務器方面,云服務廠商提供各種型別的實體,每種型別有不同的屬性側重,帶寬、CP、磁盤的I/O能力,選適合的而不是更貴的

 

  • 舍棄虛擬機 - Bare Mental,比如神龍服務器

 

  • 用ARM架構CPU的服務器,同等價格可以買到更多的服務器,對于多數可以跨平臺運行的服務端系統來說與x86區別并不大,ARM服務器的資料中心也是技術發展趨勢使然

 

  • 如果必須用x86系列的服務器,AMD也Intel的性價比更高,

 

第一點非常重要,軟體性能遵循木桶原理,一定要找到瓶頸在哪個硬體資源,把錢花在刀刃上,

 

如果是服務端帶寬瓶頸導致的性能問題,升級再多核CPU也是沒有用的,

 

我有一次性能優化案例:把一個跑復雜業務的Node.js服務器從AWS的m4型別換成c4型別,記憶體只有原來的一半,但CPU使用率反而下降了20%,同時價格還比之前更便宜,一石二鳥,

 

這是因為Node.js主執行緒的計算任務只有一個CPU核心在干,通過CPU Profile的火焰圖,可以定位到該業務的瓶頸在主執行緒的計算任務上,因此提高單核頻率的作用是立竿見影的,而該業務對記憶體的消耗并不多,套用一些定制v8引擎記憶體引數的方案,起不了任何作用,

 

畢竟這樣的例子不多,大部分時候還是要多花錢買更高配的服務器的,除了這潭訓錢能直接解決問題的辦法,剩下的辦法難度就大了:

 

  • 利用更底層的特性實作功能,比如FFI WebAssembly呼叫其他語言,Java Agent Instrument,位元組碼生成(BeanCopier, Json Lib),甚至匯編等等

  • 使用硬體提供的更高效的指令

  • 各種提升TLB命中率的機制,減少記憶體的大頁表

  • 魔改Runtime,Facebook的PHP,阿里騰訊定制的JDK

  • 網路設備引數,MTU

  • 專用硬體:GPU加速(cuda)、AES硬體卡和高級指令加速加解密程序,比如TLS

  • 可編程硬體:地獄級難度,FPGA硬體設備加速特定業務

  • NUMA

  • 更宏觀的調度,VM層面的共享vCPU,K8S集群調度,總體上的優化

 

5)小結

 

有些手段,是憑慷訓出來更多的空間和時間了嗎?

 

天下沒有免費的午餐,即使那些看起來空手套白狼的優化技術,也需要額外的人力成本來做,副作用可能就是專家級的發際線吧,還好很多復雜的性能優化技術我也不會,所以我本人發際線還可以,

 

這一小節總結了一些方向,有些技術細節非常深,這里也無力展開,不過,即使榨干了單機性能,也可能不足以支撐業務,這時候就需要分布式集群出場了,因此后面介紹的3個技術方向,都與并行化有關,

 

8、影分身術 —— 水平擴容

 

本節的水平擴容以及下面一節的分片,可以算整體的性能提升而不是單點的性能優化,會因為引入額外組件反而降低了處理單個請求的性能,

 

但當業務規模大到一定程度時,再好的單機硬體也無法承受流量的洪峰,就得水平擴容了,畢竟”眾人拾柴火焰高”,

 

在這背后的理論基礎是,硅基半導體已經接近物理極限,隨著摩爾定律的減弱,阿姆達爾定律的作用顯現出來:

 

https://en.wikipedia.org/wiki/Amdahl%27s_law

 

水平擴容必然引入負載均衡

 

  • 多副本

  • 水平擴容的前提是無狀態

  • 讀>>寫, 多個讀實體副本 (CDN)

  • 自動擴縮容,根據常用的或自定義的metrics,判定擴縮容的條件,或根據CRON

  • 負載均衡策略的選擇

 

9、奧義 —— 分片術

 

水平擴容針對無狀態組件,分片針對有狀態組件,二者原理都是提升并行度,但分片的難度更大,

 

負載均衡也不再是簡單的加權輪詢了,而是進化成了各個分片的協調器

 

  • Java1.7的及之前的 ConcurrentHashMap分段鎖

  • 有狀態資料的分片

  • 如何選擇Partition/Sharding Key

  • 負載均衡難題

  • 熱點資料,增強快取等級,解決分散的快取帶來的一致性難題

  • 資料冷熱分離,SSD - HDD

  • 分開容易合并難

  • 區塊鏈的優化,磁區域

 

10、秘術 —— 無鎖術

 

有些業務場景,比如庫存業務,按照正常的邏輯去實作,水平擴容帶來的提升非常有限,因為需要鎖住庫存,扣減,再解鎖庫存,

 

票務系統也類似,為了避免超賣,需要有一把鎖禁錮了橫向擴展的能力,

 

不管是單機還是分布式微服務,鎖都是制約并行度的一大因素,比如上篇提到的秒殺場景,庫存就那么多,系統超賣了可能導致非常大的經濟損失,但用分布式鎖會導致即使服務擴容了成千上萬個實體,最終無數請求仍然阻塞在分布式鎖這個串行組件上了,再多水平擴展的實體也無用武之地,

 

避免競爭Race Condition 是最完美的解決辦法,

 

上篇說的應對秒殺場景,預取庫存就是減輕競態條件的例子,雖然取到服務器記憶體之后仍然有多執行緒的鎖,但鎖的粒度更細了,并發度也就提高了,

 

  • 執行緒同步鎖

  • 分布式鎖

  • 資料庫鎖 update select子句

  • 事務鎖

  • 順序與亂序

  • 樂觀鎖/無鎖 CAS Java 1.8之后的ConcurrentHashMap

  • pipeline技術 - CPU流水線 Redis Pipeline 大資料分析 并行計算

  • TCP的緩沖區排頭阻塞 QUIC HTTP3.0

 

總結

 

以ROI的視角看軟體開發,初期人力成本的投入,后期的維護成本,計算資源的費用等等,選一個合適的方案而不是一個性能最高的方案,

 

本篇結合個人經驗總結了常見的性能優化手段,這些手段只是冰山一角,在初期就設計實作出一個完美的高性能系統是不可能的,隨著軟體的迭代和體量的增大,利用壓測,各種工具(profiling,vmstat,iostat,netstat),以及監控手段,逐步找到系統的瓶頸,因地制宜地選擇優化手段才是正道,

 

有利必有弊,得到一些必然會失去一些,有一些手段要慎用,Linux性能優化大師Brendan Gregg一再強調的就是:切忌過早優化、過度優化,

 

持續觀測,做80%高投入產出比的優化,

 

除了這些設計和實作時可能用到的手段,在技術選型時選擇高性能的框架和組件也非常重要,

 

另外,部署基礎設施的硬體性能也同樣,合適的服務器和網路等基礎設施往往會事半功倍,比如云服務廠商提供的各種字母開頭的instance,網路設備帶寬的速度和穩定性,磁盤的I/O能力等等,

 

多數時候我們應當使用更高性能的方案,但有時候甚至要故意去違背它們,最后,以《Effective Java》第一章的一句話結束本文吧,

 

首先要學會基本的規則,然后才能知道什么時候可以打破規則,

 

作者丨Joey Yang
來源丨網址:https://code2life.top/2020/08/15/0055-performance/

本文來自博客園,作者:古道輕風,轉載請注明原文鏈接:https://www.cnblogs.com/88223100/p/Top-Ten-Ways-to-Optimize-System-Performance.html

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/546747.html

標籤:其他

上一篇:【建造者設計模式詳解】Java/JS/Go/Python/TS不同語言實作

下一篇:【建造者設計模式詳解】Java/JS/Go/Python/TS不同語言實作

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more