如何利用 Redis 实现跨多个无状态服务实例的会话共享?

使用 Redis 实现跨多个无状态服务实例的会话共享是一种非常常见且有效的方案。无状态服务本身不存储会话信息,而是将用户的会话数据集中存储在外部存储中(如 Redis),这样任何一个服务实例都可以通过查询外部存储来获取和更新用户的会话状态。

以下是如何利用 Redis 实现跨服务实例会话共享的步骤和关键考虑:

一、核心流程

  1. 用户登录/会话创建:

    • 用户通过认证服务(或任一服务实例)进行登录。
    • 登录成功后,服务生成一个唯一的会话 ID (Session ID)
    • 服务将用户的会话数据(例如用户ID、角色、权限、购物车信息等)存储到 Redis 中,以该 Session ID 作为 Key。
    • 服务将 Session ID 返回给客户端(通常通过 Cookie 或 HTTP Header)。
  2. 后续请求处理:

    • 客户端在后续的请求中(通常通过 Cookie 或 HTTP Header)携带 Session ID。
    • 接收请求的服务实例从请求中提取 Session ID。
    • 服务实例使用该 Session ID 作为 Key 从 Redis 中查询会话数据。
    • 如果 Redis 中存在该 Session ID 对应的会话数据:
      • 服务实例加载会话数据,进行身份验证、权限校验和业务处理。
      • 如果会话数据在此次请求中被修改(例如,用户添加商品到购物车),服务实例将更新后的会话数据写回 Redis。
      • 服务实例通常会重置/刷新 (touch) 该 Session ID 在 Redis 中的过期时间 (TTL),以保持会话的活跃状态。
    • 如果 Redis 中不存在该 Session ID 对应的会话数据(可能已过期、被删除或无效):
      • 服务实例认为用户未登录或会话已失效,引导用户重新登录。
  3. 用户登出/会话销毁:

    • 用户发起登出请求。
    • 服务实例从请求中获取 Session ID。
    • 服务实例从 Redis 中删除该 Session ID 对应的会话数据。
    • 服务通知客户端清除本地存储的 Session ID (例如,删除 Cookie)。

二、Redis 中的数据结构选择

存储会话数据时,有几种常见的 Redis 数据结构选择:

  1. String (字符串):

    • 存储方式: 将整个会话对象序列化成字符串(如 JSON、Kryo、Protobuf)后存储。
    • Key: session:<session_id>
    • Value: 序列化后的会话对象字符串。
    • 优点:
      • 简单直观,一次 GET 获取全部,一次 SET 更新全部。
      • 易于整体读取和反序列化。
    • 缺点:
      • 如果只需要更新会话中的一小部分数据,也需要读取、反序列化、修改、序列化、再写入整个对象,开销较大。
      • 如果会话对象较大,内存和网络传输开销也较大。
    • 适用场景: 会话对象较小,或者会话数据通常是整体更新的。
  2. Hash (哈希表):

    • 存储方式: 将会话对象的不同属性作为 Hash 的字段存储。
    • Key: session_hash:<session_id>
    • Fields & Values:
      • userId: 123
      • username: "alice"
      • roles: "admin,editor"
      • cart_items: "[{\"itemId\": \"p1\", \"qty\": 2}]" (可以是 JSON 字符串)
    • 优点:
      • 可以原子地更新会话中的单个字段 (HSET),而无需读取和重写整个对象,效率更高。
      • 可以只获取需要的字段 (HGET, HMGET),节省网络带宽。
      • 内存使用更灵活,如果某些字段不存在,则不占用空间。
    • 缺点:
      • 如果需要获取整个会话对象,需要 HGETALL,然后应用层进行组装。
      • 如果会话中包含复杂嵌套对象,存储和读取可能需要额外的序列化/反序列化处理(例如,将购物车对象序列化为 JSON 字符串再存入 Hash 的一个字段)。
    • 适用场景: 会话对象较大,且经常需要更新或读取部分字段。这是最常用的方式之一。

三、关键实现细节和考虑因素

  1. Session ID 生成:

    • 必须保证全局唯一且难以预测。
    • 通常使用 UUID (Universally Unique Identifier) 或高强度的随机字符串。
    • 避免使用自增 ID 或简单的时间戳。
  2. Session ID 传输:

    • Cookie: 最常见的方式。
      • 设置 HttpOnly 标志以防止客户端 JavaScript 访问,增强安全性。
      • 设置 Secure 标志以确保 Cookie 只在 HTTPS 连接下传输。
      • 设置 PathDomain 属性以控制 Cookie 的作用范围。
      • 考虑 SameSite 属性来防止 CSRF 攻击。
    • HTTP Header: 常用于 API 接口,特别是移动应用或前后端分离的架构。
      • 例如,自定义 Header X-Session-ID
  3. 会话过期 (TTL - Time To Live):

    • 重要性: 必须为 Redis 中的会话数据设置过期时间。否则,无效会话会永久占用 Redis 内存。
    • 滑动窗口过期 (Sliding Window Expiration): 每次用户有活动时,刷新会话在 Redis 中的 TTL。这是最常见的做法。
      • 例如,设置会话的 TTL 为 30 分钟。用户每次请求,如果会话有效,就使用 Redis 的 EXPIREPEXPIRE 命令(或在 SET 时带上 EXPX 参数)重新设置其过期时间为 30 分钟后。
    • 绝对过期 (Absolute Expiration): 会话从创建开始,无论用户是否活跃,在固定时间后都会过期。较少用于用户会话,更多用于有时效性的 Token。
    • 合理设置 TTL:
      • 太短:用户可能频繁被要求重新登录。
      • 太长:安全性风险增加(如果 Session ID 泄露),且占用 Redis 内存时间更长。
      • 一般建议 15-60 分钟,具体根据业务需求。
  4. 会话数据的序列化/反序列化:

    • 如果选择 String 结构,或者 Hash 中的某些字段是复杂对象,需要选择合适的序列化方案。
    • JSON: 通用性好,可读性强,但性能和空间效率可能不是最优。
    • Kryo, Protobuf, MessagePack: 性能更高,序列化后体积更小,但可能需要引入额外依赖和定义 schema。
    • 考虑序列化库的兼容性和版本问题。
  5. 安全性:

    • Session ID 保护: 防止 Session ID 被窃取(XSS, CSRF, 中间人攻击)。
    • HTTPS: 强制使用 HTTPS 保护 Session ID 在传输过程中的安全。
    • Session 固定攻击 (Session Fixation): 确保在用户成功登录后,重新生成一个新的 Session ID,即使登录前已存在一个匿名会话。
    • 定期审计和更新安全措施。
  6. 高可用性和扩展性 (Redis 层面):

    • 使用 Redis Sentinel (哨兵) 或 Redis Cluster 来保证 Redis 服务的高可用性和可扩展性。
    • 如果会话数据量巨大,Redis Cluster 可以水平分片存储。
  7. 避免会话数据过大:

    • 只在会话中存储必要的信息(如用户ID、少量关键状态)。
    • 避免在会话中存储大量业务数据或临时数据,这些数据应从数据库或其他服务按需获取。过大的会话对象会增加 Redis 内存消耗和网络传输开销。
  8. 处理 Redis 连接问题:

    • 服务实例需要有健壮的 Redis 连接池管理。
    • 考虑 Redis 不可用时的降级策略(例如,暂时禁止登录,或提示服务不可用)。
  9. 代码封装:

    • 将与 Redis 交互的会话管理逻辑封装成一个独立的模块或库,方便在各个服务实例中复用和统一管理。
    • 例如,提供 getSession(sessionId), saveSession(sessionId, sessionData, ttl), deleteSession(sessionId) 等接口。

四、示例 (使用 String 结构和 JSON 序列化 - 伪代码)

import redis
import json
import uuid
import time# 假设 redis_client 是一个已配置好的 Redis 连接实例
redis_client = redis.Redis(host='localhost', port=6379, db=0)SESSION_TTL_SECONDS = 30 * 60  # 30 分钟def create_session(user_data):session_id = str(uuid.uuid4())session_key = f"session:{session_id}"# 将用户数据序列化为 JSON 字符串session_value = json.dumps(user_data)redis_client.setex(session_key, SESSION_TTL_SECONDS, session_value)return session_iddef get_session(session_id):if not session_id:return Nonesession_key = f"session:{session_id}"session_value_bytes = redis_client.get(session_key)if session_value_bytes:# 刷新 TTLredis_client.expire(session_key, SESSION_TTL_SECONDS)# 反序列化return json.loads(session_value_bytes.decode('utf-8'))return Nonedef update_session(session_id, user_data):if not session_id:return Falsesession_key = f"session:{session_id}"# 确保 key 存在才更新,或者直接 setex 覆盖if redis_client.exists(session_key):session_value = json.dumps(user_data)redis_client.setex(session_key, SESSION_TTL_SECONDS, session_value)return Truereturn Falsedef delete_session(session_id):if not session_id:returnsession_key = f"session:{session_id}"redis_client.delete(session_key)# --- 模拟使用 ---# 用户登录
user = {"user_id": 101, "username": "testuser", "roles": ["user"]}
session_id_from_login = create_session(user)
print(f"Login successful, Session ID: {session_id_from_login}")# 后续请求
retrieved_session_data = get_session(session_id_from_login)
if retrieved_session_data:print(f"Retrieved session data: {retrieved_session_data}")# 模拟更新会话数据retrieved_session_data["last_active_time"] = time.time()update_session(session_id_from_login, retrieved_session_data)print("Session updated with last_active_time.")
else:print("Session not found or expired.")# 用户登出
# delete_session(session_id_from_login)
# print("Session deleted on logout.")

通过这种方式,无论用户的请求被哪个无状态服务实例处理,该实例都能通过统一的 Session ID 从 Redis 中获取到一致的会话信息,从而实现了会话共享。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/news/908425.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《chipyard》docker使用

一、启动/重启服务 二、登入/退出 容器对象查看 sudo docker ps -a # 查看容器列表 登入已例化的容器 sudo docker exec -it -u root 737ed3ddd5ff bash # 737ed3ddd5ff<容器名称/ID> 三、容器编辑 删除单个容器 sudo docker stop <容器ID> #停止容器 s…

浏览器工作原理06 [#]渲染流程(下):HTML、CSS和JavaScript是如何变成页面的

引用 浏览器工作原理与实践 简单回顾下上节前三个阶段的主要内容&#xff1a;在HTML页面内容被提交给渲染引擎之后&#xff0c;渲染引擎首先将HTML解析为浏览器可以理解的DOM&#xff1b;然后根据CSS样式表&#xff0c;计算出DOM树所有节点的样式&#xff1b;接着又计算每个元素…

AI书签管理工具开发全记录(十三):TUI基本框架搭建

文章目录 AI书签管理工具开发全记录&#xff08;十三&#xff09;&#xff1a;TUI基本框架搭建前言 &#x1f4dd;1.TUI介绍 &#x1f50d;2. 框架选择 ⚙️3. 功能梳理 &#x1f3af;4. 基础框架搭建⚙️4.1 安装4.2 参数设计4.3 绘制ui4.3.1 设计结构体4.3.2 创建头部4.3.3 创…

CC7利用链深度解析

CommonsCollections7&#xff08;CC7&#xff09;是CC反序列化利用链中的重要成员&#xff0c;由Matthias Kaiser在2016年发现。本文将从底层原理到实战利用&#xff0c;全面剖析这条独特而强大的利用链。 一、CC7链技术定位 1.1 核心价值 无第三方依赖&#xff1a;仅需JDK原…

openvino使用教程

OpenVINO使用教程 本专栏内容支持平台章节计划 本专栏内容 OpenVINO 是一款开源工具包&#xff0c;用于在云端、本地和边缘部署高性能 AI 解决方案。我们可以使用来自最热门模型框架的生成式和传统 AI 模型来开发应用程序。充分利用英特尔 硬件的潜力&#xff0c;使用openvino…

ESP8266(NodeMcu)+GPS模块+TFT屏幕实现GPS码表

前言 去年写过一篇关于使用esp8266(nodemcu)gps模块oled屏幕diy的gps定位器的文章.点击回顾 .无奈OLED屏幕太小了,最近刚好有时间又折腾使用TFT屏幕diy了一款gps码表 效果如图 材料准备 依旧是请出我们的两位老演员 nocdmcu一块. GPS定位模块(我买的大夏龙雀的DX-GP10-GP…

解决获取视频第一帧黑屏问题

文章目录 解决获取视频第一帧黑屏问题核心代码 解决获取视频第一帧黑屏问题 废话不多说&#xff0c;直接上代码&#xff1a; <script setup> const status ref(请点击“添加视频”按钮添加视频) const videoElement ref(document.createElement(video)) const curren…

通过BUG(prvIdleTask、pxTasksWaitingTerminatio不断跳转问题)了解空闲函数(prvIdleTask)和TCB

一、前言与问题 在基于 FreeRTOS 的嵌入式系统中&#xff0c;我使用 STM32F1 开发一个 MQTT 客户端应用&#xff0c;涉及两个主要任务&#xff1a; ATRecvParser&#xff1a;负责解析 Wi-Fi 模块的 AT 命令响应&#xff08;如 OK、ERROR 和 IPD 数据&#xff09;。MQTT_Clien…

继MySQL之后的技术-JDBC-从浅到深-02

目录 概念 编程六部曲 SQL注入和statement 工具类的封装 JDBC事务 模糊查询 批处理 数据库连接池 Apache-DBUtils BasicDao 概念 JDBC为访问不同的数据库提供了统一的接口&#xff0c;为使用者屏蔽了细节问题。 Java程序员使用JDBC&#xff0c;可以连接任何提供了JD…

【配置 YOLOX 用于按目录分类的图片数据集】

现在的图标点选越来越多&#xff0c;如何一步解决&#xff0c;采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集&#xff08;每个目录代表一个类别&#xff0c;目录下是该类别的所有图片&#xff09;&#xff0c;你需要进行以下配置步骤&#x…

浅谈python如何做接口自动化

工具与环境准备 开发工具 PyCharm专业版&#xff1a;支持项目视图、代码导航、调试功能和主流框架开发官方资源&#xff1a;JetBrains PyCharm 数据库操作 使用mysqlclient库操作MySQL&#xff08;Django官方推荐&#xff09;安装命令&#xff1a;pip install mysqlclient1.3.…

知识图谱技术概述

一、概述 知识图谱&#xff08;Knowledge Graph&#xff09; 是一种基于图结构的语义网络&#xff0c;用于表示实体及其之间的关系&#xff0c;旨在实现更智能的知识表示和推理。它通过将现实世界中的各类信息抽象为 “实体-关系-实体” 的三元组结构&#xff0c;构建出复杂的知…

NodeJS Koa 后端用户会话管理,JWT, Session,长短Token,本文一次性讲明白

前言 前几天&#xff0c;我写了一篇文章&#xff0c;《我设计的一个安全的 web 系统用户密码管理流程》。其中着重点是讲的如何利用非对称加密进行安全的设计&#xff0c;并在讲述了原理之后&#xff0c;又写了 《node 后端和浏览器前端&#xff0c;有关 RSA 非对称加密的完整…

0.5S 级精度背后:DJSF1352-RN-6 如何让储能电站的每 1kWh 都「有迹可循」?

1、背景 在能源转型的时代洪流里&#xff0c;大型储能电站作为保障电网稳定运行、平衡能源供需的核心基础设施&#xff0c;其战略价值愈发凸显。而储能电站的高效运转&#xff0c;始终离不开精准的电能计量体系支撑。今日为您重点推介一款针对 1500V 储能系统研发的专业电能表…

Linux运维笔记:服务器安全加固

文章目录 背景加固措施1. 修改用户密码2. 使用公钥认证替代密码登录3. 强化系统安全4. 扫描与清理残留威胁5. 规范软件管理&#xff08;重点&#xff09; 注意事项总结 提示&#xff1a;本文总结了大学实验室 Linux 电脑感染挖矿病毒后的安全加固措施&#xff0c;重点介绍用户密…

Pycharm 配置解释器

今天更新了一版pycharm&#xff0c;因为很久没有配置解释器了&#xff0c;发现一直失败。经过来回试了几次终于成功了&#xff0c;记录一下过程。 Step 1 Step 2 这里第二步一定要注意类型要选择python 而不是conda。 虽然我的解释器是conda 里面建立的一个环境。挺有意思的

【Linux】awk 命令详解及使用示例:结构化文本数据处理工具

【Linux】awk 命令详解及使用示例&#xff1a;结构化文本数据处理工具 引言 awk 是一种强大的文本处理工具和编程语言&#xff0c;专为处理结构化文本数据而设计。它的名称来源于其三位创始人的姓氏首字母&#xff1a;Alfred Aho、Peter Weinberger 和 Brian Kernighan。 基…

MS1023/MS1224——10MHz 到 80MHz、10:1 LVDS 并串转换器(串化器)/串并转换器(解串器)

产品简述 MS1023 串化器和 MS1224 解串器是一对 10bit 并串 / 串并转 换芯片&#xff0c;用于在 LVDS 差分底板上传输和接收 10MHz 至 80MHz 的并行字速率的串行数据。起始 / 停止位加载后&#xff0c;转换为负载编 码输出&#xff0c;串行数据速率介于 120Mbps…

跟我学c++中级篇——理解类型推导和C++不同版本的支持

一、类型推导 在前面反复分析过类型推导&#xff08;包括前面提到的类模板参数推导CTAD&#xff09;&#xff0c;类型推导其实就是满足C语言这种强类型语言的要求即编译期必须确定对象的数据类型。换一句话说&#xff0c;理论上如果编译器中能够自动推导所有的相关数据类型&am…

vue3+TS+eslint9配置

记录eslint升级到9.x的版本之后遇到的坑 在 ESLint 9 中&#xff0c;配置方式发生了变化。Flat Config 格式&#xff08;eslint.config.js 或 .ts&#xff09;不再支持 extensions 选项。所以vscode编辑器中的 extensions 需要注释掉&#xff0c;要不然保存的时候不会格式化。…