盤一盤那些高效能設計的點(一)

2023-07-04 12:01:28

狹義地講,效能是指軟體在儘可能少地佔用系統資源的前提下,儘可能高地提高執行速度。

談及效能,我們的關注點不再是軟體或者系統的功能,而是在其實現功能過程中所表現出來的資源效率。

一、池化思想

什麼是池化?

簡單的說就是設定一個公共物件池,對於其中的物件直接複用而不再使用新建立的方式。

1、JDK 的包裝型別值快取池

Integer::IntegerCache 整形包裝類快取

用於 [-128, 127] 之間數位裝箱操作使用。最大值可以通過 "java.lang.Integer.IntegerCache.high" 設定。

第一次使用的時候初始化,其大小可以通過 -XX:AutoBoxCacheMax= 進行設定。

Character::CharacterCache

快取大小為 size = 127,即儲存 [0, 127] 值域的 char 字元。

Long::LongCache

快取大小 size = -(-128) + 127,即儲存 [-128, 127] 值域的 long 值。

Byte::ByteCache

快取大小 size = -(-128) + 127,即儲存 [-128, 127] 值域的 byte 值。

Short::ShortCache

快取大小 size = -(-128) + 127,即儲存 [-128, 127] 值域的 short 值。

2、Netty 記憶體池

Netty 支援通過記憶體池的方式迴圈利用 ByteBuf,避免了頻繁的建立,銷燬 ByteBuf 帶來的資源及效能損耗。

ByteBuf byte 資料緩衝區,是NIO程式設計的主要物件。高負載情景下,ByteBuf 記憶體池使用,可以有效降低GC頻率。

PoolArena Netty 的記憶體池實現類。PoolArena 是由多個Chunk組成的大塊記憶體區域,每個 Chunk 由一個多個 Page 組成。

Chunk:組織管理 Page 的記憶體分配和釋放,Page 被構建為二元樹形式:

PoolSubpage:對於小於 Page 的記憶體使用,直接在 Page 中完成分配,每個 Page 切分為大小相同的多個儲存塊兒,儲存塊兒的大小由第一次申請的記憶體塊兒大小決定。

回收:Netty 使用狀態位標識 Chunk 及 Page 記憶體可用性,Chunk 標識二元樹 Page 節點使用狀態;Page 標識內部記憶體塊兒的使用狀態。

3、redis 共用物件池

當物件為整數且值在範圍在[0-9999]時,redis 可以通過共用物件的方式來節省記憶體。

目前共用物件池只對整數設定了[0-9999]資料共用物件,一方面整數物件池複用率最大,同時等值判斷上時間複雜度為O(1)。

4、執行緒池

執行緒的建立和銷燬是一個非常重量級的操作,執行緒複用是加快服務響應的一個重要手段。

5、連線池

資料庫連線池、Http 連線池等。

基於 TCP 的連線,其連線建立及斷開需要經過三次握手及四次揮手的複雜互動過程。

... ...

二、快取

快取,即資料交換的緩衝區。通常來說,快取資料存放於記憶體,因此擁有極高的資料操作效率。

1、資料儲存快取

資料的持久化儲存一般依靠資料庫、檔案系統等儲存媒介。

直接的資料讀取效能支撐有限,一般會設定分散式快取或者本地快取中間儲存做熱點資料響應。

2、Mysql 查詢快取

對於相同查詢語句及相同查詢條件的,Mysql 會使用首次快取的結果進行相應。

同樣的機制延伸到目前廣泛使用的 Mybatis、Hibernate ORM 框架等。

3、Buffer

Kafka Buffer、Netty Buffer 等。

提供傳送及接收緩衝區,網路資料傳送及接收處理不再侷限於實時。可以通過設定積攢一定的量後再去處理,並且或支援 Buffer 內容操作。

Mysql InnoDB 的 change buffer。

InnoDB 可以使用它的 change buffer(change buffer 的主要目的是將對二級索引的資料操作快取下來,以此減少二級索引的隨機IO,並達到操作合併的效果)來批次寫二級索引記錄。

... ...

三、記憶體分配

記憶體分配觸及底層資源申請及使用,屬於記憶體管理範疇內的優化。

記憶體分配方面的優化主要涉及記憶體分配次數及記憶體使用率等因素考量。

1、redis SDS

SDS 即 Simple Dynamic String, Redis 自定的字串儲存結構。

Redis 在SDS記憶體設定策略上採用了【空間預分配】 + 【惰性刪除】相結合的策略。

空間預分配:

在一次 SDS 字元擴充套件操作中,擴充套件的空間大小會大於實際需要的空間大小。

預分配空間的大小基於以下規則計算:

SDS len<1M:分配len長度空間作為預分配空間;

SDS len>=1M:分配1M空間作為預分配空間;

惰性刪除:

調整刪除 SDS 中部分資料時,不會立刻執行記憶體重分配,而是會保留空出來記憶體,並更新內部 free 屬性。以備將來有字元擴充套件需求,可以直接使用。

2、Netty 動態緩衝區分配

動態緩衝區分配器,原始碼說明:根據實時的反饋動態的增加或者減少預需的緩衝區大小。

如果上一次分配的緩衝區被填滿了,則調高下一次分配的緩衝區大小。

如果連續兩次實際使用的容量低於分配的緩衝區大小特定比例,則減小下一次分配的緩衝區大小。

其它情景,保持分配大小不變。

Netty 的這種「智慧化」處理,可以說是相當有用的:

  • 首先,實際的應用場景千差萬別,同一場景下不同時刻的緩衝區需求也是實時變化(一句話可以是一個字,也可能是1000個字),這就需要 Netty 動態調整緩衝分配大小以適應不同的業務場景,時刻場景。

  • 其次,過大的不必要的記憶體分配,會導致 Buffer 處理效能下降;過小的記憶體分配,則會導致頻繁的分配釋放。這都是一個優良的網路框架不應該有的。

  • 最後,動態的調整最直接的好處就是記憶體的的高效使用,一定程度上做到了按需分配。

3、Memcached Slab Allocator

基於 Slab Allocator 記憶體分配機制。一個 slab 包含很多 page,一個 page 包含很多 chunk。