分布式Session

我用「餐厅点餐+代码实战」帮你彻底搞懂分布式Session,看完不仅能应对面试,还能直接应用到实际开发。先记住这个核心矛盾:多服务员如何记住同一顾客的喜好


一、从生活场景理解Session的本质

传统单机场景(小餐馆)
  • 服务员:Tom(唯一服务员)
  • 工作流程
    1. 顾客首次点餐 → Tom给纸质会员卡(SessionID)
    2. Tom把顾客口味记录在自己的笔记本(服务器内存)
    3. 顾客下次出示会员卡 → Tom查笔记本提供服务
分布式场景(连锁餐厅)
  • 服务员:Tom、Jerry、Lucy(多个服务器节点)
  • 致命问题
    • 顾客第一次找Tom存了爱吃辣 → 第二次请求被分配到Jerry → Jerry一脸懵逼

二、分布式Session五大解决方案

方案1:黏性会话(Sticky Session)
  • 原理:让同一用户的请求始终路由到同一服务器
  • 实现:Nginx配置ip_hash
nginx 复制代码
upstream backend {
    ip_hash; # 像给顾客发固定服务员工牌
    server 192.168.1.101:8080;
    server 192.168.1.102:8080;
}
  • 优点:零改造成本
  • 缺点
    • 服务器宕机 → Session丢失(相当于服务员请假,笔记本被带走)
    • 扩容缩容困难(新服务员没有历史记录)
方案2:Session复制(同步广播)
  • 原理:所有服务器实时同步Session数据
  • 实现:Tomcat配置集群
xml 复制代码
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
  • 优点:任意服务器都可响应
  • 缺点
    • 网络带宽消耗大(相当于每天让所有服务员互相抄笔记)
    • 不适合大规模集群(超过10个节点性能暴跌)
方案3:集中存储(重点掌握)
  • 原理:把Session存到独立存储服务

  • 架构

    复制代码
    用户 → 负载均衡 → 任意服务器 → Redis/Memcached
  • 代码示例(Spring Session + Redis)

    1. 添加依赖:
    xml 复制代码
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
    </dependency>
    1. 配置Redis连接:
    java 复制代码
    @EnableRedisHttpSession
    public class Config {
        @Bean
        public LettuceConnectionFactory connectionFactory() {
            return new LettuceConnectionFactory("redis-host", 6379);
        }
    }
    1. 使用Session与单机完全一致:
    java 复制代码
    @GetMapping("/login")
    public String login(HttpSession session) {
        session.setAttribute("user", "码农阿杜"); // 自动存到Redis
        return "登录成功";
    }
  • 优点

    • 服务器无状态,方便扩容
    • 数据持久化,服务器重启不丢失
  • 缺点

    • 增加网络延迟(多一次存储访问)
    • 需要维护中间件
方案4:客户端存储(JWT方案)
  • 原理:把Session数据加密后直接存Cookie

  • 代码示例

    java 复制代码
    // 生成Token
    String token = Jwts.builder()
        .setSubject("user123")
        .claim("role", "admin")
        .signWith(SignatureAlgorithm.HS256, "secretKey")
        .compact();
    
    // 验证Token
    Claims claims = Jwts.parser()
        .setSigningKey("secretKey")
        .parseClaimsJws(token)
        .getBody();
  • 优点:彻底解决服务端存储问题

  • 缺点

    • Token无法主动失效(相当于会员卡永久有效)
    • 数据大小受Cookie限制
方案5:Session共享协议(Token+数据库)
  • 实现流程

    1. 登录成功生成token(UUID)
    2. 把token和用户数据存入数据库
    3. 每次请求携带token查询数据库
  • 代码示例

    java 复制代码
    // 生成Token
    String token = UUID.randomUUID().toString();
    redisTemplate.opsForValue().set(token, userInfo, 30, TimeUnit.MINUTES);
    
    // 拦截器验证
    String token = request.getHeader("X-Token");
    User user = redisTemplate.opsForValue().get(token);
    if(user == null) throw new AuthException();
  • 优点:灵活控制存储方式

  • 缺点:需要手动管理生命周期


三、方案选型决策树

复制代码
是否需要服务器完全无状态?
├─ 是 → 客户端存储(JWT)
└─ 否 → 是否需要高并发?
   ├─ 是 → 集中存储(Redis)
   └─ 否 → Session复制(小集群)/黏性会话(稳定集群)

四、面试高频问题

Q1:如何防止Session劫持?
  • 防御措施
    1. 使用HTTPS防止网络嗅探
    2. Cookie设置HttpOnly和Secure
    3. 定期更换SessionID(如每10分钟)
Q2:分布式Session过期时间如何设置?
  • 黄金法则

    • 基础过期时间:30分钟
    • 滑动过期:每次访问刷新有效期
    java 复制代码
    // Redis示例
    redisTemplate.expire(sessionId, 30, TimeUnit.MINUTES);
Q3:Spring Session的实现原理?
  • 核心机制
    1. 通过Filter替换原生HttpSession
    2. SessionRepositoryFilter包装请求/响应
    3. 实际存储委托给Redis等实现

五、生产环境最佳实践

  1. Session数据最小化:只存必要信息(如userId)
  2. 读写分离:Redis主从架构提升读取性能
  3. 降级方案:在Redis故障时自动切换本地缓存
  4. 监控预警:监控Session存储容量和延迟

一句话总结

分布式Session的本质是:把服务员们的笔记本换成中央档案室! 掌握这个本质,所有解决方案都是围绕存储位置和同步方式的设计权衡。

补充

方案3和方案5确实都涉及外部存储,但它们的核心差异在于数据管理层次和实现方式。我用餐厅工作流程对比帮你彻底分清这对「双胞胎」:


本质区别对比表

方案3:集中存储(Spring Session) 方案5:Session共享协议(Token+DB)
管理层次 Web容器层自动管理(对开发者透明) 应用层手动管理(需要显式编码)
存储内容 完整Session对象(序列化存储) 自定义业务数据(如用户ID、权限等)
标识传递 自动通过Cookie传递JSESSIONID 手动通过Header/Param传递自定义Token
数据读写 框架自动完成(如Spring Session Filter拦截读写) 需要手动编写存取代码
典型应用 传统Web应用迁移到分布式环境 前后端分离架构/APP接口

餐厅版对比解释

假设餐厅要记录顾客的「忌口清单」:

方案3:中央档案室(Spring Session)
  1. 服务员直接说:"忌口清单存总部"
  2. 每次顾客出示会员卡 → 服务员自动联系总部查清单
  3. 优势:服务员工作方式不变,只是数据位置换了
方案5:自定义登记表(Token+DB)
  1. 服务员需要:
    • 设计新的登记表格(定义Token格式)
    • 手动打电话给总部:"把顾客A的清单给我"
    • 更新后主动回传总部:"这是顾客A的新清单"
  2. 优势:完全掌控数据格式和流程

代码级区别演示

方案3典型代码(无感知):
java 复制代码
// 和单机Session用法完全一致
HttpSession session = request.getSession();
session.setAttribute("user", user); // 自动存入Redis
方案5典型代码(全手动):
java 复制代码
// 登录时生成并存储
String token = UUID.randomUUID().toString();
redisTemplate.opsForValue().set(token, user.getId(), 30, TimeUnit.MINUTES);

// 拦截器中验证
String token = request.getHeader("X-Token");
if(!redisTemplate.hasKey(token)) {
    throw new UnauthorizedException();
}
Long userId = redisTemplate.opsForValue().get(token);

如何选择?

  • 选方案3如果:

    • 已有传统Web应用需要改造
    • 想保持原有Session API写法
    • 不介意依赖Spring生态
  • 选方案5如果:

    • 全新设计的前后端分离系统
    • 需要精细控制Session数据结构
    • 追求轻量化/去框架依赖

一句话总结区别

方案3是让框架帮你搬行李的旅行社,方案5是自己打包的自助游

两者最终都到达目的地(完成分布式Session),但过程体验和自由度截然不同。

相关推荐
FLGB3 分钟前
Kafka延迟队列实现分级重试
分布式·kafka
java1234_小锋11 小时前
Kafka中的消息是如何存储的?
分布式·kafka
老友@11 小时前
Kafka 深度解析:高性能设计、部署模式、容灾机制与 KRaft 新模式
分布式·kafka·kraft·高性能·容灾机制
余子桃11 小时前
Kafka的安装与使用(windows下python使用等)
分布式·kafka
java1234_小锋11 小时前
Kafka中的消息如何分配给不同的消费者?
分布式·kafka
小样vvv11 小时前
【Kafka】深入探讨 Kafka 如何保证一致性
分布式·kafka
快来卷java16 小时前
深入剖析雪花算法:分布式ID生成的核心方案
java·数据库·redis·分布式·算法·缓存·dreamweaver
2401_8712905817 小时前
Hadoop 集群的常用命令
大数据·hadoop·分布式
冰 河17 小时前
《Mycat核心技术》第21章:高可用负载均衡集群的实现(HAProxy + Keepalived + Mycat)
分布式·微服务·程序员·分布式数据库·mycat
小样vvv18 小时前
【分布式】深入剖析 Sentinel 限流:原理、实现
分布式·c#·sentinel