前一段时间师兄让我接触到session共享这个模块,因为有的项目跑在两台服务器上,有时候负载均衡之后,客户会突然跳到登录的界面,关于为什么要共享session这里就不再做过多赘述。这里记录一下自己的实现过程,方便以后查阅和改进。
项目是maven项目,跑在tomcat服务器上,关于tomcat、maven、redis、nginx也不做过度的介绍。
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.1.RELEASE</version>
</dependency>
<!-- redis 客户端配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="testWhileIdle" value="false"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
</bean>
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.host}" />
<property name="port" value="${redis.port}" />
<property name="password" value="${redis.password}" />
<property name="timeout" value="${redis.timeout}" />
<property name="poolConfig" ref="jedisPoolConfig" />
</bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"></bean>
</property>
</bean>
这是逻辑实现前的一些相关配置,关于session实现的逻辑,一开始的想法是在登录验证成功的时候,将HttpSession取出来,request.getSession()取到的是一个StandardSessionFacade对象,StandardSessionFacade是一个HttpSession的实现类,在将这个对象序列化,存入redis(同时在HttpSession中存入一个标记)。之后每次的请求,都会在一个Filter里面验证HttpSession中我们存的标记,如果标记不存在,可能访问的不是同一台服务器,再去redis中取。这个思路有一个问题就是序列化存入的redis的时候,序列化总是出错,开始使用Json序列化StandardSessionFacade对象,发现序列化总是不成功(这个时候还没有自己手写Json序列化的意识),后来使用JDK的序列化方式,也是不成功,原因是session中的attribute域中存的很多对象没有实现serializable接口,但是其中对象太繁杂,不好手动去改,就放弃了这种想法。
在后来的一种实现思路就是,自己写一个HttpSession的实现类UserSession,同时还要实现HttpServletRequestWrapper,自己写一个SessionFilter,在所有Filter之前就把request换成我们实现的类,同时这个类getSession调用的是我们写的UserSession,在UserSession中用自己定义的Hashmap来存储session的内容,并实时的在redis中进行存取。同样这样的方式也产生了不可序列化的问题,后来发现是关于用户权限的对象不可序列化,最终的解决办法是,将用户权限的关键信息用string类型存入redis,需要从redis中取出关于权限的信息时,再通过有效信息还原成关于权限的相关对象。
public class UserSession implements HttpSession {
private String sid = "";
private HttpSession session;
private HttpServletRequest request;
private HttpServletResponse response;
private Map<String, Object> map = null;
private SessionService sessionService = (SessionService) SpringInit.getSpringContext().getBean("sessionService");
public UserSession() {
}
public UserSession(HttpSession session) {
this.session = session;
}
public UserSession(String sid, HttpSession session) {
this(session);
this.sid = sid;
}
public UserSession(String sid, HttpSession session,
HttpServletRequest request, HttpServletResponse response) {
this(sid, session);
this.request = request;
this.response = response;
}
private Map<String, Object> getSessionMap() {
if (this.map == null) {
this.map = sessionService.getSession(this.sid , new HttpServletRequestWrapper(sid, request,response));
}
return this.map;
}
@Override
public Object getAttribute(String name) {
if (this.getSessionMap() != null) {
Object value = this.getSessionMap().get(name);
return value;
}
return null;
}
@Override
public void setAttribute(String name, Object value) {
this.getSessionMap().put(name, value);
sessionService.saveSession(this.sid, this.getSessionMap());
}
@Override
public void invalidate() {
this.getSessionMap().clear();
sessionService.removeSession(this.sid);
CookieUtil.removeCookieValue(this.request,this.response, GlobalConstant.JSESSIONID);
}
@Override
public void removeAttribute(String name) {
this.getSessionMap().remove(name);
sessionService.saveSession(this.sid, this.getSessionMap());
}
@Override
public Object getValue(String name) {
return this.session.getValue(name);
}
@SuppressWarnings("unchecked")
@Override
public Enumeration getAttributeNames() {
return (new Enumerator(this.getSessionMap().keySet(), true));
}
@Override
public String[] getValueNames() {
return this.session.getValueNames();
}
@Override
public void putValue(String name, Object value) {
this.session.putValue(name, value);
}
@Override
public void removeValue(String name) {
this.session.removeValue(name);
}
@Override
public long getCreationTime() {
return this.session.getCreationTime();
}
@Override
public String getId() {
return this.sid;
}
@Override
public long getLastAccessedTime() {
return this.session.getLastAccessedTime();
}
@Override
public ServletContext getServletContext() {
return this.session.getServletContext();
}
@Override
public void setMaxInactiveInterval(int interval) {
this.session.setMaxInactiveInterval(interval);
}
@Override
public int getMaxInactiveInterval() {
return this.session.getMaxInactiveInterval();
}
@Override
public HttpSessionContext getSessionContext() {
return this.session.getSessionContext();
}
@Override
public boolean isNew() {
return this.session.isNew();
}
}
public class HttpServletRequestWrapper extends javax.servlet.http.HttpServletRequestWrapper{
private HttpSession session;
private HttpServletRequest request;
private HttpServletResponse response;
private String sid = "";
public HttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
public HttpServletRequestWrapper(String sid, HttpServletRequest request) {
super(request);
this.sid = sid;
}
public HttpServletRequestWrapper(String sid, HttpServletRequest request,
HttpServletResponse response) {
super(request);
this.request = request;
this.response = response;
this.sid = sid;
if (this.session == null) {
this.session = new UserSession(sid, super.getSession(false),
request, response);
}
}
@Override
public HttpSession getSession(boolean create) {
if (this.session == null) {
if (create) {
this.session = new UserSession(this.sid,
super.getSession(create), this.request, this.response);
return this.session;
} else {
return null;
}
}
return this.session;
}
@Override
public HttpSession getSession() {
if (this.session == null) {
this.session = new UserSession(this.sid, super.getSession(),
this.request, this.response);
}
return this.session;
}
}
public class SessionFilter extends OncePerRequestFilter implements Filter {
// private static final Logger LOG = Logger.getLogger(SessionFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String sid;
if (request.getRequestURI().equals("/") || request.getRequestURI().equals("/merchant/login")){
filterChain.doFilter(request,response);
}else {
sid = CookieUtil.getCookieValue(request,GlobalConstant.COOKID);
if (StringUtil.isEmpty(sid)){
sid = StringUtil.getUuid();
CookieUtil.setCookie(request, response, GlobalConstant. COOKID, sid, 60 * 60 );
}
//交给自定义的HttpServletRequestWrapper处理
filterChain.doFilter(new HttpServletRequestWrapper(sid, request, response), response);
}
}
}
@Service
public class SessionService {
private final static Logger LOG = Logger.getLogger(SessionService.class);
private JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer();
@Autowired
private RedisTemplate<String,Serializable> redisTemplate ;
@Autowired
private MyUserDetailsService myUserDetailsService;
@SuppressWarnings("unchecked")
public Map<String, Object> getSession(String sid , HttpServletRequest request) {
Map<String, Object> session = new HashMap<String, Object>();
try {
Object obj = redisTemplate.opsForValue()
.get(RedisKeyUtil.SESSION_DISTRIBUTED_SESSIONID+sid);
if(obj != null){
obj = jdkSerializer.deserialize((byte[])obj);
session = (Map<String, Object>) obj;
}
} catch (Exception e) {
LOG.error("Redis获取session异常" + e.getMessage(), e.getCause());
}
return session;
}
public void saveSession(String sid, Map<String, Object> session) {
try {
redisTemplate.opsForValue()
.set(RedisKeyUtil.SESSION_DISTRIBUTED_SESSIONID + sid,
jdkSerializer.serialize(session), RedisKeyUtil.SESSION_TIMEOUT,
TimeUnit.HOURS);
} catch (Exception e) {
LOG.error("Redis保存session异常" + e.getMessage(), e.getCause());
}
}
public void removeSession(String sid) {
try {
redisTemplate.delete(
RedisKeyUtil.SESSION_DISTRIBUTED_SESSIONID + sid);
} catch (Exception e) {
LOG.error("Redis删除session异常" + e.getMessage(), e.getCause());
}
}
}
最后来看一下redis里的存储结果:


本文介绍了在多台服务器间实现Tomcat Session共享的方法。通过自定义HttpSession和HttpServletRequestWrapper,结合Redis存储Session数据,解决了负载均衡下用户Session丢失的问题。
1万+

被折叠的 条评论
为什么被折叠?



