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缓存




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