標籤:
先從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的值 */@Overridepublic 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方法的實現就是這樣:
@Overridepublic 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
http://www.cnblogs.com/5207/p/5788439.html
聊聊從web session的共用到可擴充緩衝設計