当前位置: 首页 > 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 {@Beanpublic LettuceConnectionFactory connectionFactory() {return new LettuceConnectionFactory("redis-host", 6379);}
    }
    
    1. 使用Session与单机完全一致:
    @GetMapping("/login")
    public String login(HttpSession session) {session.setAttribute("user", "码农阿杜"); // 自动存到Redisreturn "登录成功";
    }
    
  • 优点
    • 服务器无状态,方便扩容
    • 数据持久化,服务器重启不丢失
  • 缺点
    • 增加网络延迟(多一次存储访问)
    • 需要维护中间件
方案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),但过程体验和自由度截然不同。


http://www.mrgr.cn/news/92547.html

相关文章:

  • 【01】Cocos游戏开发引擎从0开发一款游戏-cocos环境搭建以及配置-Cocos Creator软件系统下载安装-node环境-优雅草卓伊凡
  • Mobaxterm服务器常用命令(持续更新)
  • UE5 Gameplay框架及继承关系详解
  • Windows下不建议使用C/C++运行库的本地化功能
  • 计算机工具基础(五)——Vim
  • KubeKey一键安装部署k8s集群和KubeSphere详细教程
  • Android移动应用开发实践-1-下载安装和简单使用Android Studio 3.5.2版本(频频出错)
  • 数据开发面试:DQL,DDL,DTL
  • 初会学习记录
  • 【射频仿真学习笔记】Cadence的Layout EXL与ADS dynamic link联动后仿
  • Harmony开发笔记(未完成)
  • 【JAVA-数据结构】Map和Set
  • 30 分钟从零开始入门 CSS
  • DeepSeek R1 + 飞书机器人实现AI智能助手
  • spring结合mybatis多租户实现单库分表
  • C语言机试编程题
  • <tauri><rust><GUI><PLC>基于tauri,编写一个串口485调试助手
  • thinkphp下的Job队列处理
  • Windows版FFmpeg使用及B站视频下载示例python源码
  • 最新版 (持续更新)docker 加速源 linux yum 源