我所知道並且試驗過的session共享的方式有兩種:
1.tomcat的session複製功能
2.單獨一臺session服務器
還有一些方式
3.對於同一ip始終分配到一臺服務器
4.cookie保存session
第三種方式的缺點比較明顯,始終到同一臺服務器看起來沒什麼問題,但是如果那臺機器剛好宕機,那請求轉發到其他服務器,其他服務器無法獲取之前session,造成session丟失的情況。
第四種方式缺點主要是:
cookie不是很安全,別人可以分析存放在本地的COOKIE並進行COOKIE欺騙(可設置:httponly)考慮到安全應當使用session。單個cookie保存的數據不能超過4K,很多瀏覽器都限制一個站點最多保存20個cookie。
綜上,session複製和單獨一臺session服務器可行性較高。在集羣不是很龐大的情況下,session複製就可以工作的很好了。
下面重點介紹第二種方式。
二.nosql服務器保存session原理
將所有節點的Session放到一起進行統一管理,每個節點在未參與集羣以前都有自己獨立的Session體系,參與到集羣以後可以讓所有節點將各自的Session信息用一套相同的機制保存到一個統一的地方進行存取,這樣不管請求被分發到哪個節點都可以訪問到其它節點創建的Session。
放到同一地方可以是數據庫,可以是cache,也可以是nosql。
做法是:
1.簡單實現一個自己的session,支持序列化
2.創建session要同步保存在session服務器
3.每次更新session都要同步
4.設置cookie,將sessionId保存在瀏覽器,設置失效時間
5.session服務器對於session也要有失效管理
三.具體實現
環境:redis2.6 tomcat6.0 apache2.2
1.對象序列化工具類
顧名思義,就是將對象轉化成可保存的字節碼。
package com.chenwei.session;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerializableUtil {
/**
* 序列化對象
*
* @param object
* @return
* @throws IOException
*/
public static byte[] writeObject(Object object) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
oos.flush();
oos.close();
return bos.toByteArray();
}
/**
* 反序列話
* @param buf
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
public static Object readObject(byte[] buf) throws IOException, ClassNotFoundException {
ByteArrayInputStream bis = new ByteArrayInputStream(buf);
ObjectInputStream ins = new ObjectInputStream(bis);
Object object = ins.readObject();
ins.close();
return object;
}
public static void main(String[] args) {
}
}
2.session實現
有兩種方式:
1.1包裝map作爲session存儲單元
1.2包裝httpsession
兩種方式要注意,sessionId一定要唯一且不能使用httpsession自帶的getId獲取到的id,因爲多個tomcat負載,每次請求都在不同tomcat,每次請求帶去的jsessionID在當前服務器找不到就會創建一個新的,這個新的也會寫回到瀏覽器,造成每次請求後cookie裏面的jsessionid改變,永遠在創建。
我這裏是包裝了map
/**
* 觀察者模式,一旦有修改 自動同步到redis服務器
* @author chenwei
*
*/
public class RedisSession extends RedisSessionObservable implements Serializable {
/**
*
*/
private static final long serialVersionUID = -121659577343047214L;
public static final String SESSION_COOKIE_NAME ="rsessionid";
private Map<String, Object> sessionMap;
private String sid;
public RedisSession(){
this.sessionMap = new HashMap<String,Object>();
sid = UUID.randomUUID().toString().replaceAll("-", "");
System.out.println("init sid :"+sid);
addObserver(new RedisSessionObserver());
setChanged();
notifyObservers();
}
public RedisSession(String sid) {
this.sessionMap = new HashMap<String,Object>();
this.sid = sid;
addObserver(new RedisSessionObserver());
setChanged();
notifyObservers();
}
public static RedisSession getSession(String sid){
return RedisSessionDAO.getInstance().get(sid);
}
public String getId() {
return sid;
}
public Object getAttribute(String name) {
return sessionMap.get(name);
}
public void setAttribute(String name, Object value) {
sessionMap.put(name, value);
setChanged();
notifyObservers();
}
public void removeAttribute(String name) {
sessionMap.remove(name);
setChanged();
notifyObservers();
}
}
這裏我使用了觀察者模式,session繼承了RedisSessionObservable,在每次更新session時候,發送通知到觀察者,由觀察者負責保存到redis。
觀察者代碼:
public class RedisSessionObserver implements Serializable{
/**
*
*/
private static final long serialVersionUID = 3004378138310570499L;
public void update(RedisSessionObservable o, Object arg) {
System.out.println("更新redis緩存");
RedisSessionDAO.getInstance().set(o);
}
}
3.redis操作
使用jedis框架對redis進行get/set
public class RedisSessionDAO {
private Jedis jsdis;
private JedisPool jedisPool;
private static RedisSessionDAO dao = new RedisSessionDAO();
public void init() {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxIdle(20);
config.setMaxIdle(5);
config.setMaxWaitMillis(1000l);
config.setTestOnBorrow(false);
jedisPool = new JedisPool(config, "127.0.0.1", 6379);
}
public void set(Object o) {
jsdis = jedisPool.getResource();
RedisSession session = (RedisSession) o;
try {
jsdis.set(session.getId().getBytes(), SerializableUtil.writeObject(o));
} catch (IOException e) {
e.printStackTrace();
}
jedisPool.returnResource(jsdis);
}
public RedisSession get(String sid) {
RedisSession session = null;
jsdis = jedisPool.getResource();
byte[] sessionByte = jsdis.get(sid.getBytes());
if (sessionByte == null) {
return session;
}
try {
session = (RedisSession) SerializableUtil.readObject(sessionByte);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
jedisPool.returnResource(jsdis);
return session;
}
public static RedisSessionDAO getInstance() {
dao.init();
return dao;
}
}
4.加載器
public class RedisSessionLoader {
public static RedisSession getSession(HttpServletRequest req, HttpServletResponse resp) {
SessionCookieReadWrapper reader = new SessionCookieReadWrapper(req);
SessionCookieWriteWrapper writer = new SessionCookieWriteWrapper(resp);
RedisSession session = null;
if (reader.exsitCookie()) {
System.out.println(reader.getSessionId());
session = RedisSessionDAO.getInstance().get(reader.getSessionId());
if (session == null) {
session = new RedisSession(reader.getSessionId());
}
} else {
System.out.println("新加一個session");
session = new RedisSession();
writer.addSessionCookie(session.getId());
}
return session;
}
}
其中SessionCookieReadWrapper和SessionCookieWriteWrapper 用來檢查cookie是否有sessionid,有的話就查找session,沒有就重新創建一個session,並且寫回到cookie。
代碼如下:
public class SessionCookieReadWrapper extends HttpServletRequestWrapper {
private HttpServletRequest request;
public SessionCookieReadWrapper(HttpServletRequest request) {
super(request);
this.request = request;
}
public boolean exsitCookie() {
Cookie[] cookies = request.getCookies();
if (cookies == null || cookies.length == 0) {
return false;
}
for (Cookie cookie : cookies) {
if (cookie.getName().equals(RedisSession.SESSION_COOKIE_NAME)) {
return true;
}
}
return false;
}
public String getSessionId() {
String id = null;
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
if (cookie.getName().equals(RedisSession.SESSION_COOKIE_NAME)) {
id = cookie.getValue();
}
}
return id;
}
}
public class SessionCookieWriteWrapper extends HttpServletResponseWrapper {
private HttpServletResponse response;
public SessionCookieWriteWrapper(HttpServletResponse response) {
super(response);
this.response = response;
}
public void addSessionCookie(String sessionId){
Cookie cookie = new Cookie(RedisSession.SESSION_COOKIE_NAME,sessionId);
cookie.setMaxAge(3600);
response.addCookie(cookie);
}
}
5.使用樣例和結果
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String name = req.getParameter("name");
RedisSession session = RedisSessionLoader.getSession(req, resp);
session.setAttribute("curreantUser", name);
}
結果截圖:
控制檯信息:
中文亂碼了。。。。。。。。。
打印依次是:
新加一個session
更新redis緩存