狹義地講,性能是指軟件在儘可能少地佔用系統資源的前提下,儘可能高地提高運行速度。
談及性能,我們的關注點不再是軟件或者系統的功能,而是在其實現功能過程中所表現出來的資源效率。
一、池化思想
什麼是池化?
簡單的說就是設置一個公共對象池,對於其中的對象直接複用而不再使用新創建的方式。
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。