快取技術-大促場景下熱點數據的讀/寫優化方案

  • 2020 年 4 月 11 日
  • 筆記

敢於跨出第一步,往往是成功的開始。

一、快取技術簡介

1、快取是指將被頻繁訪問的熱點數據存儲在距離計算最近的地方,以方便系統快速做出響應。

eg:靜態資源可快取到CDN(Content Delivery NetWork,內容分發網路,即離電信運營商最近的機房)上。也可以快取到反向代理伺服器,與CDN不同的是,反向代理伺服器屬於企業內部機房,它擋在應用伺服器的前端。

2、快取分本地快取和分散式快取,比如:Ehcache、MemCache及Redis

3、快取主要解決以下問題:

緩解應用系統或關係型資料庫的負載壓力

提升系統的吞吐量

4、本地快取:會共享同一個JVM進程內的heap空間,從Ehcache3.x以後提供off-heap(堆外記憶體),堆外記憶體可以減少GC次數或降低暫停時間,可以擴展和使用更大的記憶體空間。

5、分散式快取:由於本地快取的容量有限,無法實現橫向擴展,因此從本地快取架構演變到分散式快取架構是必經之路。常見分散式快取有Redis、MemCache

二、實際應用場景

1、基於Redis集群多寫多讀方案,保障多寫時的數據一致性可以藉助Zookeeper

2、LocalCache結合Redis集群的多級Cache方案

三、擴展,深度了解JVM堆內記憶體和堆外記憶體(轉載)

1、什麼是堆內記憶體

Java 虛擬機在執行Java程式的過程中會把它在主存中管理的記憶體部分劃分成多個區域,每個區域存放不同類型的數據。下圖所示為java虛擬機運行的時候,主要的記憶體分區:

在這些分區中,佔用記憶體空間最大的一部分叫做「堆(heap)」,也就是我們所說的堆內記憶體(on-heap memory)。java虛擬機中的「堆」主要是存放所有對象的實例。這一塊區域在java虛擬機啟動的時候被創建,被所有的執行緒所共享,同時也是垃圾收集器的主要工作區域,因此這一部分區域除了被叫做「堆內記憶體」以外,也被叫做「GC堆」(Garbage Collected Heap)。

1.1 堆內記憶體的垃圾回收

堆內記憶體是java垃圾收集器的主要工作區域,為了提高垃圾回收的效率,在堆內記憶體的內部又劃分出了新生代、老年代和永久代。在新生代記憶體中又按照8:1:1的比例(java虛擬機默認分配比例為8:1:1,這個比例也可以自定義)劃分出了Eden, Survivor1, Survivor2三個區域。

在執行垃圾回收演算法的時候,不同的回收演算法會對記憶體區域造成不一樣的影響。但是大部分的回收演算法會造成堆內記憶體空間在物理上的不連續性。下面以最基本的垃圾回收演算法「標記 – 清除演算法」為例:

可以看到,記憶體區域在經過垃圾回收之後,產生大量不連續的記憶體空間。因此,java虛擬機中的堆內記憶體區域,只是邏輯上的連續,並不能保證物理上的連續性。 所以,作業系統並不能直接得到堆內記憶體區域所存儲的數據在主存中的正確地址。在一些特定的時間點,Java虛擬機會進行一次徹底的垃圾回收(full gc)。徹底回收時,垃圾收集器會對所有分配的堆內記憶體進行完整的掃描,在掃描期間,絕大部分正在運行的java執行緒都會被暫時停止。這意味著:這樣一次垃圾收集對Java應用造成的影響,跟堆內記憶體所存儲的數據的多少是成正比的,過大的堆內記憶體會影響Java應用的性能。

2. 堆外記憶體(off-heap memory)

2.1 堆外記憶體的產生

為了解決堆內記憶體過大帶來的長時間的GC停頓的問題,以及作業系統對堆內記憶體不可知的問題,java虛擬機開闢出了堆外記憶體(off-heap memory)。堆外記憶體意味著把一些對象的實例分配在Java虛擬機堆內記憶體以外的記憶體區域,這些記憶體直接受作業系統(而不是虛擬機)管理。這樣做的結果就是能保持一個較小的堆,以減少垃圾收集對應用的影響。同時因為這部分區域直接受作業系統的管理,別的進程和設備(例如GPU)可以直接通過作業系統對其進行訪問,減少了從虛擬機中複製記憶體數據的過程。

2.2 堆外記憶體的分配

java 在NIO 包中提供了ByteBuffer類,對堆外記憶體進行訪問。下圖為NIO包中ByteBuffer的層次繼承關係

使用下面的方式,可以直接開闢指定大小的對外記憶體:

import sun.nio.ch.DirectBuffer;

import java.nio.ByteBuffer;

public class TestDirectByteBuffer {

public static void main(String[] args) throws Exception {

while (true) {

ByteBuffer buffer = ByteBuffer.allocateDirect(10 * 1024 * 1024);

}

}

}

這樣我們就開闢出了一塊大小為10M的堆外記憶體。

3. 堆外記憶體的優缺點以及與堆內記憶體聯繫

3.1堆外記憶體的優缺點:

優點 :

可以很方便的自主開闢很大的記憶體空間,對大記憶體的伸縮性很好

減少垃圾回收帶來的系統停頓時間

直接受作業系統控制,可以直接被其他進程和設備訪問,減少了原本從虛擬機複製的過程

特別適合那些分配次數少,讀寫操作很頻繁的場景

缺點 :

容易出現記憶體泄漏,並且很難排查

堆外記憶體的數據結構不直觀,當存儲結構複雜的對象時,會浪費大量的時間對其進行串列化。

3.2 堆內記憶體與堆外記憶體的聯繫:

雖然堆外記憶體本身不受垃圾回收演算法的管轄,但是因為其是由ByteBuffer所創造出來的,因此這個buffer自身作為一個實例化的對象,其自身的資訊(例如堆外記憶體在主存中的起始地址等資訊)必須存儲在堆內記憶體中,具體情況如下圖所示。

當在堆內記憶體中存放的buffer對象實例被垃圾回收演算法回收掉的時候,這個buffer對應的堆外記憶體區域同時也就被釋放掉了。