由 Redis負責 session 數據的存儲,而咱們本身實現的 session manager 將負責 session 生命週期的管理。java
通常的系統架構:web
此架構存在着當redis master故障時, 雖然能夠有一到多個備用slave,可是redis不會主動的進行master切換,這時session服務中斷。redis
爲了作到redis的高可用,引入了zookper或者haproxy或者keepalived來解決redis master slave的切換問題。即:apache
此體系結構中, redis master出現故障時, 經過haproxy設置redis slave爲臨時master, redis master從新恢復後,安全
再切換回去. 此方案中, redis-master 與redis-slave 是雙向同步的, 解決目前redis單點問題. 這樣保證了session信息服務器
在redis中的高可用。cookie
Shiro有默認的Session管理機制,經過MemorySessionDAO 進行Session的管理和維護。經過查看 org.apache.shiro.web.session.mgt.DefaultWebSessionManager 類繼承的DefaultSessionManager源碼,咱們能夠看到:session
public DefaultSessionManager() {
this.deleteInvalidSessions = true;
this.sessionFactory = new SimpleSessionFactory();
this.sessionDAO = new MemorySessionDAO();
}架構
因爲咱們採用redis來管理Session,因此須要配置本身的SessionDao,配置以下:ide
<!-- session管理器 --> <bean id="redisSessionDAO" class="com.shiro.session.RedisSessionDAO" /> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <property name="sessionDAO" ref="redisSessionDAO"></property> <!-- sessionIdCookie的實現,用於重寫覆蓋容器默認的JSESSIONID --> <property name="sessionIdCookie" ref="sharesession" /> <!-- 定時檢查失效的session --> <property name="sessionValidationSchedulerEnabled" value="true" /> </bean> <!-- sessionIdCookie的實現,用於重寫覆蓋容器默認的JSESSIONID --> <bean id="sharesession" class="org.apache.shiro.web.servlet.SimpleCookie"> <!-- cookie的name,對應的默認是 JSESSIONID --> <constructor-arg name="name" value="SHAREJSESSIONID" /> <!-- jsessionId的path爲 / 用於多個系統共享jsessionId --> <property name="path" value="/" /> <property name="httpOnly" value="true"/> </bean>
RedisSessionDao
public class RedisSessionDAO extends AbstractSessionDAO { private static Logger log = LoggerFactory.getLogger(RedisSessionDAO.class); @Autowired private RedisCache redisCache; private String keyPrefix = "shiro_redis_session:"; @Override protected Serializable doCreate(Session session) { Serializable sessionId = generateSessionId(session); assignSessionId(session, sessionId); storeSession(sessionId,session); log.debug("generate session id:"+sessionId); return sessionId; } private void storeSession(Serializable id, Session session) { if(id == null){ throw new NullPointerException("id argument cannot be null."); } byte[] key = getByteKey(session.getId()); byte[] value = SerializationUtils.serialize(session); int seconds = 1800; redisCache.addCache(key,value,seconds); } @Override protected Session doReadSession(Serializable sessionId) { if(sessionId == null){ throw new NullPointerException("id argument cannot be null."); } Session session = (Session)SerializationUtils.deserialize(redisCache.get(getByteKey(sessionId))); return session; } @Override public void update(Session session) throws UnknownSessionException { this.storeSession(session.getId(),session); } @Override public void delete(Session session) { if(session == null || session.getId() == null){ log.error("session or session id is null"); return; } byte[] key = getByteKey(session.getId()); redisCache.del(key); } @Override public Collection<Session> getActiveSessions() { Set<Session> sessions = new HashSet<Session>(); Set<byte[]> keys = redisCache.keys(this.keyPrefix + "*"); if(keys != null && keys.size()>0){ for(byte[] key:keys){ Session s = (Session)SerializationUtils.deserialize(redisCache.get(key)); sessions.add(s); } } return sessions; } /** * 得到byte[]型的key * @return */ private byte[] getByteKey(Serializable sessionId){ String preKey = this.keyPrefix + sessionId; return preKey.getBytes(); } }
RedisCache是關於Redis 服務器的一些基本操做(增刪改查),能夠本身找一個關於這方面的操做類。
有一個關鍵問題是在 updateSession的時候, RedisCache中set的時候,不管是否這個key存在, 都須要set進去;若是redis中已經存在key ,在updateSession時候沒有覆蓋原來的session值,會發生登陸失敗 的問題;而我翻看shiro自帶的SessionDao 是採用 MemorySessionDAO來管理Session的 ,查看源碼發現 MemorySessionDAO在updateSession的時候採用的以下的方式:
private ConcurrentMap<Serializable, Session> sessions;
public void update(Session session) throws UnknownSessionException {
storeSession(session.getId(), session);
}
protected Session storeSession(Serializable id, Session session) {
if (id == null) {
throw new NullPointerException("id argument cannot be null.");
}
return sessions.putIfAbsent(id, session);
}
注意紅色部分,MemorySessionDAO 管理session採用線程安全的ConcurrentMap管理,在更新的時候採用的是 putIfAbsent 方式,意思是:若是key不存在的狀況下才put session,存在則不會更新 Session,可是能夠登陸成功,研究了半天沒想明白。這個問題暫時隔着,不過不影響咱們用redis管理Session。