聊聊從web session的共用到可擴展緩存設計

来源:http://www.cnblogs.com/5207/archive/2016/08/19/5788439.html
-Advertisement-
Play Games

先從web session的共用說起 許多系統需要提供7*24小時服務,這類系統肯定需要考慮災備問題,單台伺服器如果宕機可能無法立馬恢復使用,這必定影響到服務。這個問題對於系統規模來說,從小到大可能面臨的難度會相差很大。但對於原理來說其實就是需要準備備份系統隨時可以替代正在服務的系統,也就是無論何時 ...


先從web session的共用說起

  許多系統需要提供7*24小時服務,這類系統肯定需要考慮災備問題,單台伺服器如果宕機可能無法立馬恢復使用,這必定影響到服務。這個問題對於系統規模來說,從小到大可能面臨的難度會相差很大。但對於原理來說其實就是需要準備備份系統隨時可以替代正在服務的系統,也就是無論何時都有伺服器可以提供服務。也就是災備系統或者負載均衡。   提供災備系統或者負載均衡系統都需要面臨一個問題,那就是如何解決共用數據的問題。對於web伺服器而言首先要解決的就是web session共用問題,比如A伺服器的session如何可以在B伺服器上也能一樣使用呢?畢竟是物理隔離的兩台伺服器。   這方面的方案主要是兩類:cookies和session共用。  

cookies

這種方案的思路就是將session的數據寫入到cookies里,每次請求的時候就可以帶上信息,這樣不管是哪台伺服器都能得到同樣的數據啦。這樣不管換多少伺服器都好處理。只不過這種方案需要在服務端開發時需要註意session的數據管理,而且需要接管session的生命周期。如果有一些老的系統可能session用的比較多,就不大好使了。而且將一些敏感數據寫入session還要考慮安全問題,這對於一些數據敏感的系統也可能是個問題。   但如果能控制好session的數據這種方案個人覺得還是挺不錯的,畢竟session並不適合存過多的數據。所以在我們的系統中是支持這種方案的,只需要打開開關參數就行。  

session池化

還有一種方法就是把session共用出來,所有的伺服器都連接到這個共用。這種方案可能是許多系統會使用的方案吧。因為將session池化,對於系統而言就變成透明瞭。程式員終於開心的將數據寫入session咯。這種方案除了http伺服器外,許多的tcp伺服器也是類似的方案。   我們系統因為使用的java開發,使用tomcat時可以將session共用到memcached/redis中。而且這種操作完全不需要改動系統,直接在tomcat中配置即可。所以這種方案天然就支持啦。    

做一個可擴展的緩存策略設計


原先的數據緩存都是放在jvm里的,所以機器多了每台伺服器都要自己去載入緩存,這樣一來命中就低。最近打算在系統里引入第三方緩存,當時在memcached和火的要死的redis里選擇。現在來看每種記憶體產品都各有優勢,如果硬生生的將現在這些老的緩存直接改成redis的如果以後需要用別的記憶體資料庫又得大改代碼。想到這就決定把緩存做一次設計,將現有的jvm緩存保留下來,然後做成策略以擴展新的緩存存儲。   以前的許多緩存用的HashMap/ConcurrentHashMap,反正是鍵-對值。如果我們直接使用Map結構來作為緩存介面就可以不改變現有的一些代碼,只需要改動緩存類內部的數據結構即可。這樣的改動量就比較少。   比如原來的一些緩存單元結構:
public class RoleMenuCache implements IClearCache {
    private static Map<String, RoleMenu> roleMenuCache = new HashMap<String, RoleMenu>();
...業務代碼省略
}

這裡主要是替換這個HashMap所以改動就比較小。

    先來看看類圖    

Cachemanager

這個就是緩存的管理類,用於創建、釋放緩存對象。這個類是各個所有緩存申請的入口。下麵貼出來主要的代碼:
public class CacheManager {
    private final static Logger  logger = LoggerFactory.getLogger(CacheManager.class);
    private static Map<String, ICache> caches = new ConcurrentHashMap<>();
    private static ICacheStrategy cacheStrategy = new DefaultCacheStategy(); 
    private static String cacheStrategyClass;
         
    @SuppressWarnings("unchecked")
    public static synchronized <T extends ICache> T  getOrCreateCache(String cacheName, Class<?> keyClass, Class<?> valueCalss) {
       T cache = (T) caches.get(cacheName);
        if (cache != null) {
            return cache;
        }
        cache = (T) cacheStrategy.createCache(cacheName, keyClass, valueCalss);
        caches.put(cacheName, cache);
        return cache;
    }
     
    @SuppressWarnings("rawtypes")
    public static synchronized void destroyCache(String cacheName) {
        ICache cache = caches.remove(cacheName);
        if (cache != null) {
            cache.clear();
        }
    }
     
}

 

ICache<K,V>

這個介面是規範緩存類的介面,所有的緩存類都要實現這個介面,而且它是繼承java.util.Map介面的,這樣就支持了Map派生的類,相容老程式就好多了。  

ICacheStrategy

對於具體的緩存實現就有一套策略,有一個ICacheStrategy介面來規範。這麼一來,不管是jvm還是redis都可以自己單獨擴展來實現。
public interface ICacheStrategy {
   ICache createCache(String name, Class<?> keyClass, Class<?> valueCalss);
   void destroyCache(ICache cache);
}

 

看一下DefaultCache的實現(代碼只放了一部分主要的):
public class DefaultCache<K, V> implements ICache<K, V> {
    protected Map<K, V> map;
    private String name;
    private long maxCacheSize;
    private long maxLifetime;
    private int cacheSize = 0;
 
    public DefaultCache(String name, long maxSize, long maxLifetime) {
        this.name = name;
        this.maxCacheSize = maxSize;
        this.maxLifetime = maxLifetime;
        map = new ConcurrentHashMap<K, V>(103);
    }
     
    @Override
    public V get(Object key) {
        return map.get(key);
    }
 
    @Override
    public V put(K key, V value) {
        return map.put(key, value);
    }
 
    @Override
    public V remove(Object key) {
        return map.remove(key);
    }
 
    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
        map.putAll(m);
    }
 
    @Override
    public void clear() {
        if (map != null) {
            map.clear();
        }
    }
 
}

對於調用方來說其實就很簡單,只需要調用CacheManager即可,還是前面舉的RoleMenuCache

例子,我們改造一下:
public class RoleMenuCache implements IClearCache {
    private static Map<String, RoleMenu> roleMenuCache;
    static {
        roleMenuCache = CacheManager.getOrCreateCache("permissionCache", String.class, RoleMenu.class);
    }
...業務代碼省略
}

對於老代碼的改造還是比較小的,而且這樣的好處是以後想換成redis的也很簡單,對於業務代碼就不需要再修改了。

  遇到Redis與泛型的問題   在擴展redis緩存策略的時候遇到一個問題,就是使用的jedis時,對於key值都是使用的string類型,這就給我們使用泛型設計留下了難題。當然為了相容現在的設計,最後用了JSON來解決。   但是新的問題來了,對於put時是這樣的:
/**
 * 根據key設置map的值
 */
@Override
public V put(K key, V value) {
    jedisTemp.hset(name, JSON.toJSONString(key), JSON.toJSONString(value));
    return value;
}

這並沒啥問題,因為對象轉換成json串是正常的。問題是get的時候,我們使用的

alibaba.fastjson提供的介面並不能轉回成具體類型的對象,因為get方法的的返回值是V類型,是泛型類型,沒法得到class的type。 像這樣的代碼就不行啦:JSON.parseObject(json, V.class)。最後沒辦法,我只好把K和V的類型在創建時由調用者傳入。看下麵的代碼里,兩個紅色的參數,當然這也沒問題,畢竟調用者是知道類型的:
CacheManager.getOrCreateCache("permissionCache", String.class, RoleMenu.class);

最終get方法的實現就是這樣:

@Override
public V get(Object key) {
    String json = jedisTemp.hget(name, JSON.toJSONString(key)); 
    return (V) JSON.parseObject(json, valueClass);
}

問題雖然是解決了,只不過總覺得怪怪的。

    總結與反思       整套的設計受openfire的集群設計影響比較大,我基本是借鑒過來的,目前來看還是挺不錯,最近準備嘗試Ignite,非常容易就接入了系統。         只是openfire使用的是java實現的方案(Hazelcast/Coherence ),這些都是帶Map結構的,並不會有我遇到的Redis的問題。但我覺得這套設計還挺不錯,如果把map介面去掉,自己重新定義方法就可以解決這個問題,不使用泛型,當然這樣對老代碼的改動會比較大。         還有一種情況就是多種緩存產品並存,比如同時使用redis和memcached,現有的設計可能支持不了。但是因為入口限制在了CacheManager,我想加個泛型支持就可以解決。只是這種場景或許並不多見吧。       註:此文章為原創,歡迎轉載,請在文章頁面明顯位置給出此文鏈接! 若您覺得這篇文章還不錯請點擊下右下角的推薦,非常感謝! http://www.cnblogs.com/5207
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 整理了一些常用的新特性,歡迎點贊!!! 新增操作符 1、?? $username = $_GET['user'] ?? ''; $username = isset($_GET['user']) ? $_GET['user'] : 'nobody'; 2、<=> $number1 <=> $numbe ...
  • 一、繼承關係 繼承指的是一個類(稱為子類、子介面)繼承另外的一個類(稱為父類、父介面)的功能,並可以增加它自己的新功能的能力。在Java中繼承關係通過關鍵字extends明確標識,在設計時一般沒有爭議性。在UML類圖設計中,繼承用一條帶空心三角箭頭的實線表示,從子類指向父類,或者子介面指向父介面。 ...
  • 7.vector和list的區別(這個也算是經常問的)vector和數組類似,擁有一段連續的記憶體空間,並且起始地址不變,這樣對隨機的讀取很有效率(就是我們所有的[]運算符了),因為記憶體是連續的如果我們想要插入或者刪除元素的時候就需要對當前的元素進行複製和移動,如果vector存儲的對象較大,或者構造 ...
  • maven 依賴庫查詢 http://search.maven.org ...
  • 在這裡: http://tech.meituan.com/java-memory-reordering.html 指令重排和記憶體可見性(緩存不一致)是兩個不同的問題。 volatile關鍵字太強,即阻擋指令重排,又保證記憶體一致性。 unsafe.putOrderedXXX()只阻擋指令重排,不保證內 ...
  • 面向對象是一種編程思想,和具體語言無關.c,java,JavaScript,php都可以進行帶有自己風格的面向對象的開發. 類是對一類事物都有的屬性和行為的封裝,為什麼需要類?首先我們要想想如果沒有類會怎麼樣?如果沒有類,假設現在我們要表示一輛普通的自行車,我們會想輪子數量2個,價格多少…然後我們又 ...
  • 原文來自:http://zhengdl126.iteye.com/blog/419850 第1章 引言 隨著互聯網應用的廣泛普及,海量數據的存儲和訪問成為了系統設計的瓶頸問題。對於一個大型的 互聯網應用,每天幾十億的PV無疑對資料庫造成了相當高的負載。對於系統的穩定性和擴展性造成了極大的問題。通過數 ...
  • 本視頻為activiti工作流的web流程設計器整合視頻教程 整合Acitiviti線上流程設計器(Activiti-Modeler 5.21.0 官方流程設計器) 本視頻共講了兩種整合方式 1. 流程設計器和其它工作流項目分開部署的方式 2. 流程設計器和SSM框架項目整合在一起的方式 視頻大小 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...