当前位置: 首页 > news >正文

分布式Session

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


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

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

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

方案1:黏性会话(Sticky Session)
  • 原理:让同一用户的请求始终路由到同一服务器
  • 实现:Nginx配置ip_hash
upstream backend {
    ip_hash; # 像给顾客发固定服务员工牌
    server 192.168.1.101:8080;
    server 192.168.1.102:8080;
}
  • 优点:零改造成本
  • 缺点
    • 服务器宕机 → Session丢失(相当于服务员请假,笔记本被带走)
    • 扩容缩容困难(新服务员没有历史记录)
方案2:Session复制(同步广播)
  • 原理:所有服务器实时同步Session数据
  • 实现:Tomcat配置集群
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
  • 优点:任意服务器都可响应
  • 缺点
    • 网络带宽消耗大(相当于每天让所有服务员互相抄笔记)
    • 不适合大规模集群(超过10个节点性能暴跌)
方案3:集中存储(重点掌握)
  • 原理:把Session存到独立存储服务
  • 架构
    用户 → 负载均衡 → 任意服务器 → Redis/Memcached
    
  • 代码示例(Spring Session + Redis)
    1. 添加依赖:
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
    </dependency>
    
    1. 配置Redis连接:
    @EnableRedisHttpSession
    public class Config {
        @Bean
        public LettuceConnectionFactory connectionFactory() {
            return new LettuceConnectionFactory("redis-host", 6379);
        }
    }
    
    1. 使用Session与单机完全一致:
    @GetMapping("/login")
    public String login(HttpSession session) {
        session.setAttribute("user", "码农阿杜"); // 自动存到Redis
        return "登录成功";
    }
    
  • 优点
    • 服务器无状态,方便扩容
    • 数据持久化,服务器重启不丢失
  • 缺点
    • 增加网络延迟(多一次存储访问)
    • 需要维护中间件
方案4:客户端存储(JWT方案)
  • 原理:把Session数据加密后直接存Cookie
  • 代码示例
    // 生成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查询数据库
  • 代码示例
    // 生成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分钟
    • 滑动过期:每次访问刷新有效期
    // 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典型代码(无感知):
// 和单机Session用法完全一致
HttpSession session = request.getSession();
session.setAttribute("user", user); // 自动存入Redis
方案5典型代码(全手动):
// 登录时生成并存储
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),但过程体验和自由度截然不同。

相关文章:

  • 深度学习在自动驾驶车辆车道检测中的应用
  • 提升 Spring Boot 系统性能:高效处理实时数据流的 BufferTrigger 使用详解
  • 从零开始自主「起身站立」,上海AI Lab发布最新控制算法,机器人:起猛了
  • TCP协议工作原理详细介绍(形象举例版)
  • 【读书笔记·VLSI电路设计方法解密】问题56:一种设计的门数是多少
  • 如何使用useEffect模拟组件的生命周期?
  • IP-----动态路由OSPF(2)
  • 用kiln微调大模型第二篇
  • C++ | 哈希表
  • Makefile编写和相关语法规则
  • C语言综合案例:学生成绩管理系统
  • Go语言学习笔记(三)
  • 【Go】十六、protobuf构建基础服务信息、grpc服务启动的基础信息
  • 可以免费无限次下载PPT的网站
  • 事务性质ACID
  • 若依vue plus环境搭建
  • 重构MVC
  • drupal可以自动将测试环境的网页部署到生产环境吗
  • C++17中方便文件操作的工具包filesystem-250227
  • Three.js包围盒
  • 蔡建忠已任昆山市副市长、市公安局局长
  • 《风林火山》千呼万唤始出来,戛纳首映后口碑崩盘?
  • 一箭六星,朱雀二号改进型遥二运载火箭发射成功
  • 新城悦服务:独董许新民辞任,新任独董与另两人组成调查委员会将调查与关联方资金往来
  • 中国恒大披露清盘进展:要求债权人提交债权证明表
  • 泽连斯基已离开土耳其安卡拉