服务集群部署时,如何解决session共享
当我们对后台服务做集群部署时,必须考虑下session共享,作者想到了三种方式:
1、nginx负载均衡基于ip_hash,每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题
upstream backserver {
ip_hash;
server 192.168.0.1:8080;
server 192.168.0.2:8080;
}
2、nginx负载均衡基于url_hash,按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效
3、以上两种方式都存在一个不可忽视的问题,如果某台后端服务器宕机了,这个服务器上的session就没了,因此作者这里使用了spring + shiro + redis
shiro 做安全管理, redis存储session,改动不大,思路是在session存储时,不使用原cache存储,而是用存到redis服务上, shiro配置如下:
1)、缓存使用我们配置的redis,因此不使用shiro自有的cacheManager
2)、session存储,使用我们的写的RedisSessionDAO
3)、sessionId保存在Cookie中,Tomcat运行下和默认的区别下
4)、RedisSessionDAO继承AbstractSessionDAO ,作者使用的是xml配置
作者使用的RedisTemplate是spring-data-redis中的,少写一些代码,我们也可以自己用jedis写一个
public class RedisSessionDAO extends AbstractSessionDAO { private static Logger logger = LoggerFactory.getLogger(RedisSessionDAO.class); private RedisTemplate redisTemplate; private int expire = 0; /** * The Redis key prefix for the sessions */ private String keyPrefix = "shiro_redis_session:"; @Override public void update(Session session) throws UnknownSessionException { this.saveSession(session); } /** * save session * * @param session * @throws UnknownSessionException */ private void saveSession(Session session) throws UnknownSessionException { if (session == null || session.getId() == null) { logger.error("session or session id is null"); return; } String key = getKey(session.getId()); session.setTimeout(expire * 1000); this.redisTemplate.opsForValue().set(key, session); this.redisTemplate.expire(key, expire, TimeUnit.SECONDS); } @Override public void delete(Session session) { if (session == null || session.getId() == null) { logger.error("session or session id is null"); return; } redisTemplate.delete(this.getKey(session.getId())); } @Override public Collection<Session> getActiveSessions() { Set<Session> sessions = new HashSet<Session>(); Set<String> keys = redisTemplate.keys(this.keyPrefix + "*"); if (keys != null && keys.size() > 0) { for (String key : keys) { Session s = (Session) redisTemplate.opsForValue().get(key); sessions.add(s); } } return sessions; } @Override protected Serializable doCreate(Session session) { Serializable sessionId = this.generateSessionId(session); this.assignSessionId(session, sessionId); this.saveSession(session); return sessionId; } @Override protected Session doReadSession(Serializable sessionId) { if (sessionId == null) { logger.error("session id is null"); return null; } Session s = (Session) redisTemplate.opsForValue().get(this.getKey(sessionId)); return s; } /** * 获得String型的key * * @return */ private String getKey(Serializable sessionId) { return this.keyPrefix + sessionId; } /** * Returns the Redis session keys * prefix. * * @return The prefix */ public String getKeyPrefix() { return keyPrefix; } /** * Sets the Redis sessions key * prefix. * * @param keyPrefix The prefix */ public void setKeyPrefix(String keyPrefix) { this.keyPrefix = keyPrefix; } public RedisTemplate getRedisTemplate() { return redisTemplate; } public void setRedisTemplate(RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } public int getExpire() { return expire; } public void setExpire(int expire) { this.expire = expire; } }
5)、这里作者遇到一个巨坑,本地测试、测试环境nginx集群测试均ok,但是生产上使用时,遇到用户登录,权限啥的都有了,但是页面上用户信息没显示出来,通过一步一步打印http请求上的Cookie,总算确定了是使用nginx导致的Cookie丢失,https://server_name 和 https://server_name/web 两种方式中默认的cookie上的sessionID存储路径不一致,根据网上的方案,解决方式是将根目录/ 重新指向/web。但是,我这怎么弄都不行,个人推荐以下方式:
location / { proxy_pass http://local_tomcat; rewrite ^/$ /web last; } location /web/ { proxy_pass http://local_tomcat/web/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }