本文是《輕量級 Java Web 框架架構設計》的系列博文。 java
Smart 框架一切都圍繞着輕量、簡單、實用的方向在努力,對於 Cache 插件也不例外。最近忙着拉項目,因此投入在 Smart 的精力就很少了。前幾天有朋友想讓我寫一個 Cache 淘汰策略,當時腦海裏有幾個算法,例如: git
固然還有其餘更加優秀的算法,我目前只想選擇一種最簡單的做爲缺省的實現,對於特定的需求,未來還能夠進行擴展。 算法
以上前兩種比較相似,都是針對是訪問時間或訪問次數來進行淘汰,稍許有些複雜,因此我選擇了 FIFO 的算法,經過建立時間來判斷是否淘汰。具體來講,在放入 Cache 的時候指定一個過時時間(1分鐘、10分鐘、1個小時、1天等),再用一個線程去輪詢放在 Cache 裏的過時時間,若已過時,則直接從 Cache 中移除。 架構
這種策略每每實現起來比較簡單,也很是實用,不妨先實現這種算法吧。 框架
第一步:定義一個 Expiry 常量接口 ide
public interface Expiry { long ETERNAL = -1; long ZERO = 0; long ONE_MINUTE = 60 * 1000; long FIVE_MINUTES = 5 * ONE_MINUTE; long TEN_MINUTES = 10 * ONE_MINUTE; long TWENTY_MINUTES = 20 * ONE_MINUTE; long THIRTY_MINUTES = 30 * ONE_MINUTE; long ONE_HOUR = 60 * ONE_MINUTE; long ONE_DAY = 24 * ONE_HOUR; }
該接口裏面有許多經常使用的時間選項。該接口的定義參考了 JSR 107 規範。 this
第二步:定義一個 Duration 類 spa
public class Duration { private long start; // 開始時間 private long expiry; // 過時時長(毫秒) public Duration(long start, long expiry) { this.start = start; this.expiry = expiry; } public long getStart() { return start; } public long getExpiry() { return expiry; } }
其中包括了開始時間,也就是將數據放入 Cache 的時間。此外還包括一個過時時長,用毫秒錶示,也就是多長時間過時,這個字段可使用 Expiry 常量接口中的選項。 .net
第三步:爲 CachePut 註解添加一個 expiry 屬性 插件
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface CachePut { String value(); long expiry() default Expiry.ETERNAL; }
默認過時時間爲 Expiry.ETERNAL,也就是永不過時的意思。
第四步:擴展幾個 Cache 相關類
Cache 接口:
public interface Cache<K, V> { // 從 Cache 中獲取數據 V get(K key); // 將數據放入 Cache 中 void put(K key, V value); // 將數據放入 Cache 中,指定有效期(ms) void put(K key, V value, long expiry); // 從 Cache 中移除數據 void remove(K key); // 清空 Cache void clear(); // 獲取全部的 Duration Map<K, Duration> getDurations(); }
添加了兩個方法:
void put(K key, V value, long expiry); 當初始化 Cache 的時候能夠傳入一個過時時間。
Map<K, Duration> getDurations(); 返回全部的過時對象,也就是一組 Cache key 與 Duration 的映射關係。
CacheManager 接口:
public interface CacheManager { // 獲取全部的 Cache Iterable<Cache> getCaches(); // 建立 Cache <K, V> Cache<K, V> createCache(String cacheName); // 獲取 Cache <K, V> Cache<K, V> getCache(String cacheName); // 銷燬指定 Cache void destroyCache(String cacheName); }
爲該接口添加了一個方法:
Iterable<Cache> getCaches(); 返回 CacheManager 中所管理的全部 Cache 對象。爲了在循環中迭代,因此返回了 Iterable 接口。
CacheFactory 工廠類:
public class CacheFactory { // 定義一個 CacheManager Map,用於存放目標類與 CacheManager 的對應關係(一個目標類對應一個 CacheManager),目標類通常爲 Service 類 private static final Map<Class<?>, CacheManager> cacheManagerMap = new HashMap<Class<?>, CacheManager>(); public static Iterable<CacheManager> getCacheManagers() { return cacheManagerMap.values(); } ... }
對外提供了一個 getCacheManagers 方法,便於訪問 CacheFactory 中的私有成員 cacheManagerMap。
具體的實現,請參考 Smart Cache Plugin 源碼。
第五步:提供一個 CacheThread,用於實現淘汰算法
public class CacheThread extends Thread { @Override @SuppressWarnings("unchecked") public void run() { try { while (true) { // 遍歷全部的 Cache Manager Iterable<CacheManager> cacheManagers = CacheFactory.getCacheManagers(); for (CacheManager cacheManager : cacheManagers) { // 遍歷全部的 Cache Iterable<Cache> caches = cacheManager.getCaches(); for (Cache cache : caches) { // 遍歷全部的 Duration Map Map<Object, Duration> durationMap = cache.getDurations(); for (Object entrySet : durationMap.entrySet()) { // 獲取 Duration Map 中的 key 與 value Map.Entry<Object, Duration> entry = (Map.Entry<Object, Duration>) entrySet; Object cacheKey = entry.getKey(); Duration duration = entry.getValue(); // 獲取 Duration 中的相關數據 long start = duration.getStart(); // 開始時間 long expiry = duration.getExpiry(); // 過時時長 // 獲取當前時間 long current = System.currentTimeMillis(); // 判斷是否已過時 if (current - start >= expiry) { // 若已過時,則首先移除 Cache(也會同時移除 Duration Map 中對應的條目) cache.remove(cacheKey); } } } } // 使線程休眠 5 秒鐘 sleep(5000); } } catch (InterruptedException e) { throw new CacheException("錯誤:運行 CacheThead 出現異常!", e); } } }
以上代碼從 CacheManager 開始進行遍歷,最終獲取相應的 Cache,並從中獲取 Duration,經過很簡單的方法就能判斷出 Cache 是否已過時,過時了就從 Cache 中移除,同時也要移除 Duration Map 中相應的條目。
這個線程如何才能開啓呢?須要找個地方初始化。
第六步:建立一個 CachePlugin 類並實現 Plugin 接口
public class CachePlugin implements Plugin { @Override public void init() { new CacheThread().start(); } }
Plugin 接口由 Smart 框架提供,此時只需實現該接口,在 init 方法中完成初始化工做便可,也就是開啓 CacheThread 這個線程。
還差什麼呢?那就是如何去使用這個新的特性了!
最後一步:配置 Cache 過時時間
不妨使用註解的方式來配置過時時間,固然也能夠經過 API 的方式來設置。
@Bean @Cachable public class CustomerServiceCacheAnnotationImpl extends BaseService implements CustomerService { @Override @CachePut(value = "customer_list_cache", expiry = Expiry.ONE_MINUTE) public List<Customer> getCustomerList() { return DataSet.selectList(Customer.class, "", ""); } ... }
這裏將 customer_list_cache 這個名稱的 Cache 的過時時間設置爲 1 分鐘。
OK,打完收工!回家抱小孩了。
期待您的意見或建議!