本文算是前一篇的后續,java web application中,難免會用到session,集群環境中apache會將http請求智能轉發到其中某台jboss server。假設有二個jboss server:Server A,Server B,Session值在Server A上。用戶在訪問某一個依賴session的頁面時,如果第一次訪問到Server A,能正常取到Session值,刷新一下,如果這時轉發到Server B,Session值取不到,問題就來了。
解決的辦法簡單到讓人不敢相信,在app的web.xml中加一行 <distributable /> 即可(前提:jboss cluster是使用mod_cluster實現的),有了這個節點后,向某台server寫入session時,session會自動復制到其它server node。
下面來具體驗證一下:
網絡環境:
如上圖,有二台機器:172.21.129.181(Master Server & Apacha Server)、172.21.129.128(Slave Server)
User所在的計算機IP為: 172.21.129.57 (圖中未標出)
Sample Application:
為了驗證,我們建一個最簡單的spring mvc web應用
Controller代碼如下:
1 package com.cnblogs.yjmyzz; 2 3 import javax.servlet.http.HttpServletRequest; 4 import javax.servlet.http.HttpSession; 5 6 import org.apache.log4j.Logger; 7 import org.springframework.stereotype.Controller; 8 import org.springframework.ui.Model; 9 import org.springframework.web.bind.annotation.RequestMapping; 10 import org.springframework.web.bind.annotation.RequestMethod; 11 import org.springframework.web.servlet.ModelAndView; 12 13 @Controller 14 public class HomeController { 15 16 Logger log = Logger.getLogger(this.getClass()); 17 18 private static final String sessionKey = "test"; 19 20 /** 21 * 寫入session 22 * @param request 23 * @return 24 */ 25 @RequestMapping(value = "/session-write", method = RequestMethod.GET) 26 public String writeSession(HttpServletRequest request) { 27 HttpSession session = request.getSession(); 28 session.setAttribute(sessionKey, "sample value"); 29 return "session/write"; 30 } 31 32 /** 33 * 讀取session 34 * @param request 35 * @return 36 */ 37 @RequestMapping(value = "/session-read", method = RequestMethod.GET) 38 public ModelAndView readSession(HttpServletRequest request) { 39 HttpSession session = request.getSession(); 40 Object sessionValue = session.getAttribute(sessionKey); 41 ModelAndView model = new ModelAndView(); 42 if (sessionValue != null) { 43 model.addObject(sessionKey, sessionValue); 44 } 45 46 try { 47 //顯示幾個IP到頁面,用於輔助判斷本次Http請求轉發到了哪台server 48 String hostInfo = "InetAddress.getLocalHost() = " 49 + java.net.InetAddress.getLocalHost() 50 + "<br/>request.getRemoteAddr() = " 51 + request.getRemoteAddr() + ":" + request.getRemotePort() 52 + "<br/>x-forwarded-for = " + getUserReadIP(request) 53 + "<br/>request.getLocalAddr() = " + request.getLocalAddr() 54 + ":" + request.getLocalPort(); 55 model.addObject("host", hostInfo); 56 57 } catch (Exception e) { 58 59 } 60 model.setViewName("session/read"); 61 return model; 62 } 63 64 // 獲取用戶真實IP 65 private String getUserReadIP(HttpServletRequest request) { 66 if (request.getHeader("x-forwarded-for") == null) { 67 return request.getRemoteAddr(); 68 } 69 return request.getHeader("x-forwarded-for"); 70 } 71 72 }
write.jsp:(寫入session)
1 <%@ page language="java" contentType="text/html; charset=UTF-8" 2 pageEncoding="UTF-8"%> 3 <html> 4 <head> 5 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 6 <title>session write test</title> 7 </head> 8 <body> 9 <h1>Session寫入成功!</h1> 10 </body> 11 </html>
read.jsp:(顯示session)
1 <%@ page language="java" contentType="text/html; charset=UTF-8" 2 pageEncoding="UTF-8"%> 3 <html> 4 <head> 5 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 6 <title>session read test</title> 7 </head> 8 <body> 9 <h1>Session("test"):${test}</h1> 10 <h1>${host}</h1> 11 </body> 12 </html>
准備就緒,依次以domain模式啟動master server、slave server上的jboss,最后啟動apache server。
然后訪問:
http://172.21.129.181/ModClusterSample/session-write.do 寫入session
繼續訪問:
http://172.21.129.181/ModClusterSample/session-read.do 讀取session
從輸出的幾個IP看,本次請求apache轉發到了 172.21.129.128上(即:slave Server),user客戶端的IP為 172.21.129.57,而apache server的IP為172.21.129.181
另外第一行表明正確讀取到了session值:sample value
這時進入master server的jboss控制台,將slave master上的jboss server給stop掉
再次刷新user機器上的http://172.21.129.181/ModClusterSample/session-read.do
可以看到,因為slave server上的jboss server已經被停掉了,所以本次http請求被轉發到了172.21.129.181上(即master server),但是session值仍然能正常輸出,說明session值在寫入的同時,確實已經被復制到二台jboss server上了,session replication驗證成功!