摘要:本文主要研究 基於 spring-seesion 解決分佈式 session 的共享問題。首先講述 session 共享問題的產生背景以及常見的解決方案;而後講解本文主要研究的 spring-session 的概念和功能;接着演示了 spring-session 的兩種管理 sessionid 的實現方案,屬於實戰內容,需重點掌握;再接着對後臺保存數據到 redis 上的數據結構進行了分析;而後對 spring-session 的核心源代碼進行了解讀,方便理解 spring-session 框架的實現原理;最後列舉了在使用 spring-session 的實踐過程當中可能遇到的問題或坑,重點去理解一下。html
HttpSession 是經過 Servlet 容器建立和管理的,像 Tomcat/Jetty 都是保存在內存中的。而若是咱們把 web 服務器搭建成分佈式的集羣,而後利用 LVS 或 Nginx 作負載均衡,那麼來自同一用戶的 Http 請求將有可能被分發到兩個不一樣的 web 站點中去。那麼問題就來了,如何保證不一樣的 web 站點可以共享同一份 session 數據呢?html5
最簡單的想法將 session 管理從容器中獨立出來。而實現方案有不少種,下面簡單介紹下:java
Spring Session 是 Spring 的項目之一,GitHub地址:https://github.com/spring-pro...。 git
Spring Session 提供了一套建立和管理 Servlet HttpSession 的完美方案。github
spring Session 提供了 API 和實現,用於管理用戶的 Session 信息。除此以外,它還提供了以下特性:web
<!-- spring-session-data-redis 是一個空的包,僅僅只有一個 META-INF 文件夾。它的做用就在於引入以下四個 包 spring-data-redis,jedis,spring-session,commons-pool2 --> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> <version>1.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.6.1.RELEASE</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.5.2</version> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session</artifactId> <version>1.1.0.RELEASE</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.2</version> </dependency>
<!-- redis 的 bean 配置以下 --> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"/> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <property name="hostName" value="127.0.0.1" /> <property name="port" value="6379" /> <property name="password" value="" /> <property name="timeout" value="3600" /> <property name="poolConfig" ref="jedisPoolConfig" /> <property name="usePool" value="true" /> <property name="database" value="0"/> <!-- 默認存放在0號庫中 --> </bean> <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory" /> </bean> <!-- 將 session 放入 redis, spring-session 會使用此 bean --> <bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"> <property name="maxInactiveIntervalInSeconds" value="1800" /> </bean>
這裏前面幾個 bean 都是操做 redis 時候使用的,最後一個 bean 纔是 spring-session 須要用到的,其中的 id 能夠不寫或者保持不變,這也是一個約定優先配置的體現。這個 bean 中又會自動產生多個 bean ,用於相關操做,極大的簡化了咱們的配置項。其中有個比較重要的是 springSessionRepositoryFilter ,它將在下面的代理 filter 中被調用到。maxInactiveIntervalInSeconds 表示超時時間,默認是 1800 秒。上述配置能夠採用 xml 來定義,官方文檔中有采用註解來聲明一個配置類。redis
接下來在 web.xml 中添加一個 session 代理 filter ,經過這個 filter 來包裝 Servlet 的 getSession() 。須要注意的是這個 filter 須要放在全部 filter 鏈最前面,從而保證徹底替換掉 tomcat 的 session。這個是約定。。算法
<!-- delegatingFilterProxy --> <filter> <filter-name>springSessionRepositoryFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSessionRepositoryFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
第一步:編寫 Controller 代碼spring
@RequestMapping(value = "user", method = RequestMethod.POST) public void setUser(HttpSession session) { User user = new User(); user.setName("lyf"); user.setPassword("123"); session.setAttribute("user", user); } @RequestMapping(value = "user", method = RequestMethod.GET) public String getUser(HttpSession session) { User user = (User) session.getAttribute("user"); String name = user.getName(); return "用戶名稱:" + name; }
第二步:瀏覽器中訪問 Controller數據庫
響應頭部以下:Response Headers:
Set-Cookie:SESSION=a2c10601-3204-454e-b545-85e84f587045; Path=/training/; HttpOnly ...
會發現瀏覽器 Cookie 中的 jsessionid 已經替換爲 session**
此時使用 redis-cli 到 redis 庫中查詢以下:
springsession:0>keys * 1) spring:session:sessions:a2c10601-3204-454e-b545-85e84f587045 2) spring:session:expirations:1502595600000
請求頭部以下:Request Headers:
Cookie:SESSION=a2c10601-3204-454e-b545-85e84f587045;
服務器經過 Cookie 中的 session 識別碼從 redis 庫中找到了須要的 session 對象並返回,瀏覽器顯示以下:
用戶名稱:lyf
經過如上 spring-session 配置便可將其集成到項目中,以後使用的全部有關 session 的操做,都會由 spring-session 來接管建立和信息存取。官方默認 spring-session 中的 session 信息都保存在 redis 數據庫中。
此實現方式弊端:若是瀏覽器禁用掉了 cookie 或者是非 web 請求時根本沒有 cookie 的時候,那麼如上經過cookie 管理 sessionid 的實現方式將不可以實現 session 共享。
同3.1
<!-- redis 的 bean 配置以下 --> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"/> <!-- 替代默認使用 cookie ,這裏使用的是 httpheader --> <bean id="httpSessonStrategy" class="org.springframework.session.web.http.HeaderHttpSessionStrategy"/> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <property name="hostName" value="127.0.0.1" /> <property name="port" value="6379" /> <property name="password" value="" /> <property name="timeout" value="3600" /> <property name="poolConfig" ref="jedisPoolConfig" /> <property name="usePool" value="true" /> </bean> <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory" /> </bean> <!-- 將 session 放入 redis --> <bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"> <property name="maxInactiveIntervalInSeconds" value="1800" /> <property name="httpSessionStrategy" ref="httpSessonStrategy"/> </bean>
同3.3
第一步:編寫 Controller代碼
@RequestMapping(value = "user", method = RequestMethod.POST) public void setUser(HttpSession session) { User user = new User(); user.setName("lyf"); user.setPassword("123"); session.setAttribute("user", user); } @RequestMapping(value = "user", method = RequestMethod.GET) public String getUser(HttpSession session) { User user = (User) session.getAttribute("user"); String name = user.getName(); return "用戶名稱:" + name; }
第二步:瀏覽器中訪問 Controller
響應頭部以下:Response Headers:
x-auth-token:256064c7-b583-460f-bbd2-1f6dab3fd418 ...
區別 Cookie 的地方在於,這種方式在響應頭信息中添加了惟一標識字段 x-auth-token
此時使用 redis-cli 到 redis 庫中查詢以下:
springsession:0>keys * 1) spring:session:expirations:1502597280000 2) spring:session:sessions:256064c7-b583-460f-bbd2-1f6dab3fd418
get 請求:localhost:8080/training/user
請求頭部以下:Response Headers:
x-auth-token:00ee4b6a-0aeb-42b1-a2bd-eae6f370c677
會發現此時在響應頭信息中又從新建立了一個 x-auth-token ,由於 spring-seesion 的底層實現是在請求的時候服務端若是沒有拿到這個惟一標識,就會從新建立一個新的 x-auth-token,
並保存到 redis 庫中。
此時使用 redis-cli 到 redis 庫中查詢以下:
springsession:0>keys * 1) spring:session:sessions:00ee4b6a-0aeb-42b1-a2bd-eae6f370c677 2) spring:session:expirations:1502597280000 3) spring:session:sessions:256064c7-b583-460f-bbd2-1f6dab3fd418 4) spring:session:expirations:1502597460000
所以要想獲取到 session 中的用戶信息,須要將服務端返回的 x-auth-token 惟一標識符附加到 Headers上,而後服務器根據這個惟一標識符才能找到對應的用戶信息
在此過程的 get 請求的 Headers 中添加以下鍵值對:
x-auth-token:256064c7-b583-460f-bbd2-1f6dab3fd418
服務器經過 Headers 中的 x-auth-token 從 redis 庫中找到了須要的 session 對象並返回,瀏覽器顯示以下:
用戶名稱:lyf
所以:
Spring-session 能夠控制客戶端和服務器端之間如何進行 sessionid 的交換,這樣更加易於編寫 Restful API,由於它能夠從 HTTP 頭信息中獲取 sessionid ,而沒必要再依賴於 cookie 。
RedisSession 在建立時設置 3 個變量 creationTime ,maxInactiveInterval ,lastAccessedTime 。maxInactiveInterval 默認值爲 1800 ,表示 1800s 以內該 session 沒有被再次使用,則代表該 session 已過時。每次 session 被訪問都會更新 lastAccessedTime 的值, session 的過時計算公式:當前時間-lastAccessedTime > maxInactiveInterval
.
/** * Creates a new instance ensuring to mark all of the new attributes to be * persisted in the next save operation. **/ RedisSession() { this(new MapSession()); this.delta.put(CREATION_TIME_ATTR, getCreationTime()); this.delta.put(MAX_INACTIVE_ATTR, getMaxInactiveIntervalInSeconds()); this.delta.put(LAST_ACCESSED_ATTR, getLastAccessedTime()); this.isNew = true; this.flushImmediateIfNecessary(); } public MapSession() { this(UUID.randomUUID().toString()); }
flushImmediateIfNecessary 判斷 session 是否須要當即寫入後端存儲。
spring session在 redis 裏面保存的數據包括:
spring:session:expireations:[min]
min 表示從 1970 年 1 月 1 日 0 點 0 分通過的分鐘數, SET 集合的 member 爲 expires:[sessionId] ,表示 members 會在 min 分鐘後過時。
spring:session:sessions:expires:[sessionId]
該數據的 TTL 表示 sessionId 過時的剩餘時間,即 maxInactiveInterval。
spring:session:sessions:[sessionId]
session 保存的數據,記錄了 creationTime,maxInactiveInterval,lastAccessedTime,attribute。前兩個數據是用於 session 過時管理的輔助數據結構。
獲取 session 流程:
應用經過 getSession(boolean create) 方法來獲取 session 數據,參數 create 表示 session 不存在時是否建立新的 session 。 getSession 方法首先從請求的 「.CURRENT_SESSION」 屬性來獲取 currentSession ,沒有 currentSession ,則從 request 取出 sessionId ,而後讀取 spring:session:sessions:[sessionId] 的值,同時根據 lastAccessedTime 和 MaxInactiveIntervalInSeconds 來判斷這個 session 是否過時。若是 request 中沒有 sessionId ,說明該用戶是第一次訪問,會根據不一樣的實現,如 RedisSession ,MongoExpiringSession ,GemFireSession 等來建立一個新的 session 。
另外, 從 request 取 sessionId 依賴具體的 HttpSessionStrategy 的實現,spring session 給了兩個默認的實現 CookieHttpSessionStrategy 和 HeaderHttpSessionStrategy ,即從 cookie 和 header 中取出 sessionId 。
具體的代碼實如今第 4 章已經演示了。
spring session 的有效期指的是訪問有效期,每一次訪問都會更新 lastAccessedTime 的值,過時時間爲lastAccessedTime + maxInactiveInterval ,也即在有效期內每訪問一次,有效期就向後延長 maxInactiveInterval。
對於過時數據,通常有三種刪除策略:
1)定時刪除,即在設置鍵的過時時間的同時,建立一個定時器, 當鍵的過時時間到來時,當即刪除。
2)惰性刪除,即在訪問鍵的時候,判斷鍵是否過時,過時則刪除,不然返回該鍵值。
3)按期刪除,即每隔一段時間,程序就對數據庫進行一次檢查,刪除裏面的過時鍵。至於要刪除多少過時鍵,以及要檢查多少個數據庫,則由算法決定。
redis 刪除過時數據採用的是懶性刪除+按期刪除
組合策略,也就是數據過時了並不會及時被刪除。爲了實現 session 過時的及時性,spring session 採用了定時刪除的策略,但它並非如上描述在設置鍵的同時設置定時器,而是採用固定頻率(1分鐘)輪詢刪除過時值,這裏的刪除是惰性刪除。
輪詢操做並無去掃描全部的 spring:session:sessions:[sessionId] 的過時時間,而是在當前分鐘數檢查前一分鐘應該過時的數據,即 spring:session:expirations:[min] 的 members ,而後 delete 掉 spring:session:expirations:[min] ,惰性刪除 spring:session:sessions:expires:[sessionId] 。
還有一點是,查看三個數據結構的TTL時間,spring:session:sessions:[sessionId] 和 spring:session:expirations:[min] 比真正的有效期大 5 分鐘,目的是確保當 expire key 數據過時後,監聽事件還能獲取到 session 保存的原始數據。
@Scheduled(cron = "${spring.session.cleanup.cron.expression:0 * * * * *}") public void cleanupExpiredSessions() { this.expirationPolicy.cleanExpiredSessions(); } public void cleanExpiredSessions() { long now = System.currentTimeMillis(); long prevMin = roundDownMinute(now); // preMin 時間到,將 spring:session:expirations:[min], // set 集合中 members 包括了這一分鐘以內須要過時的全部 // expire key 刪掉, member 元素爲 expires:[sessionId] String expirationKey = getExpirationKey(prevMin); Set<Object> sessionsToExpire = this.redis.boundSetOps(expirationKey).members(); this.redis.delete(expirationKey); for (Object session : sessionsToExpire) { // sessionKey 爲 spring:session:sessions:expires:[sessionId] String sessionKey = getSessionKey((String) session); // 利用 redis 的惰性刪除策略 touch(sessionKey); } }
spring session 在 redis 中保存了三個 key ,爲何? sessions key 記錄 session 自己的數據,expires key標記 session 的準確過時時間,expiration key 保證 session 可以被及時刪除,spring 監聽事件可以被及時處理。
上面的代碼展現了 session expires key 如何被刪除,那 session 每次都是怎樣更新過時時間的呢? 每一次 http 請求,在通過全部的 filter 處理事後,spring session 都會經過 onExpirationUpdated() 方法來更新 session 的過時時間, 具體的操做看下面源碼的註釋。
public void onExpirationUpdated(Long originalExpirationTimeInMilli, ExpiringSession session) { String keyToExpire = "expires:" + session.getId(); long toExpire = roundUpToNextMinute(expiresInMillis(session)); if (originalExpirationTimeInMilli != null) { long originalRoundedUp = roundUpToNextMinute(originalExpirationTimeInMilli); // 更新 expirations:[min] ,兩個分鐘數以內都有這個 session ,將前一個 set 中的成員刪除 if (toExpire != originalRoundedUp) { String expireKey = getExpirationKey(originalRoundedUp); this.redis.boundSetOps(expireKey).remove(keyToExpire); } } long sessionExpireInSeconds = session.getMaxInactiveIntervalInSeconds(); String sessionKey = getSessionKey(keyToExpire); if (sessionExpireInSeconds < 0) { this.redis.boundValueOps(sessionKey).append(""); this.redis.boundValueOps(sessionKey).persist(); this.redis.boundHashOps(getSessionKey(session.getId())).persist(); return; } String expireKey = getExpirationKey(toExpire); BoundSetOperations<Object, Object> expireOperations = this.redis .boundSetOps(expireKey); expireOperations.add(keyToExpire); long fiveMinutesAfterExpires = sessionExpireInSeconds + TimeUnit.MINUTES.toSeconds(5); // expirations:[min] key 的過時時間加 5 分鐘 expireOperations.expire(fiveMinutesAfterExpires, TimeUnit.SECONDS); if (sessionExpireInSeconds == 0) { this.redis.delete(sessionKey); } else { // expires:[sessionId] 值爲「」,過時時間爲 MaxInactiveIntervalInSeconds this.redis.boundValueOps(sessionKey).append(""); this.redis.boundValueOps(sessionKey).expire(sessionExpireInSeconds, TimeUnit.SECONDS); } // sessions:[sessionId] 的過時時間加 5 分鐘 this.redis.boundHashOps(getSessionKey(session.getId())) .expire(fiveMinutesAfterExpires, TimeUnit.SECONDS); }
使用 spring-session 須要解決兩個核心問題:
問題一:如何建立集羣環境下高可用的 session,要求可以可靠並高效地存儲數據
解決:在高可用可擴展的集羣中存儲數據已經經過各類數據存儲方案獲得瞭解決,如 Redis、GemFire 以及 Apache Geode 等等
問題二:如何保證無論請求是 HTTP、WebSocket 等其餘協議,服務端都可以獲取到 sessionid 來找到對應的資源
解決:Spring Session 認爲將請求與特定的 session 實例關聯起來的問題是與協議相關的,由於在請求/響應週期中,客戶端和服務器之間須要協商贊成一種傳遞 sessionid 的方式。例如,若是請求是經過 HTTP 傳遞進來的,那麼 session 能夠經過 HTTP cookie 或 HTTP Header 信息與請求進行關聯。若是使用 HTTPS 的話,那麼能夠藉助SSL sessionid 實現請求與 session 的關聯。若是使用 JMS 的話,那麼 JMS 的 Header 信息可以用來存儲請求和響應之間的 sessionid 。
Spring Session 對 HTTP 的支持是經過標準的 servlet filter 來實現的,這個 filter 必需要配置爲攔截全部的 web 應用請求,而且它應該是 filter 鏈中的第一個 filter 。Spring Session filter 會確保隨後調用javax.servlet.http.HttpServletRequest
的getSession()
方法時,都會返回 Spring Session 的HttpSession
實例,而不是應用服務器默認的 HttpSession。
首先,咱們瞭解一下標準 servlet 擴展點的一些背景知識:
在2001年,Servlet 2.3規範引入了ServletRequestWrapper
。官方API中解釋,ServletRequestWrapper
「提供了ServletRequest
接口的便利實現,開發人員若是但願將請求適配到 Servlet 的話,能夠編寫它的子類。這個類實現了包裝(Wrapper)或者說是裝飾(Decorator)模式。對方法的調用默認會經過包裝的請求對象來執行」。以下的代碼樣例抽取自 Tomcat,展示了 ServletRequestWrapper 是如何實現的。
public class ServletRequestWrapper implements ServletRequest { private ServletRequest request; /** * 建立 ServletRequest 適配器,它包裝了給定的請求對象。 */ public ServletRequestWrapper(ServletRequest request) { if (request == null) { throw new IllegalArgumentException("Request cannot be null"); } this.request = request; } public ServletRequest getRequest() { return this.request; } public Object getAttribute(String name) { return this.request.getAttribute(name); } }
Servlet 2.3 規範還定義了HttpServletRequestWrapper
,它是ServletRequestWrapper
的子類,可以快速提供HttpServletRequest
的自定義實現,以下的代碼是從 Tomcat 抽取出來的,展示了HttpServletRequesWrapper
類是如何運行的。
public class HttpServletRequestWrapper extends ServletRequestWrapper implements HttpServletRequest { public HttpServletRequestWrapper(HttpServletRequest request) { super(request); } private HttpServletRequest _getHttpServletRequest() { return (HttpServletRequest) super.getRequest(); } public HttpSession getSession(boolean create) { return this._getHttpServletRequest().getSession(create); } public HttpSession getSession() { return this._getHttpServletRequest().getSession(); } }
因此,藉助這些包裝類就能編寫代碼來擴展HttpServletRequest
,重載返回HttpSession
的方法,讓它返回由外部存儲所提供的實現。以下的代碼是從 Spring Session 項目中提取出來的。
/* * 注意,Spring Session 項目定義了擴展自 * 標準 HttpServletRequestWrapper 的類,用來重載 * HttpServletRequestWrapper 中與 session 相關的方法。 */ private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper { private HttpSessionWrapper currentSession; private Boolean requestedSessionIdValid; private boolean requestedSessionInvalidated; private final HttpServletResponse response; private final ServletContext servletContext; /* * 注意,這個構造器很是簡單,它接收稍後會用到的參數, * 而且委託給它所擴展的 HttpServletRequestWrapper */ private SessionRepositoryRequestWrapper( HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) { super(request); this.response = response; this.servletContext = servletContext; } /* * 在這裏,Spring Session 項目再也不將調用委託給 * 應用服務器,而是實現本身的邏輯, * 返回由外部數據存儲做爲支撐的 HttpSession 實例。 * * @Param create 參數表示 session 不存在時是否建立新的 session */ @Override public HttpSession getSession(boolean create) { // 檢查是否存在 session ,若是存在,則直接返回 if(currentSession != null) { return currentSession; } // 檢查當前的請求中是否存在 sessionid String requestedSessionId = getRequestedSessionId(); if(requestedSessionId != null) { // 若是存在 sessionid ,將會根據這個 sessionid,從它的 SessionRepository 中加載 session S session = sessionRepository.getSession(requestedSessionId); if(session != null) { // 封裝 session 並返回 this.requestedSessionIdValid = true; currentSession = new HttpSessionWrapper(session, getServletContext()); currentSession.setNew(false); return currentSession; } } if(!create) { return null; } // session repository 中沒有 session ,而且在當前請求中也沒有與之關聯的 sessoinid, // 那麼就建立一個新的 session ,並將其持久化到 session repository 中 S session = sessionRepository.createSession(); currentSession = new HttpSessionWrapper(session, getServletContext()); return currentSession; } @Override public HttpSession getSession() { return getSession(true); } }
Spring Session 定義了SessionRepositoryFilter
,它實現了 Servlet Filter
接口。以下是抽取了這個 filter的關鍵部分
/* * SessionRepositoryFilter 只是一個標準的 ServletFilter, * 它的實現擴展了一個 helper 基類。 */ public class SessionRepositoryFilter < S extends ExpiringSession > extends OncePerRequestFilter { /* * 這個方法是魔力真正發揮做用的地方。這個方法至關於重寫了doFilter, * 建立了咱們上文所述的封裝請求對象和 * 一個封裝的響應對象,而後調用其他的 filter 鏈。 * 這裏,關鍵在於當這個 filter 後面的應用代碼執行時, * 若是要得到 session 的話,獲得的將會是 Spring Session 的 * HttpServletSession 實例,它是由後端的外部數據存儲做爲支撐的。 */ protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { request.setAttribute(SESSION_REPOSITORY_ATTR, sessionRepository); SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request,response,servletContext); SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest, response); HttpServletRequest strategyRequest = httpSessionStrategy.wrapRequest(wrappedRequest, wrappedResponse); HttpServletResponse strategyResponse = httpSessionStrategy.wrapResponse(wrappedRequest, wrappedResponse); try { filterChain.doFilter(strategyRequest, strategyResponse); } finally { wrappedRequest.commitSession(); } } }
總結:經過對 spring-session 核心源碼的分析獲得的關鍵信息是,Spring Session 對 HTTP 的支持所依靠的是一個簡單老式的ServletFilter
,藉助 servlet 規範中標準的特性來實現 Spring Session 的功能。所以,咱們可以讓已有的 war 文件使用 Spring Session 的功能,而無需修改已有的代碼。
默認狀況下,session 存儲在 redis 的 key 是「spring:session::」,但若是有多個系統同時使用一個 redis,則會衝突,此時應該配置 redisNamespace 值,配置後,其 key 爲 spring:session:devlops:keyName
配置 redisNamesapce 的方式,在以前配置文件的 bean 中添加一個屬性便可
<!-- 將session放入redis --> <bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"> <property name="maxInactiveIntervalInSeconds" value="1800" /> <property name="redisNamespace" value="${redisNamespace}"/> </bean>
注意:spring-session 的版本在 1.1.0 及以上才支持命名空間
Serializable
接口,這樣 Spring-session 才能對保存的對象進行序列化,從而存儲在 redis 裏若是選用 redis 雲服務,使用過程當中會出現異常,異常緣由是:不少 Redis 雲服務提供商考慮到安全因素,會禁用掉 Redis 的 config 命令,所以須要咱們手動在雲服務後臺管理系統手動配置,或者找雲服務售後幫忙配置。而後咱們在配置文件 RedisHttpSessionConfiguration 的 bean 中添加以下配置,解決使用 redis 雲服務異常問題
<!-- 讓Spring Session再也不執行config命令 --> <util:constant static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"> </util:constant>
注意:判斷 config 命令是否被禁用,能夠在 redis 的命令行去使用 config 命令,若是報沒有找到該命令,說明 config 命令被禁用了。