apache+tomcat集羣session共享-redis服務器

一.tomacat集羣session共享方式
我所知道並且試驗過的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緩存




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章