持久化记忆redis

引入 Redis 解决会话丢失问题,看似只是加了几行代码,但其背后蕴含着经典的 Web 状态管理(Session Management) 架构思想。

由于 HTTP 协议本身是"无状态"的(服务器记不住上一次是谁发起的请求),加上 Streamlit 这种前端框架在刷新时会清空内存,我们必须引入一个"外部大脑"来存储记忆。

下面我将把刚才的代码拆解开来,为你极其详细地剖析其中的核心技术点和设计逻辑。

🧠 深度剖析 1:Redis 中间件 (redis_manager.py)

这个文件是我们与 Redis 数据库交互的底层引擎。在编写企业级后端时,我们绝对不会把数据库连接代码散落在各个业务逻辑里,而是会将其封装成一个专用的管理类。

复制代码
import redis
import json

class RedisChatManager:
    def __init__(self, host='localhost', port=6379, db=0):
        # 💡 核心知识点 1:为什么不用 redis.Redis() 直接连,而是用 ConnectionPool(连接池)?
        # 思考:如果每次用户发请求,我们都去跟 Redis 建立一次 TCP 握手连接,在高并发下网络开销会极其巨大。
        # 连接池就像一个"共享单车停放点",预先建立好一批连接。用的时候借走,用完还回来。这能将性能提升几个数量级。
        self.pool = redis.ConnectionPool(host=host, port=port, db=db, decode_responses=True)
        self.r = redis.Redis(connection_pool=self.pool)

        # 💡 核心知识点 2:内存风暴与 TTL(Time To Live,存活时间)
        # Redis 是基于内存的数据库,极其昂贵。如果用户聊完天就关了网页,历史记录永远存在内存里,服务器很快就会 OOM(内存溢出)宕机。
        # 所以我们设定 604800 秒(7天)的过期时间,实现自动化的垃圾回收。
        self.expire_time = 604800 

    def save_history(self, session_id: str, chat_history: list):
        # 💡 核心知识点 3:键名设计规范(Key Naming Convention)
        # 在 Redis 中,我们通常用冒号 `:` 来做命名空间的隔离,类似于文件夹结构。
        key = f"smart_oj:history:{session_id}"

        # 💡 核心知识点 4:序列化 (Serialization)
        # Redis 是一个 Key-Value 数据库,它的 Value 只能存字符串、哈希、列表等基础结构,【不能直接存 Python 的复杂对象(如字典组成的 list)】。
        # 所以必须用 json.dumps 将 Python 数据"降维"拍平成纯字符串(序列化),ensure_ascii=False 是为了保证中文不乱码。
        json_data = json.dumps(chat_history, ensure_ascii=False)

        # 使用 setex (SET with EXpire) 原子操作:存入数据的同时挂上倒计时炸弹。
        self.r.setex(key, self.expire_time, json_data)

    def load_history(self, session_id: str) -> list:
        key = f"smart_oj:history:{session_id}"
        data = self.r.get(key)

        # 💡 核心知识点 5:反序列化 (Deserialization)
        if data:
            # 如果从 Redis 里捞到了字符串,用 json.loads 把它"升维"还原成 Python 的字典列表。
            return json.loads(data)

        # 兜底逻辑:如果这个用户是第一次来,或者记录已经过期被删了,返回一个空列表。
        return []

# 全局单例模式,保证整个程序只初始化一个连接池
chat_db = RedisChatManager()

🖥️ 深度剖析 2:前端与 Redis 的桥梁 (app.py)

前端这部分的核心逻辑是如何在用户按下 F5(刷新浏览器)时,依然能认出"你是谁"。

复制代码
import streamlit as st
import uuid
from redis_manager import chat_db

# 💡 核心知识点 6:URL 传参(唯一可以抵御 F5 刷新的防线)
# 当你刷新网页时,浏览器的内存、Streamlit 的 st.session_state 会全部清空。
# 【唯一】不会变的,是浏览器地址栏里的 URL。
# st.query_params 就是用来读取或修改 URL 后面的参数的(比如 ?session_id=123)。

if "session_id" not in st.query_params:
    # 如果用户是第一次打开网页(URL 里没有参数),我们就给他发一张"身份证"(UUID),并强行写到地址栏里。
    st.query_params["session_id"] = str(uuid.uuid4())

# 把地址栏里的身份证号拿出来,作为去 Redis 取件的"取件码"。
current_session_id = st.query_params["session_id"]


# 💡 核心知识点 7:拦截初始化,实现"记忆恢复"
if "chat_history" not in st.session_state:
    # 以前我们这里是直接赋为空列表 `[]`。
    # 现在,我们拿着身份证号,去 Redis 数据库里查询。
    # 如果查到了,页面一加载就会把历史记录塞满;查不到,chat_db 底层也会安全地返回 []。
    st.session_state.chat_history = chat_db.load_history(current_session_id)


    # ... (中间是用户输入表单和多智能体流转逻辑) ...


    # 💡 核心知识点 8:数据的"写回" (Write-back)
    # 当大模型辛苦生成了代码并进行了讲解后,我们把新的记录追加到内存里的 chat_history。
    st.session_state.chat_history.append({
        "mode": mode_selection,
        "req": requirement,
        "user_code": user_code,
        "final_code": final_state.get("current_code", "生成失败"),
        "tutor_feedback": final_state.get("tutor_feedback", "无导师总结")
    })

    # 最关键的一步:内存里的数据是极其脆弱的,必须立刻"落盘"。
    # 调用 Redis 的 save_history,将最新的完整列表覆盖写入数据库,完成状态持久化的闭环。
    chat_db.save_history(current_session_id, st.session_state.chat_history)

代码疑问:

🧠 一、 探秘 Redis 连接池 (Connection Pool)

我们提前创建好已经和 Redis 连接好的网络通道(TCP Connections),我们的 Python **线程(执行代码的工人)**借用这些现成的通道向 Redis 服务发送指令,进行数据的修改。

1. 连接池默认有多少连接?

在 Python 的 redis-py 库中,如果你只写 redis.ConnectionPool(host=...),它的默认最大连接数(max_connections)其实是 None**(无限制)**。

  • 机制: 它会按需创建连接。刚启动时没有连接,来一个请求就建一个;如果同时有 50 个并发请求,它就建 50 个。等请求处理完,这些连接不会被销毁,而是留在池子里"待命"。
  • 实战警告: 在企业级开发中,我们通常会强制限制最大连接数 (例如 max_connections=100),防止由于代码 Bug 或突发流量导致 Redis 服务器的 TCP 连接数爆满而宕机。
2. 连接池到底减少了什么?

它减少的不是"并发连接的数量",而是**"TCP 握手和挥手的昂贵开销"**。

  • 不使用连接池: 每次存取数据,Python 都要和 Redis 经历完整的步骤:发起 TCP 三次握手建连 ➡️ 验证密码 ➡️ 发送数据 ➡️ 接收结果 ➡️ 发起 TCP 四次挥手断连。这个过程可能耗费好几毫秒,甚至几十毫秒。
  • 使用连接池: 池子里的连接都是长连接(Persistent Connections)。Python 线程需要存数据时,直接从池子里"借"一个已经连好的通道,瞬间把数据发过去,然后把通道"还"回池子。这把毫秒级的延迟降到了微秒级。
3. 如果代码和 Redis 在同一台服务器上,可以不用连接池吗?

答案是:坚决不能省略,依然要用!

即使你的 Python 和 Redis 部署在同一台机器(localhost127.0.0.1),它们依然是两个独立的进程。

  • 不走网卡,不代表不走操作系统的网络协议栈。它们之间的通信依然要经过内核的 Loopback 接口(环回网卡),依然要进行 TCP 三次握手、分配文件描述符(File Descriptor)、上下文切换。
  • 结论: 无论多近,频繁创建和销毁连接对 CPU 和操作系统内核都是巨大的负担。使用连接池是所有高并发后端雷打不动的铁律。

🌐 二、 揭秘 session_id 与浏览器记忆

4. 用户关闭浏览器,下次还是这个 session_id 吗?

答案是:不是!关闭浏览器标签页再重新打开,这段对话记录就丢失了。

这里我们要坦诚地揭露当前 app.py 中这行代码的"真面目":

复制代码
if "session_id" not in st.query_params:
    st.query_params["session_id"] = str(uuid.uuid4())
  • F5 刷新为何有效? 因为刷新时,浏览器地址栏里的 URL 没变(比如依然是 http://.../?session_id=123)。Streamlit 读取到了 URL 里的 123,去 Redis 里成功把记忆捞了出来。
  • 关闭浏览器为何失效? 当你关闭标签页,第二天再次输入 http://你的IP:8501 时,URL 后面是没有 参数的。上面的代码一看没有参数,就会傻乎乎地重新生成一个新的 UUID。此时拿着新 ID 去 Redis 里找,自然什么都找不到,就像个全新用户一样。
5. 如何保证"永久记忆"?(Cookie 与 LocalStorage)

我们目前的方法叫"URL 状态传递",它是极其脆弱的。在真正的 Web 开发中,要把身份凭证直接缓存给浏览器,我们只有两种武器:

  1. Cookies(饼干): 服务器命令浏览器把 session_id 写到本地的一个小文件里,设置有效期 30 天。以后这 30 天内,浏览器只要访问你的服务器,都会悄悄把这个 Cookie 塞在 HTTP 请求头里发给你。
  2. LocalStorage(本地存储): 现代浏览器提供的一种 H5 存储机制,用前端 JS 代码把数据存在用户的浏览器沙箱里,哪怕重启电脑都不会丢。

💡 破局方案:

Streamlit 默认不支持操作浏览器的 Cookie,但开源社区有非常成熟的插件(比如 streamlit-cookies-manager)。如果想要实现"关闭电脑,明天打开记录还在"的真正商业级效果,我们需要把生成好的 session_id 塞进 Cookie 里。

你想不想挑战一下这最后一块 Web 拼图?我们可以通过几行代码,把 URL 传参替换为 Cookie 存储,彻底实现用户级别的身份固化!

相关推荐
java资料站1 小时前
第07章:LangChain使用之Agents
langchain
小驴程序源3 小时前
【OpenClaw 完整安装实施教程(Windows + Ollama 本地模型)】
gpt·langchain·aigc·embedding·ai编程·llama·gpu算力
Trouvaille ~3 小时前
零基础入门 LangChain 与 LangGraph(三):环境搭建、包安装与第一个 LangChain 程序
python·ai·chatgpt·langchain·大模型·openai·langgraph
西西弗Sisyphus3 小时前
大模型运行的 enforce_eager 参数
langchain·prompt·transformer·vllm·enforce_eager
花千树-0104 小时前
Java 实现 ReAct Agent:工具调用与推理循环
java·spring boot·ai·chatgpt·langchain·aigc·ai编程
m0_747124536 小时前
LangChain 索引增强对话链详解
python·ai·langchain
m0_747124538 小时前
LangChain RAG Chain Types 详解
python·ai·langchain
龘龍龙1 天前
大模型学习(三)-RAG、LangChain
学习·langchain
Thomas.Sir1 天前
第九章:RAG知识库开发之【LangChain 基础入门:从零构建大模型应用】
ai·langchain·检索增强·知识库