前言
RedisHttpSession 是我的一個 Java 開源專案,通過將 Session 儲存在 Redis 中實現多伺服器間共享 Session,同時這一過程是完全透明的。主要用於支援 RESTfuls API。下面我將對其核心類進行分析,闡述它的設計以及實現細節。
RedisHttpSession
這個類實現了HttpSession
介面,用於替換預設的HttpSession
實現。RedisHttpSession
將HttpSession
的介面方法重寫了一遍,將HttpSession
的屬性儲存到了 Redis 中。
每個RedisHttpSession
都有一個 UUID 與之對應,該欄位加上session:
字首作為儲存在 Redis 中的鍵值。例如:
1 2 3 4 5 6 |
localhost:63679> keys * 1) "session:fd9ec3cf-fb9b-4672-ade6-67a810e7db9f" 2) "session:cbaa057c-85a4-475d-b399-38c320e85dcc" 3) "session:13e030f5-de3d-458f-8d25-fd5643c40ff0" 4) "session:262596b3-3d13-4df1-8328-714153c1ae83" 5) "session:0b7d04c6-eaac-4eed-a9aa-8366f25f04f0" |
同時,RedisHttpSession
中的屬性是直接序列化成位元組陣列儲存在 Redis 中的,儲存在對應鍵中的雜湊表裡面。例如:
1 2 3 4 5 6 7 |
localhost:63679> hgetall session:fd9ec3cf-fb9b-4672-ade6-67a810e7db9f 1) "lastAccessedTime" 2) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01T\x91\x03\"\xec" 3) "maxInactiveInterval" 4) "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\a\b" 5) "creationTime" 6) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01T\x91\x03\"\xb4" |
另外,Session 的自動過期是通過 Redis 設定鍵的過期時間實現的。
RedisHttpSessionProxy
RedisHttpSessionProxy
是RedisHttpSession
的代理類,使用了 JDK 的動態代理。為什麼要引入代理?這是基於下面的考慮做出的設計。
- 由於 Session 儲存在 Redis 中,在執行每個
RedisHttpSession
的介面方法之前都需要檢查 Redis 連線是否可用。 - 訪問一個已經被登出的 Session 需要丟擲異常。
- 每次訪問 Session 需要重新整理過期時間和最後訪問時間。
基於上面的考慮,對於每個RedisHttpSession
的介面方法,我們都需要進行重複的操作,因此使用動態代理對每個介面方法進行增強是最合適的。程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { RedisHttpSession session = (RedisHttpSession) originalObj; //check redis connection RedisConnection connection = session.getRedisConnection(); if (!connection.isConnected()){ connection.close(); session.setRedisConnection(repository.getRedisConnection()); } //For every methods of interface, check it valid or not if (session.isInvalidated()){ throw new IllegalStateException("The HttpSession has already be invalidated."); } else { Object result = method.invoke(originalObj, args); //if not invalidate method, refresh expireTime and lastAccessedTime; if (!method.getName().equals("invalidate")) { session.refresh(); session.setLastAccessedTime(System.currentTimeMillis()); } return result; } } |
RedisHttpSessionFilter
RedisHttpSessionFilter
作為過濾器,將請求和響應替換成RedisSessionRequestWrapper
和RedisSessionResponseWrapper
,利用了裝飾器模式動態的給請求和響應進行增強:
RedisSessionRequestWrapper
:重寫了getSession
,替換預設的HttpSession
實現為RedisHttpSession
。RedisSessionResponseWrapper
:在響應的頭部中加入x-auth-token
欄位,作為 Session 的 ID。客戶端之後的請求都需要附帶該欄位,以便服務端識別對應的 Session。
總結
RedisHttpSession 利用 Filter 將請求的 Session 替換成 RedisHttpSession
,在過濾階段偷樑換柱,在之後對 Session 的操作都無需關心其內部實現,整個過程都是透明的。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式