傳統 Servlet 叢集中,Session 僅存在單一 JVM 記憶體中。伺服器故障或維護導致 Session 遺失;多伺服器需黏性 Session,降低彈性。曾嘗試資料庫或 JavaSpaces 持久化 Session,但資料庫本身成為單點故障,且序列化物件存儲困難;JavaSpaces 服務掛掉同樣導致全系統 Session 遺失。
silentWrite/read/take(不發事件)與 loud 版本(發事件通知監聽者)。所有儲存物件必須實作 Memory 介面,定義 equalsMemory() 判斷物件相等性,並支援序列化以便 RMI 傳輸。
public void synchronize(Vector Mnemosynes) throws RemoteException, TransactionException {
MatchAllMemory matchAll = new MatchAllMemory();
Mnemosyne primary = (Mnemosyne) Mnemosynes.firstElement();
Vector allMemories = primary.readAll(matchAll, null);
commonContext.silentWriteAll(allMemories); // 靜默寫入本地
// 雙向註冊事件監聽器
for (Mnemosyne remote : Mnemosynes) {
remote.addTakeRemoteEventListener(this, matchAll);
remote.addWriteRemoteEventListener(this, matchAll);
this.addTakeRemoteEventListener(remote, matchAll);
this.addWriteRemoteEventListener(remote, matchAll);
}
}
public void notify(RemoteEvent evt) throws RemoteException {
if (evt instanceof WriteRemoteEvent) {
commonContext.silentWrite(((WriteRemoteEvent)evt).getMemory());
} else if (evt instanceof TakeRemoteEvent) {
commonContext.silentTake(((TakeRemoteEvent)evt).getMemory());
}
}
遠端寫入/刪除觸發本地靜默同步;本地操作則透過已註冊的監聽器廣播至所有遠端節點,達到最終一致性。
Mnemosyne 實例Naming.rebind("//<localIP>/MnemosyneImpl", mnemosyne)synchronize() 完成叢集同步利用 JDK 1.3 Proxy.newProxyInstance() 建立動態代理,所有方法調用委派給 FailoverHandler.invoke():
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
while (true) {
try {
return method.invoke(delegate, args); // 嘗試當前節點
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof RemoteException) {
delegate = getNextValidDelegate(); // 無縫切換下一節點
} else throw e.getTargetException();
}
}
}
Servlet 端無感知節點切換,實現真正的高可用 Session 存取。
public class SessionWrapperImpl implements SessionWrapper, Memory {
private String _id;
private HttpSession _sess;
public boolean equalsMemory(Memory m) {
return m instanceof SessionWrapper && _id.equals(((SessionWrapper)m)._id);
}
// getSession(), 建構子略
}
實作 Memory 介面,以 Session ID 為比對鍵值,支援 read/write/take 操作。
public void init(ServletConfig conf) {
String[] urls = getURLs(conf); // 取得叢集 RMI URL 列表
SessionManager.initializeMnemosyne(urls);
}
public void doGet(HttpServletRequest req, HttpServletResponse rsp) {
HttpSession baseSess = req.getSession(); // 取得本地 Session (僅為 ID)
HttpSession realSess = SessionManager.getSession(baseSess.getId()); // 從 Mnemosyne 取得真實 Session
SessionManager.saveSession(realSess); // 寫回同步
}
Mnemosyne 不限於 Session 管理,適用於任何需容忍節點故障的分散式記憶體管理系統、P2P 應用(節點動態加入/離開),新節點可快速同步狀態,無單點依賴。