Distributed session sharing: session+redis+nginx

Some time ago, my brother let me touch the session sharing module, because some projects run on two servers, and sometimes after load balancing, the customer will suddenly jump to the login interface, and I will no longer do it here about why the session should be shared. More to say. Record your own implementation process here for future reference and improvement.

The project is a maven project, running on the tomcat server, and there is no excessive introduction to tomcat, maven, redis, and 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 client configuration -->
    <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>

 This is some relevant configuration before the logic implementation. Regarding the logic of session implementation, the initial idea is to take out the HttpSession when the login verification is successful. What request.getSession() gets is a StandardSessionFacade object, and StandardSessionFacade is an HttpSession The implementation class of this object serializes and stores it in redis (and stores a tag in HttpSession). Each subsequent request will verify the tag we saved in HttpSession in a Filter. If the tag does not exist, it may not be accessing the same server, and then go to redis to get it. One problem with this idea is that when serializing the stored redis, the serialization is always wrong. I started to use Json to serialize the StandardSessionFacade object, and found that the serialization was always unsuccessful (at this time, I didn’t have the awareness of handwriting Json serialization), Later, the serialization method of JDK was also unsuccessful. The reason was that many objects stored in the attribute field in the session did not implement the serializable interface, but the objects were too complicated to be changed manually, so I gave up this idea.

A later implementation idea is to write a HttpSession implementation class UserSession, and also implement HttpServletRequestWrapper, write a SessionFilter, and replace the request with the class we implemented before all Filters. At the same time, this class getSession calls the The UserSession we wrote uses the Hashmap we define in UserSession to store the content of the session, and access it in redis in real time. The same method also caused the problem of non-serialization. Later, it was found that the object about user permissions was not serializable. The final solution was to store the key information of user permissions in redis in string type, and it was necessary to take out the information about user permissions from redis. When the information of the permission is obtained, it can be restored to the related object about the permission through the valid information.

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 );

            }
           
            //Hand over to the custom HttpServletRequestWrapper for processing
            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 gets session exception" + 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 delete session exception" + e.getMessage(), e.getCause());
        }
    }
}

 Finally, let's take a look at the storage results in redis:

 

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326079112&siteId=291194637