乐趣区

分布式Session的几种实现方式

SpringBoot 分布式 session

在如今服务器集群的情况下,用户登录会话状态的保存也从单机的变成了分布式要求的,下面详细说一下几种分布式 session 存储方案。

  1. session 复制:在支持 session 复制的服务器上进行,同步 session,保持 session 一致

    方案:tomcat-redis-session-manager

  2. session 粘滞:强行分发 session 到各个服务器

    方案:负载均衡

  3. cookie 存储 session:把 sessionid 存储到 cookie 中(不安全,cookie 容易被盗取,可以存储不重要的数据)
  4. session 集中管理:把用户的 session 存储在单台或者集群服务器的缓存中,所有 web 服务器从中拿取 session,实现 session 共享

    方案:Redis 存储用户生成的 sessionId 或者存储保存 sessionId 的 cookie

这里只讲解第四种方案,使用最多最稳定

Redis 做缓存持久化存储 session

Redis 存储 cookie,里面保存用户生成的 uuid(token 作为 sessionId)

  • cookie 名称和过期时间
public static final String COOKIE_NAME_TOKEN = "token";
private static final ex  = 3600;
  • 创建 cookie 并存储到 redis
private String addCookie(HttpServletResponse response,SeckillUser user){String token = UUIDUtil.uuid();
        redisService.set(token,user);// 使用 redis 把 token 作为键存储 user 作为值 我使用 jedis 自己实现的也可以使用 redisTemplate
        Cookie cookie = new Cookie(COOKIE_NAME_TOKEN, token);// 设置 cookie 名称为 token
        cookie.setMaxAge(ex);// 设置过期时间 
        cookie.setPath("/");
        response.addCookie(cookie);
        return token;
    }
  • uuid 作为 sessionid 生成工具
public class UUIDUtil {public static String uuid(){return UUID.randomUUID().toString();}
}

使用 Shiro 集成的crazy-cake(使用 Redis 存储 SessionId)

  • shiro 是一个 Web 安全框架,用于登录认证,用户身份授权,使用易于spring-security,还可以继承 session、cookie 分布式存储

不了解的可以看这篇博文:Shiro 的使用以及集成 redis 做缓存

缓存
  • 使用缓存存储 session(单服务器使用 EhCacheManager)
  • 但是在分布式系统中,服务器集群情况下,EhCacheManager 无法解决数据共享(会多次查询数据库),则选择使用 redis 作为缓存
Redis 实现 shiro 缓存
  • 分布式共享 session 和授权信息需要把 session 和授权持久化到数据库或者缓存 shiro 集群为了防止多次插查询数据库
  • 自定义实现类: 或者使用 crazycake 开源 shiro-redis 实现好的工具

    • RedisSessionDAO 可以继承 EnterpriseCacheSessionDAO 实现 session 控制
    • RedisCache 继承 Cache 类实现具体 redis 操作缓存(remove、get、set、keys
    • RedisCacheManager 实现接口 CacheManager 的 getCache 获得 RedisCache 交给 securityManager 管理

使用了 ConcurrentMap 管理数据和缓存,更加高效

  1. ShiroConfig 配置类中把 sessionManager 交给 DefaultWebSecurityManager 管理
@Bean
    public DefaultWebSessionManager sessionManager(){DefaultWebSessionManager sessionManager  = new DefaultWebSessionManager();
        //session 时间
        sessionManager.setGlobalSessionTimeout(redisConfig().getTimeout());
        // 删除无效 session
        sessionManager.setDeleteInvalidSessions(true);
        log.info("sessionManager 注入成功");
        sessionManager.setSessionIdCookie(cookie());
        // sessionDao 分布式共享 session 和授权信息需要把 session 和授权持久化到数据库或者缓存 shiro 集群为了防止多次插查询数据库
        sessionManager.setSessionDAO(redisSessionDao());
        return sessionManager;
    }
  1. sessionManager 中注入 cookie 存储jsessionId
@Bean
    public SimpleCookie cookie() {SimpleCookie cookie = new SimpleCookie("JSESSIONID");
        cookie.setHttpOnly(true);
        cookie.setPath("/");
        return cookie;
    }
  1. 再在 sessionManager 中注入 redisSessionDao 负责 session 持久化
@Bean
    public RedisSessionDAO redisSessionDao(){RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());
        return redisSessionDAO;
    }
退出移动版