1. 程式人生 > >Redis實現分散式session功能的共享

Redis實現分散式session功能的共享

最近專案設計叢集,實現了一下session的共享功能,其原理是將session儲存到分散式快取資料庫中如:redis, memcache等,然後多個伺服器tomcat
每次請求都通過NoSql資料庫查詢,如果存在,則獲取值;反之存放值。
我是通過redis來實現session的共享,其主要有一下兩種方法:
1、通過tomcat伺服器的拓展功能實現
    這種方式比較簡單,主要是通過繼承session的ManagerBase類,實現重寫session相關的方法,這種比較簡單,
    參考原始碼連結(http://download.csdn.net/detail/fengshizty/9417242)。
2、通過filter攔截request請求實現
    下面主要介紹這樣實現方式:
    (1)寫HttpSessionWrapper實現HttpSession介面,實現裡面session相關的方法。
    (2)寫HttpServletRequestWrapper繼承javax.servlet.http.HttpServletRequestWrapper類,重寫對於session  相關的方法。
    (3)寫SessionFilter攔截配置的請求url,過去cookie中
    的sessionId,如果為空,對此次請求重寫生成一個新的sessionId,在sessionId構造新的HttpServletRequestWrapper物件。
    (4)寫SessionService實現session到redis的儲存和過去,其key為sessionId,value為session對於的Map。
3、程式碼實現
    (1)HttpSessionWrapper
/**
 * 建立時間:2016年1月21日 下午7:55:41
 * 
 * @author andy
 * @version 2.2
 */

public class HttpSessionWrapper implements HttpSession {

    private String sid = "";

    private HttpSession session;

    private HttpServletRequest request;

    private HttpServletResponse response;

    private Map<String, Object> map = null
; private SessionService sessionService = (SessionService) SpringContextHolder.getBean("sessionService"); public HttpSessionWrapper() { } public HttpSessionWrapper(HttpSession session) { this.session = session; } public HttpSessionWrapper(String sid, HttpSession session) { this
(session); this.sid = sid; } public HttpSessionWrapper(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); } 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(); } }
(2)HttpServletRequestWrapper實現
/**
 * 建立時間:2016年1月22日 下午7:52:29
 * 
 * @author andy
 * @version 2.2
 */

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 HttpSessionWrapper(sid, super.getSession(false),
                    request, response);
        }
    }

    @Override
    public HttpSession getSession(boolean create) {
        if (this.session == null) {
            if (create) {
                this.session = new HttpSessionWrapper(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 HttpSessionWrapper(this.sid, super.getSession(),
                    this.request, this.response);
        }
        return this.session;
    }

}
(3)SessionFilter攔截器的實現
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 {
        //從cookie中獲取sessionId,如果此次請求沒有sessionId,重寫為這次請求設定一個sessionId
        String sid = CookieUtil.getCookieValue(request, GlobalConstant.JSESSIONID);
        if(StringUtils.isEmpty(sid) || sid.length() != 36){
            sid = StringUtil.getUuid();
            CookieUtil.setCookie(request, response, GlobalConstant.JSESSIONID, sid, 60 * 60); 
        }

        //交給自定義的HttpServletRequestWrapper處理
        filterChain.doFilter(new HttpServletRequestWrapper(sid, request, response), response);
    }

}
(4)SessionService實現session從redis的讀寫儲存
public class SessionService {

    private final static Logger LOG = Logger.getLogger(SessionService.class);

    private JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer();

    @Autowired
    private RedisTemplate<Serializable, Serializable> redisTemplate;


    @SuppressWarnings("unchecked")
    public Map<String, Object> getSession(String sid) {
        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 {
            redisTemplates.opsForValue()
                    .set(RedisKeyUtil.SESSION_DISTRIBUTED_SESSIONID + sid,
                            jdkSerializer.serialize(session), RedisKeyUtil.SESSION_TIMEOUT,
                            TimeUnit.MINUTES);
        } catch (Exception e) {
            LOG.error("Redis儲存session異常" + e.getMessage(), e.getCause());
        }
    }

    public void removeSession(String sid) {
        try {
            redisTemplates.delete(
                    RedisKeyUtil.SESSION_DISTRIBUTED_SESSIONID + sid);
        } catch (Exception e) {
            LOG.error("Redis刪除session異常" + e.getMessage(), e.getCause());
        }
    }
}
(5)Session的攔截配置,一般的我們只需要攔截我們定義的攔截請求攔截,而不需要所有的都需要攔截。在web.xml中配置SessionFilter。
<filter>
        <filter-name>sessionFilter</filter-name>
        <filter-class>org.andy.shop.session.SessionFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>sessionFilter</filter-name>
        <url-pattern>*.do</url-pattern>
    </filter-mapping>

附:設計到的工具類

1、StringUtil工具類

/**
 * String工具類
 * 
 * @author andy
 * @date 2015-5-16 下午4:04:22
 * 
 */
public class StringUtil {

    private StringUtil() {
        super();
    }

    /**
     * 出去null和""
     * @param src
     * @return
     */
    public static String formatNull(String src) {
        return (src == null || "null".equals(src)) ? "" : src;
    }

    /**
     * 判斷字串是否為空的正則表示式,空白字元對應的unicode編碼
     */
    private static final String EMPTY_REGEX = "[\\s\\u00a0\\u2007\\u202f\\u0009-\\u000d\\u001c-\\u001f]+";

    /**
     * 驗證字串是否為空
     * 
     * @param input
     * @return
     */
    public static boolean isEmpty(String input) {
        return input == null || input.equals("") || input.matches(EMPTY_REGEX);
    }

    public static boolean isNotEmpty(String input){
        return !isEmpty(input);
    }

    public static String getUuid() {
        return UUID.randomUUID().toString();
    }
}

2、Cookie管理CookieUtil工具類

/**
 * 建立時間:2016年1月22日 下午8:33:56
 * 
 * @author andy
 * @version 2.2
 */

public class CookieUtil {
    private static final String KEY = "jkdflsffff()kldkjapfdY=::$B+DUOWAN";

    private HttpServletRequest request;

    private HttpServletResponse response;

    private static String domain = "andy.com";

    public CookieUtil(HttpServletRequest request, HttpServletResponse response) {
        this.request = request;
        this.response = response;
    }

    /**
     * 儲存cookie
     * @param request
     * @param response
     * @param name cookie名稱
     * @param value cookie值
     * @param seconds 過期時間(單位秒) -1代表關閉瀏覽器時cookie即過期
     */
    public static void setCookie(HttpServletRequest request,
            HttpServletResponse response, String name, String value, int seconds) {
        if (StringUtils.isEmpty(name) || StringUtils.isEmpty(value))
            return;
        Cookie cookie = new Cookie(name, value);
        //cookie.setDomain(domain);
        cookie.setMaxAge(seconds); 
        cookie.setPath("/");
        response.setHeader("P3P",
                "CP='IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT'");
        response.addCookie(cookie);
    }

    /**
     * 過去cookie中儲存的值
     * @param name
     * @return
     * @throws UnsupportedEncodingException
     */
    public String getCookieValue(String name)
            throws UnsupportedEncodingException {
        Cookie cookies[] = request.getCookies();
        if (cookies != null) {
            for (int i = 0; i < cookies.length; i++) {
                if (name.equalsIgnoreCase(cookies[i].getName())) {
                    return cookies[i].getValue();
                }
            }
        }
        return "";
    }

    /**
     * 設定加密的cookie
     * @param name cookie名稱
     * @param value cookie值
     * @param seconds 過期時間 -1代表關閉瀏覽器時cookie即過期
     */
    public void setCheckCodeCookie(String name, String value, int seconds) {
        if (StringUtils.isEmpty(name) || StringUtils.isEmpty(value)) {
            return;
        }
        String md5Value = MD5Utils.getMD5(KEY + value);
        Cookie cookie = new Cookie(name, md5Value);
        //cookie.setDomain(domain);
        cookie.setMaxAge(seconds);
        cookie.setPath("/");
        response.addCookie(cookie);
    }

    /**
     * 校驗加密的cookie
     * @param name
     * @param value
     * @return
     */
    public boolean checkCodeCookie(String name, String value) {
        if (StringUtils.isEmpty(name) || StringUtils.isEmpty(value)) {
            return false;
        }
        boolean result = false;
        String cookieValue = getCookieValue(request, name);
        if (MD5Utils.getMD5(KEY + value).equalsIgnoreCase(
                cookieValue)) {
            result = true;
        }
        return result;
    }

    /**
     * 獲取cookie值
     * @param request
     * @param name
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String name) {
        try {
            Cookie cookies[] = request.getCookies();
            if (cookies != null) {
                for (int i = 0; i < cookies.length; i++) {
                    if (name.equalsIgnoreCase(cookies[i].getName())) {
                        return cookies[i].getValue();
                    }
                }
            }
        } catch (Exception e) {
        }
        return "";
    }

    /**
     * 移除客戶端的cookie
     * @param request
     * @param response
     * @param name
     */
    public static void removeCookieValue(HttpServletRequest request,
            HttpServletResponse response, String name) {
        try {
            Cookie cookies[] = request.getCookies();
            if (cookies != null && cookies.length > 0) {
                for (Cookie cookie : cookies) {
                    if (name.equalsIgnoreCase(cookie.getName())) {
                        cookie = new Cookie(name, null);
                        cookie.setMaxAge(0);
                        cookie.setPath("/");
                        //cookie.setDomain(domain);
                        response.addCookie(cookie);
                        break;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3、redis鍵key設定類RedisKeyUtil

/**
 * 建立時間:2015年9月22日 下午4:51:11
 * 
 * @author andy
 * @version 2.2
 */

public class RedisKeyUtil {

    public static final String SESSION_DISTRIBUTED_SESSIONID = "session:distributed:"; //分散式session sessionid -- sessionvalue

    public static final Integer SESSION_TIMEOUT = 2; //session 失效時間2小時

}

4、分散式session常量設定類GlobalConstant

/**
 * 建立時間:2016年1月23日 上午11:16:56
 * 
 * 分散式session常量
 * 
 * @author andy
 * @version 2.2
 */

public class GlobalConstant {

    public static final String USER_SESSION_KEY = "user_session_key";//使用者session資訊

    public static final String JSESSIONID = "YHMJSESSIONID"; //jsessionid
}

分散式session在redis執行結果:
redis中session儲存結果