1、 前置知識git
1. redis 在鍵實際過時以後不必定會被刪除,可能會繼續存留github
2. 具備過時時間的 key 有兩種方式來保證過時redis
一是這個鍵在過時的時候被訪問了spring
二是後臺運行一個定時任務本身刪除過時的 keysession
劃重點:這啓發咱們在 key 到期後只須要訪問一下 key 就能夠確保 redis 刪除該過時鍵併發
2、三種類型的鍵app
192.168.1.251:6379> type spring:session:sessions:804f5333-e5dc-48c8-a3d3-86e832f41045 hash 192.168.1.251:6379> hgetall spring:session:sessions:804f5333-e5dc-48c8-a3d3-86e832f41045 1) "lastAccessedTime" 2) "1546913894340" 3) "sessionAttr:_SESSION_CACHE_PREFIX_" 4) "{\"@class\":\"com.reals.session.SessionInfo\",\"mainBindId\":1,\"bindIds\":null,\"phone\":null,\"loginMode\":null,\"openId\":\"o6kAJ4z4LvyPao\",\"platform\":\"Miniprogram\",\"sid\":\"804f5333-e5dc-48c8-a3d3-86e832f41045\",\"validSeconds\":2678400,\"session_key\":\"bBhW9tWg==\"}" 5) "maxInactiveInterval" 6) "2678400" 7) "creationTime" 8) "1546913846141" 192.168.1.251:6379> type spring:session:expirations:1549592340000 set 192.168.1.251:6379> 192.168.1.251:6379> smembers spring:session:expirations:1549592340000 1) "\"expires:804f5333-e5dc-48c8-a3d3-86e832f41045\"" 92.168.1.251:6379> type spring:session:sessions:expires:804f5333-e5dc-48c8-a3d3-86e832f41045 string 192.168.1.251:6379> get spring:session:sessions:expires:804f5333-e5dc-48c8-a3d3-86e832f41045 ""
A型鍵(Hash):spring:session:sessions:2ce8e358-3c23-4233-af40-a338deb0691f
B型鍵(Set):spring:session:expirations:1550627520000
C型鍵(String):spring:session:sessions:expires:2ce8e358-3c23-4233-af40-a338deb0691fthis
A/B類型的鍵ttl比C的長5分鐘spa
3、運行機制debug
1. 定時任務每分鐘查找spring:session:expirations:{timestamp}的值
RedisSessionExpirationPolicy.cleanExpiredSessions public void cleanExpiredSessions() { long now = System.currentTimeMillis(); long prevMin = roundDownMinute(now); //看到是set操做,是B型鍵 String expirationKey = getExpirationKey(prevMin); Set<Object> sessionsToExpire = this.redis.boundSetOps(expirationKey).members(); this.redis.delete(expirationKey); //B型鍵有三種類型的值,以下示例 for (Object session : sessionsToExpire) { String sessionKey = getSessionKey((String) session); touch(sessionKey); } }
參考github issue併發致使的問題
/** * By trying to access the session we only trigger a deletion if it the TTL is * expired. This is done to handle * https://github.com/spring-projects/spring-session/issues/93 * * @param key the key */ private void touch(String key) { this.redis.hasKey(key); }
2. B類型鍵的值
# 1. 已過時,已被刪除的鍵。 # 2. 已過時,可是還沒來得及被 redis 清除的 key。在 key 到期後只須要訪問一下 key 就能夠確保 redis 刪除該過時鍵 # 3. 併發問題致使的多餘數據,實際上並未過時。 192.168.0.200:6379[2]> smembers spring:session:expirations:1550627520000 1) "\"86719669-9214-4dfa-952d-e4a956a201c2\"" 192.168.0.200:6379[2]> 192.168.0.200:6379[2]> smembers spring:session:expirations:1549766100000 # RedisSessionExpirationPolicy.onExpirationUpdated 在這裏加了下面這種類型的值 1) "\"expires:00e801a5-30dd-4e12-8398-ac9b9336e3b1\""
3. RedisSessionExpirationPolicy.onExpirationUpdated
public void onExpirationUpdated(Long originalExpirationTimeInMilli, Session session) { String keyToExpire = "expires:" + session.getId(); long toExpire = roundUpToNextMinute(expiresInMillis(session)); //刪除B型鍵的舊值 if (originalExpirationTimeInMilli != null) { long originalRoundedUp = roundUpToNextMinute(originalExpirationTimeInMilli); if (toExpire != originalRoundedUp) { String expireKey = getExpirationKey(originalRoundedUp); this.redis.boundSetOps(expireKey).remove(keyToExpire); } } long sessionExpireInSeconds = session.getMaxInactiveInterval().getSeconds(); //C型鍵spring:session:sessions:expires:2ce8e358-3c23-4233-af40-a338deb0691f 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; } //B型鍵spring:session:expirations:1550627520000 String expireKey = getExpirationKey(toExpire); BoundSetOperations<Object, Object> expireOperations = this.redis .boundSetOps(expireKey); expireOperations.add(keyToExpire); long fiveMinutesAfterExpires = sessionExpireInSeconds + TimeUnit.MINUTES.toSeconds(5); //A、B型鍵的過時時間加多5分鐘 expireOperations.expire(fiveMinutesAfterExpires, TimeUnit.SECONDS); if (sessionExpireInSeconds == 0) { this.redis.delete(sessionKey); } else { this.redis.boundValueOps(sessionKey).append(""); this.redis.boundValueOps(sessionKey).expire(sessionExpireInSeconds, TimeUnit.SECONDS); } this.redis.boundHashOps(getSessionKey(session.getId())) .expire(fiveMinutesAfterExpires, TimeUnit.SECONDS); }
You will note that the expiration that is set is 5 minutes after the session
actually expires. This is necessary so that the value of the session can be
accessed when the session expires. An expiration is set on the session itself
five minutes after it actually expires to ensure it is cleaned up, but only
after we perform any necessary processing.
4.刪除String類型鍵spring:session:sessions:expires觸發鍵空間通知
public void onMessage(Message message, byte[] pattern) { byte[] messageChannel = message.getChannel(); byte[] messageBody = message.getBody(); String channel = new String(messageChannel); if (channel.startsWith(getSessionCreatedChannelPrefix())) { // TODO: is this thread safe? Map<Object, Object> loaded = (Map<Object, Object>) this.defaultSerializer .deserialize(message.getBody()); handleCreated(loaded, channel); return; } String body = new String(messageBody); //C型鍵spring:session:sessions:expires才繼續執行 if (!body.startsWith(getExpiredKeyPrefix())) { return; } boolean isDeleted = channel.endsWith(":del"); if (isDeleted || channel.endsWith(":expired")) { int beginIndex = body.lastIndexOf(":") + 1; int endIndex = body.length(); String sessionId = body.substring(beginIndex, endIndex); RedisSession session = getSession(sessionId, true); if (session == null) { logger.warn("Unable to publish SessionDestroyedEvent for session " + sessionId); return; } if (logger.isDebugEnabled()) { logger.debug("Publishing SessionDestroyedEvent for session " + sessionId); } cleanupPrincipalIndex(session); if (isDeleted) { handleDeleted(session); } else { handleExpired(session); } } }