分布式session共享:session+redis+nginx

前一段时间师兄让我接触到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里的存储结果:

 
分布式session共享:session+redis+nginx
 

相关推荐