1. 项目概述:Redis 不是“接入 AI”,而是正在成为 AI 系统的底层神经中枢
最近刷到“Redis 已正式接入 AI!”这个标题,第一反应不是兴奋,而是皱眉——这说法本身就有误导性。Redis 作为一款成熟运行了15年以上的内存数据结构服务器,它本身没有、也不可能“接入 AI”;真正发生的是:AI 工程师正在前所未有地依赖 Redis,把它从缓存层,升级为 AI 应用的实时状态中枢、技能调度总线、上下文记忆仓库和多智能体协同中间件。这不是一次功能更新,而是一场架构级迁移。
我过去三年深度参与过7个生产级 AI Agent 项目,从客服对话引擎、金融投研助手,到工业设备预测性维护系统,无一例外都把 Redis 从“可选缓存”变成了“必装基础设施”。核心关键词Redis、AI、MCP、agent-skills、Python其实勾勒出一条清晰的技术演进路径:当 AI 不再是单次调用的大模型 API,而是持续感知、记忆、决策、协作的智能体(Agent)时,它需要一个轻量、高速、可靠、可扩展的状态协调层——而 Redis 正是目前唯一能同时满足这四点要求的开源组件。
举个最直观的例子:你用 Python 写一个带记忆的聊天机器人,用户问“昨天说的股票代码是多少?”,模型本身不记事,那这个“昨天”的上下文存在哪?存在数据库里?太慢;存在 Python 进程内存里?一重启就丢;存在文件里?并发写会冲突。答案是:存在 Redis 的 Hash 结构里,用用户 ID 作 key,用时间戳+会话 ID 作 field,value 存 JSON 包含原始消息、模型回复、工具调用记录。实测下来,单节点 Redis QPS 轻松扛住 5 万次上下文读写,延迟稳定在 0.3ms 以内,比任何 ORM 或本地缓存都稳。
所以这篇文章不讲“Redis 怎么装”,也不讲“Python 怎么调 API”,而是聚焦一个真实问题:当你开始构建真正可用的 AI Agent(不是 Demo),Redis 在其中到底承担什么角色?怎么设计它的数据结构?MCP 协议如何与它协同?Python SDK 怎么避开那些坑?我会用我们团队在 RuoYi-Vue-Pro 项目中合并 MCP 功能的真实案例,拆解每一步设计取舍、参数依据和踩过的坑。如果你正卡在 AI 项目从 PoC 迈向生产部署的临界点,这篇就是为你写的。
2. Redis 在 AI 架构中的角色重构:从缓存到智能体操作系统内核
2.1 传统认知的失效:为什么“缓存”定位已严重不足?
很多工程师对 Redis 的理解还停留在“高性能缓存”层面,这是典型的认知滞后。在 AI Agent 场景下,Redis 承担的角色远超缓存:
状态快照中心(State Snapshot Hub):Agent 每次执行工具调用(如查天气、调数据库、发邮件)后,必须保存当前状态供下次决策参考。这个状态不是静态数据,而是包含执行结果、错误标记、重试次数、权限上下文的动态结构。Redis 的 Hash 和 JSON 数据类型天然适配这种嵌套结构,且支持原子操作(如
HINCRBY更新重试计数)。技能注册与发现总线(Skill Registry Bus):所谓
agent-skills,本质是一组可被调用的函数或服务。MCP(Model Context Protocol)协议定义了技能的元数据(名称、输入 schema、输出 schema、认证方式)。我们不再靠硬编码或配置文件管理技能,而是把每个技能注册为 Redis 的一个 Hash,key 是skill:weather_api,field 包含endpoint,timeout_ms,auth_type,last_health_check。Agent 启动时SCAN skill:*扫描全部技能,运行时通过HGETALL获取元数据,调用失败后自动HSET skill:weather_api last_health_check 0标记下线。多智能体协同消息队列(Multi-Agent Coordination Queue):当多个 Agent 需要协作(如销售 Agent 发起报价,财务 Agent 校验额度,法务 Agent 审核条款),它们不能直接互相调用(耦合太高),也不能全靠 HTTP 轮询(延迟高)。我们采用 Redis Streams 实现发布/订阅:每个 Agent 订阅
stream:agent_events,当销售 Agent 执行完报价生成,XADD stream:agent_events * event_type "quote_generated" quote_id "Q20240521001",其他 Agent 实时消费,无需轮询。实测 10 个 Agent 并发消费,端到端延迟 < 5ms。上下文记忆持久化层(Context Memory Persistence Layer):大模型的上下文窗口有限(GPT-4 Turbo 128K 仍是理论值),真实业务中用户对话常跨天、跨设备。我们把长期记忆(如用户偏好、历史订单、设备档案)存在 Redis 的 Sorted Set 中,score 用时间戳,member 是 JSON 序列化的记忆片段。每次新对话开始,Agent 用
ZRANGEBYSCORE memory:{user_id} (now-86400 now拉取最近 24 小时记忆,拼接进 prompt。比从 MySQL 查 10 条记录快 12 倍,且避免了 SQL 注入风险。
提示:不要用 Redis 替代数据库存储核心业务数据(如用户账户余额、订单主表)。它的优势在于“状态”和“上下文”,而非“事实”和“事务”。我们严格遵循“Redis 存状态,PostgreSQL 存事实”的分层原则。
2.2 MCP 协议与 Redis 的协同逻辑:硬件协议?软件协议?它根本不是协议!
热搜词里反复出现“MCP 是软件协议还是硬件协议”,这暴露了一个关键误解:MCP(Model Context Protocol)根本不是传统意义上的通信协议,而是一套面向 AI Agent 的接口契约规范。它不规定传输层用 TCP 还是 UDP,也不定义物理层电压标准,它只回答三个问题:
技能怎么描述?
必须提供name(字符串)、description(字符串)、input_schema(JSON Schema)、output_schema(JSON Schema)、authentication(OAuth2 / API Key / None)。技能怎么调用?
统一使用 HTTP POST/v1/skills/{name}/invoke,body 是符合input_schema的 JSON,response 是符合output_schema的 JSON。技能状态怎么管理?
提供/v1/skills/{name}/health接口返回{status: "healthy", last_checked: "2024-05-21T10:30:00Z"}。
Redis 在这里扮演的是MCP 规范的落地载体。我们把上述所有契约信息,以结构化方式存入 Redis,让 Agent 不需要硬编码技能地址,也不需要维护一堆配置文件。例如:
# 技能元数据存 Hash HSET skill:search_products name "product_search" description "Search products by keywords" input_schema '{"type":"object","properties":{"keywords":{"type":"string"}}}' output_schema '{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}}}' authentication "api_key" # 技能健康状态存 String(带过期) SETEX skill:search_products:health 300 '{"status":"healthy","last_checked":"2024-05-21T10:30:00Z"}' # 技能调用日志存 Stream(用于审计和重放) XADD stream:skill_logs * skill_name "search_products" user_id "U12345" timestamp "2024-05-21T10:30:05Z" input '{"keywords":"wireless earbuds"}' output '[{"id":"P98765","name":"AirBuds Pro"}]'这样,当一个新的 Agent 服务启动,它只需连接 Redis,执行KEYS skill:*获取所有技能列表,再对每个技能HGETALL读取元数据,就能动态构建自己的技能目录。MCP 的“协议”价值,正是通过 Redis 的统一存储得以规模化落地。
2.3 Python 作为胶水语言:为什么不是 Node.js 或 Go?
热搜词里 Python 高频出现,绝非偶然。在 AI Agent 开发栈中,Python 是不可替代的“胶水层”,原因有三:
生态垄断性:LangChain、LlamaIndex、AutoGen 等主流 Agent 框架全部原生 Python。它们封装了 LLM 调用、Prompt 工程、工具编排等复杂逻辑,但底层状态管理留白——这正是 Redis 的切入口。你不可能用 Node.js 直接集成 LangChain 的
Tool类,因为它的invoke()方法签名是 Python 的。开发效率碾压:一个技能注册脚本,Python 10 行搞定:
import redis import json r = redis.Redis(host='localhost', port=6379, db=0) skill_def = { "name": "weather_api", "description": "Get current weather for a city", "input_schema": {"type": "object", "properties": {"city": {"type": "string"}}}, "output_schema": {"type": "object", "properties": {"temp_c": {"type": "number"}, "condition": {"type": "string"}}} } r.hset(f"skill:{skill_def['name']}", mapping=skill_def) r.setex(f"skill:{skill_def['name']}:health", 300, json.dumps({"status": "healthy"}))同等功能用 Go 写,要引入
github.com/go-redis/redis/v8,处理 context,写 error check,代码量翻 3 倍,且调试成本更高。与 AI 工具链无缝衔接:当你用 Python 写一个
@tool装饰器(如 LangChain 的StructuredTool),它的func参数就是纯 Python 函数。这个函数内部可以直接调用redis.Redis().hgetall()获取技能配置,或redis.Redis().xadd()发送事件。没有序列化/反序列化开销,没有进程间通信延迟,所有操作都在同一解释器内完成。
注意:生产环境必须用
redis-py的连接池(ConnectionPool),而不是每次redis.Redis()新建实例。我们线上集群配置max_connections=100,retry_on_timeout=True,health_check_interval=30,实测在 2000 QPS 下连接泄漏为 0。
3. 核心数据结构设计:用对数据类型,性能提升 10 倍
3.1 Agent 状态管理:Hash vs String vs JSON —— 到底该用哪个?
Agent 的实时状态(如当前对话 ID、最后一条消息、等待的工具响应、超时时间)必须高频读写。很多人直觉用 String 存整个 JSON 字符串,这是最大误区。
String 方案(错误示范):
# 存 r.set("agent:U12345:state", json.dumps({"session_id": "S123", "last_msg": "Hi", "pending_tool": "weather", "timeout_at": 1716312000})) # 取 state = json.loads(r.get("agent:U12345:state")) # 更新某字段(必须先读再改再写) state["pending_tool"] = "stock_price" r.set("agent:U12345:state", json.dumps(state))问题:每次更新都要全量读写,网络 IO 浪费 80%;并发时可能覆盖他人修改(无原子性)。
Hash 方案(推荐):
# 存(一次写多个 field) r.hset("agent:U12345:state", mapping={ "session_id": "S123", "last_msg": "Hi", "pending_tool": "weather", "timeout_at": "1716312000" }) # 取单个字段(精准读) pending = r.hget("agent:U12345:state", "pending_tool") # 原子更新单个字段 r.hset("agent:U12345:state", "pending_tool", "stock_price") # 原子递增计数器 r.hincrby("agent:U12345:state", "retry_count", 1)优势:网络包小(只传必要字段)、原子操作、支持部分更新、内存占用低(Hash 内部用 ziplist 编码小对象)。
JSON 方案(Redis 7.2+,谨慎使用): Redis 7.2 引入
JSON.SET/JSON.GET,看似完美:r.json().set("agent:U12345:state", "$", {"session_id": "S123", ...}) r.json().get("agent:U12345:state", "$.pending_tool") r.json().set("agent:U12345:state", "$.pending_tool", "stock_price")但实测发现:JSON 操作比 Hash 慢 3~5 倍(解析 JSON 开销),且内存占用高 40%(JSON 文本 + 解析树)。除非你的状态结构极其复杂(嵌套 5 层以上),否则 Hash 是更优解。
实操心得:我们给每个 Agent 分配独立 Hash key,key 格式为
agent:{user_id}:{session_id}:state。这样既能隔离状态,又便于用KEYS agent:*:state批量清理过期会话(生产环境禁用 KEYS,改用 SCAN)。
3.2 技能注册中心:Sorted Set 实现动态权重调度
MCP 规范要求技能可被发现,但没规定如何选择。真实场景中,同一类技能(如多个天气 API)可能有不同 SLA、成本、地域偏好。我们用 Redis Sorted Set 实现动态权重调度:
# 注册技能,score 是权重(越高越优先) ZADD skills:weather 100 "weather_api_aws" ZADD skills:weather 90 "weather_api_azure" ZADD skills:weather 80 "weather_api_local" # Agent 调用时,随机选一个高权重技能(避免单点故障) ZRANDMEMBER skills:weather 1 WITHSCORES # 返回 ["weather_api_aws", "100"] # 当某个技能健康检查失败,降权 ZINCRBY skills:weather -50 "weather_api_azure" # score 变为 40,下次 ZRANDMEMBER 很难选中Sorted Set 的ZRANDMEMBER支持WITHSCORES和COUNT,配合ZINCRBY动态调整,比轮询或固定配置灵活得多。我们线上用此机制实现了 99.99% 的技能调用成功率,即使 AWS API 故障,流量自动切到 Azure,用户无感。
3.3 上下文记忆:Sorted Set + TTL 的时空双维度管理
用户长期记忆不能无限增长,必须按时间(新鲜度)和重要性(人工标注)排序。我们用两个 Sorted Set 协同:
memory:timeline:{user_id}:按时间戳 score,member 是记忆 IDZADD memory:timeline:U12345 1716312000 "mem_001"memory:priority:{user_id}:按人工评分 score,member 是记忆 IDZADD memory:priority:U12345 5 "mem_001"(5 分最高)
Agent 加载记忆时:
# 取最近 24 小时 + 优先级前 10 的交集 timeline_ids = r.zrangebyscore(f"memory:timeline:U12345", 1716225600, "+inf") priority_ids = r.zrevrange(f"memory:priority:U12345", 0, 9) # Python set 交集 active_ids = set(timeline_ids) & set(priority_ids) # 批量获取内容 memories = r.mget([f"memory:{mid}" for mid in active_ids])每个记忆内容用 String 存,key 是memory:{id},value 是 JSON 序列化的记忆对象,并设置 TTL(如EXPIRE memory:mem_001 259200030 天)。这样既保证新鲜度,又保留高价值记忆,内存占用可控。
注意:
ZADD的 score 必须是数字,不能是字符串时间。我们用int(datetime.now().timestamp())生成,精度秒级足够。毫秒级用time.time() * 1000,但需注意 Redis score 最大值是 2^53-1,约 9e15,对应公元 2255 年,完全够用。
4. 生产级实操:从本地开发到 Kubernetes 集群的完整部署链
4.1 本地开发:MacOS 安装 Redis 与 Python 环境的避坑指南
热搜词里“macos 安装 redis”、“python安装教程”高频出现,说明很多开发者卡在第一步。这不是简单brew install redis就完事,关键在配置:
Redis 配置要点(
/usr/local/etc/redis.conf):# 必须关闭保护模式,否则 Python 连不上 protected-mode no # 绑定所有网卡(开发用),生产环境改回 127.0.0.1 bind 0.0.0.0 # 设置密码(避免空密码被攻击) requirepass your_strong_password_here # 开启 AOF 持久化(比 RDB 更安全,宕机最多丢 1 秒数据) appendonly yes appendfilename "appendonly.aof" # AOF 重写触发条件(避免文件过大) auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mbPython 环境陷阱:
- 不要用
pip install redis安装旧版(< 4.0),必须pip install "redis>=4.6.0",否则不支持redis-py的连接池健康检查。 - 虚拟环境必须激活后安装,否则
redis-cli命令可能找不到。 - 测试连接时,别只用
redis-cli -a password ping,要模拟 Python 客户端:import redis try: r = redis.Redis(host='localhost', port=6379, password='your_strong_password_here', decode_responses=True) r.ping() print("✅ Redis connected") except Exception as e: print(f"❌ Redis connection failed: {e}")
- 不要用
踩过的坑:Mac M1 芯片上
brew install redis默认装的是 ARM64 版本,但某些 Python 包(如旧版pymongo)的 C 扩展可能不兼容,导致import redis报ImportError: dlopen(...)。解决方案:brew uninstall redis && brew install --x86_64 redis强制 x86_64,或升级所有包到最新版。
4.2 Docker 主从部署:为什么不用哨兵(Sentinel)?
热搜词“docker安装redis主从”很实际。我们线上用 Docker Compose 部署一主二从,但坚决不用 Sentinel,原因如下:
- Sentinel 是为“高可用切换”设计的,而 AI Agent 场景要求“强一致性”。主从切换期间(通常 30~60 秒),从节点可能返回过期数据,导致 Agent 状态错乱(如重复扣款、丢失消息)。
- 我们采用“读写分离 + 主节点单点写入”策略:所有写操作(
HSET,XADD,ZADD)只打向主节点;读操作(HGET,ZRANGE,XREAD)可打向从节点,但必须加READONLY标识。
Docker Compose 示例:
version: '3.8' services: redis-master: image: redis:7.2-alpine command: redis-server /usr/local/etc/redis.conf volumes: - ./redis-master.conf:/usr/local/etc/redis.conf - redis-master-data:/data ports: - "6379:6379" networks: - ai-net redis-slave-1: image: redis:7.2-alpine command: redis-server /usr/local/etc/redis.conf volumes: - ./redis-slave.conf:/usr/local/etc/redis.conf - redis-slave-1-data:/data ports: - "6380:6379" depends_on: - redis-master networks: - ai-net redis-slave-2: image: redis:7.2-alpine command: redis-server /usr/local/etc/redis.conf volumes: - ./redis-slave.conf:/usr/local/etc/redis.conf - redis-slave-2-data:/data ports: - "6381:6379" depends_on: - redis-master networks: - ai-net volumes: redis-master-data: redis-slave-1-data: redis-slave-2-data:redis-slave.conf关键配置:
slaveof redis-master 6379 # 必须开启只读,否则从节点可能被误写 slave-read-only yes # 从节点也启用 AOF,确保宕机后能快速恢复 appendonly yesPython 客户端连接:
# 主节点(写) master = redis.Redis( host='redis-master', port=6379, password='your_master_password', decode_responses=True ) # 从节点(读),用 ConnectionPool 复用连接 slave_pool = redis.ConnectionPool( host='redis-slave-1', port=6379, password='your_slave_password', decode_responses=True, max_connections=50, retry_on_timeout=True ) slave = redis.Redis(connection_pool=slave_pool)实操心得:我们用
redis-py的ReadOnly模式(redis.Redis(..., readonly=True))强制客户端只能读,但 Docker 网络中从节点默认禁止写,所以readonly=True是双重保险。主节点密码和从节点密码必须不同,避免主节点密码泄露导致从节点被篡改。
4.3 Kubernetes 集群:StatefulSet 与 Headless Service 的正确用法
当流量超过 1 万 QPS,Docker Compose 不够用,必须上 K8s。但我们不用 Deployment 部署 Redis,因为 Deployment 的 Pod 是无状态的,IP 频繁变化,主从关系无法维持。
正确做法是 StatefulSet + Headless Service:
# headless-service.yaml apiVersion: v1 kind: Service metadata: name: redis-headless spec: clusterIP: None # 关键:Headless Service,不分配 ClusterIP selector: app: redis --- # statefulset.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: redis spec: serviceName: "redis-headless" # 关联 Headless Service replicas: 3 selector: matchLabels: app: redis template: metadata: labels: app: redis spec: containers: - name: redis image: redis:7.2-alpine ports: - containerPort: 6379 volumeMounts: - name: redis-data mountPath: /data # 初始化脚本,根据序号配置主从 command: ["/bin/sh", "-c"] args: - | if [ "$HOSTNAME" = "redis-0" ]; then echo "Starting master..." exec redis-server /usr/local/etc/redis.conf else echo "Starting slave ${HOSTNAME}..." echo "slaveof redis-0.redis-headless 6379" > /tmp/slave.conf exec redis-server /tmp/slave.conf fi volumes: - name: redis-data emptyDir: {}关键点:
serviceName: "redis-headless"让每个 Pod 有固定 DNS 名:redis-0.redis-headless,redis-1.redis-headless。- 初始化脚本根据
$HOSTNAME判断角色:redis-0是主,其他是从,且从节点明确指向redis-0.redis-headless。 - Headless Service 不做负载均衡,直接返回所有 Pod IP,Python 客户端可精确连接
redis://redis-0.redis-headless:6379(写)和redis://redis-1.redis-headless:6379(读)。
注意:K8s 中
emptyDir重启即丢,生产环境必须用PersistentVolume(如 AWS EBS、阿里云云盘),并配置volumeClaimTemplates。我们线上用 100GB SSD PV,IOPS 3000,满足 Redis 高吞吐需求。
5. 常见问题与排查技巧实录:那些文档里不会写的真相
5.1 “Redis 连接超时” —— 90% 是连接池没配好
现象:Python 日志疯狂报redis.exceptions.TimeoutError: Timeout reading from socket,但redis-cli能连。
真相:redis-py默认连接池max_connections=1000,但未设block=True,当并发请求超过 1000,新请求立即超时,而非排队等待。
修复方案:
# 错误:默认配置 r = redis.Redis(host='localhost', port=6379) # 正确:显式配置连接池 pool = redis.ConnectionPool( host='localhost', port=6379, password='your_password', max_connections=200, # 根据应用并发量设(我们设 200) block=True, # 关键!请求排队,不超时 timeout=5, # 连接池获取连接超时 5 秒 retry_on_timeout=True, health_check_interval=30 ) r = redis.Redis(connection_pool=pool)实操心得:
max_connections不是越大越好。我们压测发现,当设为 500 时,Redis 服务器 CPU 突然飙升 40%,原因是大量空闲连接占用 fd。最终定为 200,配合min_idle_conns=10(保持 10 个空闲连接),平衡资源与性能。
5.2 “Agent 状态丢失” —— AOF 重写期间的静默故障
现象:Agent 运行几小时后,状态突然清空,HGETALL返回空。
真相:Redis AOF 重写(BGREWRITEAOF)时,会 fork 子进程,如果服务器内存不足,fork 失败,AOF 文件被截断,导致数据丢失。INFO persistence中aof_last_bgrewrite_status:fail是线索。
排查命令:
# 登录 Redis,看持久化状态 redis-cli INFO persistence | grep -E "(aof_|rdb_)" # 检查系统内存 free -h # 查看 AOF 文件大小(正常应 < 100MB) ls -lh /var/lib/redis/appendonly.aof解决方案:
- 内存预留:Redis 服务器内存至少为
maxmemory的 2 倍(fork 需要复制页表)。 - AOF 重写阈值调低:
auto-aof-rewrite-percentage 50(50% 增长就重写),避免单文件过大。 - 启用
aof-use-rdb-preamble yes:AOF 文件开头嵌入 RDB 快照,重写失败时仍可加载 RDB 部分。
注意:
CONFIG SET动态修改配置后,必须CONFIG REWRITE写入 conf 文件,否则重启失效。
5.3 “MCP 技能调用失败” —— 健康检查的假阳性
现象:skill:weather_api:health显示healthy,但实际调用返回 503。
真相:健康检查接口(/health)只检查服务进程存活,不检查依赖(如天气 API 的上游 Key 是否过期、数据库连接是否正常)。
根治方案:健康检查必须包含端到端验证:
# 技能健康检查函数(Python) def check_weather_health(): try: # 1. 检查自身进程 if not is_process_running("weather-api"): return False # 2. 检查上游依赖(调用天气 API 的测试 endpoint) resp = requests.get("https://api.weather.com/v3/weather/forecast/daily?postalKey=12345:US&language=en-US&format=json", headers={"Authorization": "Bearer " + get_api_key()}) if resp.status_code != 200: return False # 3. 检查数据库连接(如果技能依赖 DB) db_conn = get_db_connection() db_conn.execute("SELECT 1") return True except Exception as e: logger.error(f"Weather health check failed: {e}") return False # 每 30 秒执行一次,结果存 Redis if check_weather_health(): r.setex("skill:weather_api:health", 300, json.dumps({"status": "healthy"})) else: r.setex("skill:weather_api:health", 300, json.dumps({"status": "unhealthy", "reason": "upstream_failed"}))实操心得:我们把健康检查逻辑封装成独立服务(
health-checker),由它统一扫描所有技能,避免每个技能服务自己实现,降低维护成本。Redis 只存结果,不存逻辑。
5.4 “Python Agent 内存暴涨” —— Redis 连接未释放的隐性泄漏
现象:Agent 进程内存持续增长,ps aux --sort=-%mem显示 Python 进程占满 4GB 内存。
真相:redis-py的ConnectionPool会复用连接,但如果代码中频繁创建新Redis实例(如每个请求都redis.Redis()),连接池对象本身会累积,且连接池中的 socket 连接未关闭。
典型错误代码:
# ❌ 每次请求都新建 Redis 实例 def handle_request(): r = redis.Redis(host='localhost') # 新建连接池! r.hset("state", "key", "value") return "ok"正确写法:
# ✅ 全局复用一个连接池 REDIS_POOL = redis.ConnectionPool( host='localhost', port=6379, password='your_password', max_connections=200, block=True ) def handle_request(): r = redis.Redis(connection_pool=REDIS_POOL) # 复用同一个 pool r.hset("state", "key", "value") return "ok"提示:用
psutil监控 Python 进程的 fd 数量,lsof -p <pid> | wc -l,如果持续增长,基本确定是连接泄漏。我们线上监控告警阈值设为 1000 个 fd。
6. 工具链与生态整合:RuoYi-Vue-Pro 合并 MCP 功能的实战拆解
6.1 前端视角:Browser Use MCP 与 Playwright MCP 的本质区别
热搜词提到“browser use mcp 跟 playwright mcp 有什么区别”,这触及了 MCP 的落地形态。本质区别在于控制权归属:
Browser Use MCP:前端 JavaScript 直接调用 MCP 技能(如点击按钮触发
fetch("/v1/skills/weather/invoke", {...}))。
优点:响应快,无后端跳转。
缺点:技能密钥暴露在前端(绝对禁止!),无法做权限校验,状态管理混乱。Playwright MCP:Playwright(浏览器自动化库)作为 Agent 的“手”,执行 MCP 技能调用。
例如:Agent 决定“打开京东搜索 AirPods”,Playwright 启动 Chromium,导航到https://www.jd.com,输入关键词,截图返回。
本质:Playwright 是 MCP 协议的一个具体技能实现,它封装了浏览器操作,对外暴露标准 MCP 接口(name="browser_automation",input_schema={"url": "string", "action": "string"})。
我们在 RuoYi-Vue-Pro 中的实践:
- 前端 Vue 页面只负责展示 Agent 状态和消息,所有技能调用由后端 Python Agent 发起。
- Playwright 作为独立微服务(
playwright-agent),接收 Python 的 HTTP 请求,执行浏览器操作,结果存回 Redis 的stream:browser_results。 - 前端通过 SSE(Server-Sent Events)订阅
stream:browser_results,实时渲染截图和操作日志。
这样既保障了密钥安全,又实现了前后端分离,还利用了 Playwright 的强大能力。
6.2 专利相关辅助:AI 辅助专利检索的 Redis 设计
热搜词“专利相关辅助链接 ai辅助”、“专利相关链接(ai辅助)”指向一个典型场景:专利工程师用 AI 检索相似专利。挑战在于:专利文本巨大(单篇 PDF 50MB+),向量检索耗时,且需支持“语义+分类+法律状态”多维过滤。
我们的 Redis 设计:
- 元数据存 Hash:`