Redis 上的 Spring 会话 - Redis 宕机时的故障转移是什么

问题描述 投票:0回答:2
我正在使用 Spring 和 Spring Security,并且希望将 spring-session-data-redis 与 RedisHttpSessionConfiguration 一起使用,以在 Redis 上存储会话 ID(这样当 Web 应用程序失败并切换到另一台服务器时,客户端不会丢失会话)。

我的问题,当Redis服务器宕机时会发生什么? 在Redis恢复之前,Spring是否能够通过将Session存储在内存中来继续工作?有没有办法这样配置?

我在 AWS ElastiCache 上使用 Redis,在 DNS 上配置替换主节点之前,故障转移可能需要几分钟时间。

spring caching spring-security redis amazon-elasticache
2个回答
2
投票
我已经设法在 Redis 无法访问时对内存中会话实现故障转移机制。不幸的是,这不能仅通过 Spring 属性来完成,因此您必须实现自定义 SessionRepository 并将其配置为使用 SessionRepositoryFilter,每当 Redis 无法访问时,它将故障转移到内存缓存。

import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Primary; import org.springframework.session.MapSession; import org.springframework.session.Session; import org.springframework.session.SessionRepository; import org.springframework.stereotype.Component; @Component("customSessionRepository") @Primary public class CustomFailoverToMapSessionRepository implements SessionRepository { private static final Logger LOGGER = LoggerFactory.getLogger(CustomFailoverToMapSessionRepository.class); private GuavaBasedSessionRepository guavaBasedSessionRepository; private SessionRepository sessionRepository; public CustomFailoverToMapSessionRepository(SessionRepository sessionRepository, GuavaBasedSessionRepository guavaBasedSessionRepository) { this.sessionRepository = sessionRepository; this.guavaBasedSessionRepository = guavaBasedSessionRepository; } @Override public Session createSession() { Session session = null; MapSession mapSession = guavaBasedSessionRepository.createSession(); try { session = sessionRepository.createSession(); mapSession = toMapSession(session); } catch (Exception e) { LOGGER.warn("Unexpected exception when trying to create a session will create just an in memory session", e); } return session == null ? mapSession : session; } @Override public void save(Session session) { try { if (!isOfMapSession(session)) { sessionRepository.save(session); } } catch (Exception e) { LOGGER.warn("Unexpected exception when trying to save a session with id {} will create just an in memory session", session.getId(), e); } guavaBasedSessionRepository.save(toMapSession(session)); } @Override public Session findById(String id) { try { return sessionRepository.findById(id); } catch (Exception e) { LOGGER.warn("Unexpected exception when trying to lookup a session with id {}", id, e); return guavaBasedSessionRepository.findById(id); } } @Override public void deleteById(String id) { try { try { guavaBasedSessionRepository.deleteById(id); } catch (Exception e) { //ignored } sessionRepository.deleteById(id); } catch (Exception e) { LOGGER.warn("Unexpected exception when trying to delete a session with id {}", id, e); } } private boolean isOfMapSession(Session session) { return session instanceof MapSession; } private MapSession toMapSession(Session session) { final MapSession mapSession = guavaBasedSessionRepository.createSession(); if (session != null) { mapSession.setId(session.getId()); mapSession.setCreationTime(session.getCreationTime()); mapSession.setLastAccessedTime(session.getLastAccessedTime()); mapSession.setMaxInactiveInterval(session.getMaxInactiveInterval()); session.getAttributeNames() .forEach(attributeName -> mapSession.setAttribute(attributeName, session.getAttribute(attributeName))); } return mapSession; }
使用Guava实现内存缓存会话存储库

import com.google.common.annotations.VisibleForTesting; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import org.springframework.beans.factory.annotation.Value; import org.springframework.session.MapSession; import org.springframework.session.Session; import org.springframework.session.SessionRepository; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.time.Duration; import java.util.concurrent.TimeUnit; @Component("guavaBasedSessionRepository") public class GuavaBasedSessionRepository implements SessionRepository<MapSession> { private Cache<String, Session> sessionCache; @Value("${session.local.guava.cache.maximum.size}") private int maximumCacheSize; @Value("${redis.session.keys.timeout}") private long sessionTimeout; @PostConstruct void init(){ sessionCache = CacheBuilder .newBuilder() .maximumSize(maximumCacheSize) .expireAfterWrite(sessionTimeout, TimeUnit.MINUTES) .build(); } @Override public void save(MapSession session) { if (!session.getId().equals(session.getOriginalId())) { this.sessionCache.invalidate(session.getOriginalId()); } this.sessionCache.put(session.getId(), new MapSession(session)); } @Override public MapSession findById(String id) { Session saved = null; try { saved = this.sessionCache.getIfPresent(id); } catch (Exception e){ //ignored } if (saved == null) { return null; } if (saved.isExpired()) { deleteById(saved.getId()); return null; } return new MapSession(saved); } @Override public void deleteById(String id) { this.sessionCache.invalidate(id); } @Override public MapSession createSession() { MapSession result = new MapSession(); result.setMaxInactiveInterval(Duration.ofSeconds(sessionTimeout)); return result; }
配置 Spring 使用自定义 SessionRepository

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.session.Session; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; import org.springframework.session.web.http.CookieHttpSessionIdResolver; import org.springframework.session.web.http.CookieSerializer; import org.springframework.session.web.http.SessionRepositoryFilter; import javax.annotation.PostConstruct; @EnableRedisHttpSession @Configuration public class CustomSessionConfig { private CookieHttpSessionIdResolver defaultHttpSessionIdResolver = new CookieHttpSessionIdResolver(); @Autowired private CookieSerializer cookieSerializer; @PostConstruct public void init(){ this.defaultHttpSessionIdResolver.setCookieSerializer(cookieSerializer); } @Bean @Primary public <S extends Session> SessionRepositoryFilter<? extends Session> sessionRepositoryFilter(CustomFailoverToMapSessionRepository customSessionRepository) { SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<>(customSessionRepository); sessionRepositoryFilter.setHttpSessionIdResolver(this.defaultHttpSessionIdResolver); return sessionRepositoryFilter; }
    

1
投票
据我所知,您需要提供

CacheErrorHandler

 ( 
javadoc) 的实现。

您可以通过提供一个

Configuration

 实例来实现此目的,该实例实现 
CachingConfigurer
,并重写 
errorHandler()
 方法。

例如:

@Configuration @Ena1bleCaching public class MyApp extends SpringBootServletInitializer implements CachingConfigurer { @Override public CacheErrorHandler errorHandler() { return MyAppCacheErrorHandler(); } }

我不清楚您将如何提供不间断的服务 - 如果不复制故障转移缓存中的当前会话,这似乎是不可能的。

如果您使用 ElasticCache,是否可以让 AWS 为您处理复制设置,以便在一个节点发生故障时,另一个节点可以接管?

© www.soinside.com 2019 - 2024. All rights reserved.