2 緩存概述
2.1 緩存本質
系統各級處理速度不比對,導緻利用空間換時間。緩存是提升系統性能的一個簡單有效的辦法。
2.2 緩存加載時機
啟動全量加載
全局有效,使用簡單,但導緻啟動慢。
懶加載
同步使用加載
- 先看緩存是否有資料,沒有則從資料庫讀取
- 讀取的資料,先放入
延遲異步加載
從緩存擷取資料,不管是否為空直接傳回,有如下兩種政策:
- 異步,如果為空,則發起一個異步加載的線程,負責加載資料
- 解耦,異步線程負責維護緩存的資料,定期或根據條件觸發更新
2.3 緩存的有效性
-
變動頻、一緻性要求高的資料,不适合用緩存
變化大,意味着記憶體緩存資料和原始資料庫資料之間一直有差異;
一緻性要求高,意味着隻有使用原始資料,甚至加了事務,才穩妥。
緩存的有效性度量
-
讀寫比
對資料的寫操作導緻資料變動,意味着維護成本。N :1。
-
命中率
命中緩存意味着緩存資料被使用,意味着有價值。90%+
計算機科學隻存在兩個難題:緩存失效和命名。
- Phil Karlton
是以必須綜合衡量資料一緻性,性能,成本來決定是否引入緩存。
2 緩存媒體
從硬體媒體上來看,記憶體和硬碟
從技術上,可以分成記憶體、硬碟檔案、資料庫
- 記憶體
将緩存存儲于記憶體中是最快的選擇,無需額外的I/O開銷,但是記憶體的缺點是沒有持久化落地實體磁盤,一旦應用異常break down而重新啟動,資料很難或者無法複原
- 硬碟
一般來說,很多緩存架構會結合使用記憶體和硬碟,在記憶體配置設定空間滿了或是在異常的情況下,可以被動或主動的将記憶體空間資料持久化到硬碟中,達到釋放空間或備份資料的目的。
- 資料庫
增加緩存的政策的目的之一就是為了減少資料庫的I/O壓力。現在使用資料庫做緩存媒體是不是又回到了老問題上了?其實,資料庫也有很多種類型,像那些不支援SQL,隻是簡單的key-value存儲結構的特殊資料庫(如BerkeleyDB和Redis),響應速度和吞吐量都遠遠高于我們常用的關系型資料庫等。
3 緩存分類和應用場景
根據緩存與應用的藕合度,分為local cache(本地緩存)和remote cache(分布式緩存)
本地緩存
在業務系統應用中的緩存元件:
- 最大的優點
應用和cache是在同一個JVM程序,請求緩存非常快速,沒有過多網絡開銷(請求 redis 伺服器)等,在單體應用不需叢集或叢集下各節點無需互相通知的資料強一緻場景下使用本地緩存較合适
- 缺點
應為緩存跟應用程式耦合,多個應用程式無法直接的共享緩存,各應用或叢集的各節點都需要維護自己的單獨緩存,對記憶體是一種浪費。
分布式緩存
與應用分離的緩存元件或服務,其最大的優點是自身就是一個獨立的應用,與本地應用隔離,多個應用可直接的共享緩存
3.1 本地緩存
3.1.1 程式設計直接實作緩存
有的場景隻需簡單的緩存資料的功能,無需關注更多存取、清空政策等深入特性,這時直接程式設計實作緩存最為簡單高效。
成員變量或局部變量實作
public void UseLocalCache(){
//一個本地的緩存變量
Map<String, Object> localCacheStoreMap = new HashMap<String, Object>();
List<Object> infosList = this.getInfoList();
for(Object item:infosList){
if(localCacheStoreMap.containsKey(item)){ //緩存命中 使用緩存資料
// todo
} else { // 緩存未命中 I/O擷取資料,結果存入緩存
Object valueObject = this.getInfoFromDB();
localCacheStoreMap.put(valueObject.toString(), valueObject);
}
}
}
//示例
private List<Object> getInfoList(){
return new ArrayList<Object>();
}
//示例資料庫I/O擷取
private Object getInfoFromDB(){
return new Object();
}
以局部變量map結構緩存部分業務資料,減少頻繁的重複資料庫I/O操作。缺點僅限于類的自身作用域内,類間無法共享緩存。
靜态變量實作
最常用的單例實作靜态資源緩存
public class CityUtils {
private static final HttpClient httpClient = ServerHolder.createClientWithPool();
private static Map<Integer, String> cityIdNameMap = new HashMap<Integer, String>();
private static Map<Integer, String> districtIdNameMap = new HashMap<Integer, String>();
static {
HttpGet get = new HttpGet("http://gis-in.sankuai.com/api/location/city/all");
BaseAuthorizationUtils.generateAuthAndDateHeader(get,
BaseAuthorizationUtils.CLIENT_TO_REQUEST_MDC,
BaseAuthorizationUtils.SECRET_TO_REQUEST_MDC);
try {
String resultStr = httpClient.execute(get, new BasicResponseHandler());
JSONObject resultJo = new JSONObject(resultStr);
JSONArray dataJa = resultJo.getJSONArray("data");
for (int i = 0; i < dataJa.length(); i++) {
JSONObject itemJo = dataJa.getJSONObject(i);
cityIdNameMap.put(itemJo.getInt("id"), itemJo.getString("name"));
}
} catch (Exception e) {
throw new RuntimeException("Init City List Error!", e);
}
}
static {
HttpGet get = new HttpGet("http://gis-in.sankuai.com/api/location/district/all");
BaseAuthorizationUtils.generateAuthAndDateHeader(get,
BaseAuthorizationUtils.CLIENT_TO_REQUEST_MDC,
BaseAuthorizationUtils.SECRET_TO_REQUEST_MDC);
try {
String resultStr = httpClient.execute(get, new BasicResponseHandler());
JSONObject resultJo = new JSONObject(resultStr);
JSONArray dataJa = resultJo.getJSONArray("data");
for (int i = 0; i < dataJa.length(); i++) {
JSONObject itemJo = dataJa.getJSONObject(i);
districtIdNameMap.put(itemJo.getInt("id"), itemJo.getString("name"));
}
} catch (Exception e) {
throw new RuntimeException("Init District List Error!", e);
}
}
public static String getCityName(int cityId) {
String name = cityIdNameMap.get(cityId);
if (name == null) {
name = "未知";
}
return name;
}
public static String getDistrictName(int districtId) {
String name = districtIdNameMap.get(districtId);
if (name == null) {
name = "未知";
}
return name;
}
}
O2O業務中常用的城市基礎基本資訊判斷,通過
靜态變量一次擷取緩存記憶體中,減少頻繁的I/O讀取
。
靜态變量實作類間可共享,程序内可共享,緩存的實時性稍差