如何利用 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
繁體地址,請注明出處:http://hk.pswp.cn/news/908425.shtml
英文地址,請注明出處:http://en.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;要不然保存的時候不會格式化。…