最近在自研框架里加入了session共享的实现,现在和大家分享下
session共享:
传统的单机服务器session由服务端生成并保存,那么在分布式场景下,session如何共享?
一般有两种方案:
1,集中式存储session。原理就是利用redis,memcache等统一存储。2,是在各服务器之间进行session复制。
我这里选择使用redis实现session共享。
具体步骤:
1,新增一个filter,进行session相关校验
2,实现HttpSession接口,改写getSession方法,从redis进行存取
3,生产新session后,将新session写到cookie中
简要代码:
1,web.xml配置filter
<!-- 集群session配置-基于缓存实现 -->
<filter>
<filter-name>ClusterSessionFilter</filter-name>
<filter-class>com.xxx.session.filter.ClusterSessionFilter</filter-class>
<init-param>
<param-name>sessionServiceId</param-name>
<param-value>sessionService</param-value>
</init-param>
<init-param>
<param-name>sessionKey</param-name>
<param-value>${server.name}_sessionKey</param-value>
</init-param>
<!-- <init-param>
<param-name>requestUriIgnorePattern</param-name>
<param-value>.*\.(png|gif|jpg|css|js|ico|jpeg|htm|html)lt;/param-value>
</init-param> -->
</filter>
<filter-mapping>
<filter-name>ClusterSessionFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2,ClusterSessionFilter简要实现
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//提出特殊连接的过滤
String requestUrl = request.getRequestURI();
if (this.requestUriIgnorePattern.matcher(requestUrl).matches()) {
chain.doFilter(servletRequest, servletResponse);
return;
}
Cookie cookies[] = request.getCookies();
String sid = "";
//从cookie中拿出sessionid
if (cookies != null) {
for (int i = 0; i < cookies.length; i++) {
Cookie cookie = cookies[i];
if (cookie.getName().equals(sessionKey)) {
sid = cookie.getValue();
break;
}
}
}
// 这一段校验拿到的sessionid,不是重点
String ip = IpUtil.getIpAddress(request);
int ipHash = ip.hashCode();
String sessionId = generateSessionString(ipHash);
if (StringUtils.isBlank(sid)) {
resetSessionIdValueInCookie(response, sessionId);
sid = sessionId;
}else{//判断是否伪造session
String[] sessionIdArr = sid.split("!");
if(sessionIdArr != null && sessionIdArr.length == 2){
if(!StringUtils.equals(String.valueOf(ipHash), sessionIdArr[1])){
resetSessionIdValueInCookie(response, sessionId);
sid = sessionId;
}
}
}
ClusterHttpServletRequestWrapper clusterHttpServletRequest = new ClusterHttpServletRequestWrapper(servletContext,request, sid, this.sessionService);
// 调用下个过滤器
// 这里是重点,往下调用时,我已经将request换成我的包装类了
// 下面重点看这个包装类
chain.doFilter(clusterHttpServletRequest, servletResponse);
//更新session的活动时间
ClusterHttpSessionWrapper clusterHttpSessionWrapper = (ClusterHttpSessionWrapper) clusterHttpServletRequest.getSession();
if(clusterHttpSessionWrapper != null){
clusterHttpSessionWrapper.saveSession();
}
}
4,对getSession的重写
public class ClusterHttpServletRequestWrapper extends HttpServletRequestWrapper {
private SessionManager sessionService;
private String sessionId;
private HttpSession httpSession;
private ServletContext servletContext;
public ClusterHttpServletRequestWrapper(ServletContext servletContext, HttpServletRequest request, String sessionId, SessionManager sessionService) {
super(request);
this.sessionId = sessionId;
this.sessionService = sessionService;
this.servletContext = servletContext;
}
@Override
public HttpSession getSession(boolean create) {
if(this.httpSession == null){
this.httpSession = new ClusterHttpSessionWrapper(this.sessionId, servletContext, this.sessionService);
}
return this.httpSession;
}
@Override
public HttpSession getSession() {
return getSession(false);
}
}
ClusterHttpSessionWrapper 实现HttpSession接口
public class ClusterHttpSessionWrapper implements HttpSession{
private SessionManager sessionService;
private Map<Object,Object> map;
private String sessionId;
private long creationTime;
private long lastAccessedTime;
private int maxInactiveInterval;
private ServletContext servletContext;
private boolean isNew = false;
/**
* 标志位,是否对session中的信息作了更新操作
*/
private boolean changed;
public ClusterHttpSessionWrapper(String sessionId,ServletContext servletContext,SessionManager sessionService) {
this.sessionId = sessionId;
this.sessionService = sessionService;
this.map = this.sessionService.getSession(sessionId);
this.creationTime = (Long)this.map.get("_SESSION_CREATE_TIME_");
this.lastAccessedTime = System.currentTimeMillis();
this.servletContext = servletContext;
this.isNew = true;
}
@Override
public Object getAttribute(String key) {
resetLastAccessTime();
return this.map.get(key);
}
@Override
public Enumeration<Object> getAttributeNames() {
resetLastAccessTime();
return Collections.enumeration(this.map.keySet());
}
@Override
public void invalidate() {
resetLastAccessTime();
this.map.clear();
this.map.put("_SESSION_CREATE_TIME_", System.currentTimeMillis());
this.changed = true;
this.sessionService.removeSession(this.sessionId);
}
@Override
public void removeAttribute(String key) {
resetLastAccessTime();
this.map.remove(key);
this.changed = true;
}
@Override
public void setAttribute(String key, Object value) {
resetLastAccessTime();
this.map.put(key, value);
this.changed = true;
}
@Override
public String getId() {
return this.sessionId;
}
public void saveSession(){
if(this.changed){
this.sessionService.updateSession(this.sessionId, this.map);
}
}
@Override
public long getCreationTime() {
return creationTime;
}
@Override
public long getLastAccessedTime() {
return lastAccessedTime;
}
private void resetLastAccessTime(){
this.lastAccessedTime = System.currentTimeMillis();
this.map.put("_SESSION_CREATE_TIME_", this.lastAccessedTime);
}
@Override
public void setMaxInactiveInterval(int interval) {
this.maxInactiveInterval = interval;
}
@Override
public int getMaxInactiveInterval() {
return this.maxInactiveInterval;
}
@Override
public ServletContext getServletContext() {
return this.servletContext;
}
@Override
public HttpSessionContext getSessionContext() {
throw new UnsupportedOperationException("该方法不被支持调用");
}
@Override
public Object getValue(String name) {
return getAttribute(name);
}
@Override
public String[] getValueNames() {
throw new UnsupportedOperationException("该方法不被支持调用");
}
@Override
public void putValue(String name, Object value) {
setAttribute(name, value);
}
@Override
public void removeValue(String name) {
removeAttribute(name);
}
@Override
public boolean isNew() {
return isNew;
}
}
5,sessionService的实现,就是对redis的操作
public class SessionManagerRedisImpl implements SessionManager {
private JedisCluster jedisCluster;
//second
private Integer expireTime;
public void setJedisCluster(JedisCluster jedisCluster) {
this.jedisCluster = jedisCluster;
}
public void setExpireTime(Integer expireTime) {
this.expireTime = expireTime;
}
@Override
public Map<Object, Object> getSession(String sessionId) {
Map<byte[],byte[]> map = jedisCluster.hgetAll(sessionId.getBytes());
Map<Object,Object> session = new HashMap<Object,Object>();
if (map == null || map.size() == 0) {
session.put("_SESSION_CREATE_TIME_", System.currentTimeMillis());//session创建时间
addSession(sessionId,session);
return session;
}
Set<Map.Entry<byte[],byte[]>> set = map.entrySet();
Iterator<Map.Entry<byte[],byte[]>> it = set.iterator();
while (it.hasNext()){
Map.Entry<byte[],byte[]> entry = it.next();
session.put(SerializeUtil.unserialize(entry.getKey()),SerializeUtil.unserialize(entry.getValue()));
}
//update session expire time
jedisCluster.expire(sessionId.getBytes(),expireTime);
return session;
}
@Override
public void updateSession(String id, Map<Object, Object> session) {
if (StringUtils.isBlank(id) || session == null){
return;
}
Set<Map.Entry<Object, Object>> set = session.entrySet();
Iterator<Map.Entry<Object, Object>> it = set.iterator();
while (it.hasNext()){
Map.Entry<Object, Object> entry = it.next();
jedisCluster.hset(id.getBytes(), SerializeUtil.serialize(entry.getKey()),SerializeUtil.serialize(entry.getValue()));
}
jedisCluster.expire(id.getBytes(),expireTime);
}
@Override
public void removeSession(String id) {
if (StringUtils.isBlank(id)){
return;
}
jedisCluster.del(id.getBytes());
}
@Override
public void addSession(String id, Map<Object, Object> session) {
updateSession(id,session);
}
}