集群環境下的Session共享
一、Cookie機制和Session機制回顧
1)定義:Session成為「會話」,具體是指一個終端用戶與交互系統進行通信的時間間隔,通常指從註冊進入系統到註銷退出系統之間所經過的時間。Session實際上是一個特定的時間概念。
2)HTTP協議與狀態保持:HTTP 協議本身是無狀態的,這與HTTP協議本來的目的是相符的,客戶端只需要簡單的向伺服器請求下載某些文件,無論是客戶端還是伺服器都沒有必要紀錄彼此過去的行為,每一次請求之間都是獨立的,好比一個顧客和一個自動售貨機或者一個普通的(非會員制)大賣場之間的關係一樣。
cookie的作用就是為了解決HTTP協議無狀態的缺陷。至於後來出現的session機制則是又一種在客戶端與伺服器之間保持狀態的解決方案。
3)Cookie和Session機制的區別和聯繫(幾個有趣的例子):
1、該店的店員很厲害,能記住每位顧客的消費數量,只要顧客一走進咖啡店,店員就知道該怎麼對待了。這種做法就是協議本身支持狀態。
2、發給顧客一張卡片,上面記錄著消費的數量,一般還有個有效期限。每次消費時,如果顧客出示這張卡片,則此次消費就會與以前或以後的消費相聯繫起來。這種做法就是在客戶端保持狀態。(Cookie原理)
3、發給顧客一張會員卡,除了卡號之外什麼信息也不紀錄,每次消費時,如果顧客出示該卡片,則店員在店裡的紀錄本上找到這個卡號對應的紀錄添加一些消費信息。這種做法就是在伺服器端保持狀態。(Session原理)
由於HTTP協議是無狀態的,cookie機制採用的是在客戶端保持狀態的方案,而session機制採用的是在伺服器端保持狀態的方案。同時我們也看到,由於採用伺服器端保持狀態的方案在客戶端也需要保存一個標識,所以session機制可能需要藉助於cookie機制來達到保存標識的目的,但實際上它還有其他選擇。
二、集群下實現Session共享的幾種方案
1.請求精確定位:基於IP地址的Hash策略,將同一用戶的請求都集中在一台伺服器上,這台伺服器上保存了該用戶的Session信息。缺點:單點部署發生宕機時,Session丟失。
2.Session複製共享:比如可以用Tomcat自帶的插件進行Session同步,使得多台應用伺服器之間自動同步Session,保持一致。如果一台發生故障,負載均衡會遍歷尋找可用節點,Session也不會丟失。缺點:必須是Tomcat和Tomcat之間,Session的複製也會消耗系統 的性能,使得同步給成員時容易造成內網流量瓶頸。
3.基於cache DB緩存的Session共享(推薦,Spring-Session也是同樣的原理,同自定義的JRedis一起配置可以實現目的):使用Redis存取Session信息,應用伺服器發生故障時,當Session不在內存中時就會去CacheDB中查找(要求Redis支持持久化),找到則複製到本機,實現Session共享和高可用。
分散式Session配置原理圖
其他方式:利用公共的NFS伺服器做共享伺服器、完全利用Cookie(將Session數據全放在Cookie中)等。
三、基於Redis的Session共享實現(核心代碼)
1)原理:寫一個Session過濾器攔截每一次請求,在這裡檢查由Cookie生成的SessionID,進行創建或獲取。核心是實現使用裝飾類,實現Session在Redis中的存取操作。
2)此處存取方式為 sessionID+sessionKey作為Redis的key ==== sessionValue作為Redis的value,這樣保存了每次存取都從Redis中操作,效率更高。
3)注意:序列化方式推薦使用Apache下Commons組件——SerializationUtils 或 org.springframework.util.SerializationUtils
1 //定義請求經過的Session過濾器
2 public class SessionFilter extends OncePerRequestFilter implements Filter {
3 @Override
4 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
5 throws ServletException, IOException {
6 // 從cookie中獲取sessionId,如果此次請求沒有sessionId,重寫為這次請求設置一個sessionId
7 String sid = CookieUtil.getCookieValue(request, GlobalConstant.JSESSIONID);
8 if (StringUtils.isEmpty(sid) || sid.length() != 36) {
9 sid = UUID.randomUUID().toString();
10 CookieUtil.setCookie(request, response, GlobalConstant.JSESSIONID, sid, 60 * 60);
11 }
12 // 交給自定義的HttpServletRequestWrapper處理
13 filterChain.doFilter(new HttpServletRequestWrapper(sid, request, response), response);
14 }
15 }
1 //Cookie
2 public static void setCookie(HttpServletRequest request,
3 HttpServletResponse response, String name, String value, int seconds) {
4 if (StringUtils.isEmpty(name) || StringUtils.isEmpty(value))
5 return;
6 Cookie cookie = new Cookie(name, value);
7 //cookie.setDomain(domain);
8 cookie.setMaxAge(seconds);
9 cookie.setPath("/");
10 response.setHeader("P3P",
11 "CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"");
12 response.addCookie(cookie);
13 }
14
15 public String getCookieValue(String name)
16 throws UnsupportedEncodingException {
17 Cookie cookies[] = request.getCookies();
18 if (cookies != null) {
19 for (int i = 0; i < cookies.length; i++) {
20 if (name.equalsIgnoreCase(cookies[i].getName())) {
21 return cookies[i].getValue();
22 }
23 }
24 }
25 return "";
26 }
1 //SessionService實現 sidKey == sessionID+SessionKey
2 public Object getSession(String sidKey) {
3 Object realValue = null;
4 try {
5 String key = 「SESSION_DISTRIBUTED_SESSIONID」 + sidKey;
6 realValue = SerializeUtil.unserialize(RedisUtils.getInstance().get(key.getBytes()));
7 } catch (Exception e) {
8 LOG.error("Redis獲取session異常" + e.getMessage(), e.getCause());
9 }
10 return realValue;
11 }
12
13 public void saveSession(String sidKey, Object value) {
14 try {
15 String key = 「SESSION_DISTRIBUTED_SESSIONID」 + sidKey;
16 boolean isSetSuccess = RedisUtils.getInstance().set(key.getBytes(), SerializeUtil.serialize(value));
17 if (!isSetSuccess) {
18 LOG.error("Redis保存session異常");
19 }
20 } catch (Exception e) {
21 LOG.error("Redis保存session異常" + e.getMessage(), e.getCause());
22 }
23 }
24
25 public void removeSession(String sidKey) {
26 try {
27 String key =「SESSION_DISTRIBUTED_SESSIONID」+ sidKey;
28 RedisUtils.getInstance().del(key.getBytes());
29 } catch (Exception e) {
30 LOG.error("Redis刪除session的attribute異常" + e.getMessage(), e.getCause());
31 }
32 }
33
34 public void removeAllSession(String sid) {
35 try {
36 String keyPattern =「SESSION_DISTRIBUTED_SESSIONID」 + sid + "*";
37 Set<byte[]> keys = RedisUtils.getInstance().keys(keyPattern.getBytes());
38 for (byte[] key : keys) {
39 RedisUtils.getInstance().del(key);
40 }
41 } catch (Exception e) {
42 LOG.error("Redis刪除session異常" + e.getMessage(), e.getCause());
43 }
44 }
45
46 public Set<String> getAllKeys(String sid) {
47 try {
48 Set<String> keysResult = new HashSet<String>();
49 String keyPattern =「SESSION_DISTRIBUTED_SESSIONID」 + sid + "*";
50 Set<byte[]> keys = RedisUtils.getInstance().keys(keyPattern.getBytes());
51
52 for (byte[] key : keys) {
53 keysResult.add(new String(key));
54 }
55 return keysResult;
56 } catch (Exception e) {
57 LOG.error("Redis刪除session異常" + e.getMessage(), e.getCause());
58 return null;
59 }
60 }
1 HttpServletRequestWrapper extends javax.servlet.http.HttpServletRequestWrapper
2 private HttpSession session;
3
4 private HttpServletRequest request;
5
6 private HttpServletResponse response;
7
8 private String sid = "";
9
10 public HttpServletRequestWrapper(HttpServletRequest request) {
11 super(request);
12 }
13
14 public HttpServletRequestWrapper(String sid, HttpServletRequest request) {
15 super(request);
16 this.sid = sid;
17 }
18
19 public HttpServletRequestWrapper(String sid, HttpServletRequest request, HttpServletResponse response) {
20 super(request);
21 this.request = request;
22 this.response = response;
23 this.sid = sid;
24 if (this.session == null) {
25 this.session = new HttpSessionWrapper(sid, super.getSession(false), request, response);
26 }
27 }
28
29 @Override
30 public HttpSession getSession(boolean create) {
31 if (this.session == null) {
32 if (create) {
33 this.session = new HttpSessionWrapper(this.sid, super.getSession(create), this.request, this.response);
34 return this.session;
35 } else {
36 return null;
37 }
38 }
39 return this.session;
40 }
41
42 @Override
43 public HttpSession getSession() {
44 if (this.session == null) {
45 this.session = new HttpSessionWrapper(this.sid, super.getSession(), this.request, this.response);
46 }
47 return this.session;
48 }
1 HttpSessionWrapper implements HttpSession{
2
3 private String sid = "";
4
5 private HttpSession session;
6
7 private HttpServletRequest request;
8
9 private HttpServletResponse response;
10
11 private SessionService sessionService = (SessionService) SpringContextHolder.getBean("sessionService");
12
13 public HttpSessionWrapper() {
14 }
15
16 public HttpSessionWrapper(HttpSession session) {
17 this.session = session;
18 }
19
20 public HttpSessionWrapper(String sid, HttpSession session) {
21 this(session);
22 this.sid = sid;
23 }
24
25 public HttpSessionWrapper(String sid, HttpSession session,
26 HttpServletRequest request, HttpServletResponse response) {
27 this(sid, session);
28 this.request = request;
29 this.response = response;
30 }
31
32
33 @Override
34 public Object getAttribute(String name) {
35 return sessionService.getSession(this.sid+"#"+name);
36 }
37
38 @Override
39 public void setAttribute(String name, Object value) {
40 sessionService.saveSession(this.sid+"#"+name, value);
41 }
42
43 @Override
44 public void invalidate() {
45 sessionService.removeAllSession(this.sid);
46 CookieUtil.removeCookieValue(this.request,this.response, GlobalConstant.JSESSIONID);
47 }
48
49 @Override
50 public void removeAttribute(String name) {
51 sessionService.removeSession(this.sid+"#"+name);
52 }
53
54 @Override
55 public Object getValue(String name) {
56 return this.session.getValue(name);
57 }
58
59 @SuppressWarnings("unchecked")
60 @Override
61 public Enumeration getAttributeNames() {
62 return (new Enumerator(sessionService.getAllKeys(this.sid), true));
63 }
64
65 @Override
66 public String getId() {
67 return this.sid;
68 }
69 }
TAG:程序員小新人學習 |