Redis 1:简述Redis概念与Agent的联系

一、Redis 是什么

Redis 是一个高性能的键值型内存数据库,数据默认放在内存里,读写延迟通常在亚毫秒级,是后端最常用的缓存与中间件之一。

它有三个区别于普通数据库的特点:

  • 内存存储------没有磁盘寻道,单次读写微秒级;
  • 数据结构丰富------不只是字符串,还有 Hash、List、Set、ZSet 等;
  • 单线程执行命令 ------每条命令天然原子,不用加锁就能保证 INCR、SADD 的正确性。

因为它快、能设过期、能被多个服务实例共享,所以特别适合高频读写、且生命周期有限的数据。

二、为什么 Redis 适合做 Agent 短期记忆

大模型本身是无状态的,跨请求不会记得你说过什么。想让它"记住",必须由外部存储来保存上下文。常见分层是:

复制代码
本轮内存 message  →  Redis(短期记忆)  →  PostgreSQL / 向量库(长期记忆)

短期记忆和长期记忆的分工:

短期记忆 长期记忆
解决什么 当前会话说了什么、任务进行到哪一步 跨会话的偏好、长期目标、值得沉淀的历史信息
存储 Redis PostgreSQL + 向量检索(如 Mem0)
关键需求 快、可过期、多实例共享 可检索、可语义匹配、长期保留

Redis 的四项特性正好对上短期记忆的需求:

  1. TTL(Time To Live)------会话闲置一段时间自动清理,不用自己写定时任务;
  2. 多实例共享------所有 Agent 实例连同一个 Redis,负载均衡打到哪台机器都能读到同一份上下文;
  3. 丰富的数据结构------String 存摘要,Hash 存会话元信息,List 直接存对话列表;
  4. 持久化------AOF/RDB 保证重启后数据不丢。

还有一个绕不开的问题:上下文窗口有限。不可能每次把完整聊天记录塞给模型,否则 token 超限、成本上升、延迟变长。所以工程上要配合上下文截断、摘要压缩和相关记忆检索,只把"当前真正需要的那部分"组装进 Prompt。

三、连接 Redis

1. 用 Docker 起一个

yaml 复制代码
services:
  redis:
    image: redis:7-alpine
    container_name: agent_redis
    command: redis-server --appendonly yes
    ports:
      - "6380:6379"        # 宿主机 6380 → 容器 6379
    volumes:
      - ./data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
    networks:
      - common-network

networks:
  common-network:
    name: common-network

注意端口的方向:容器内永远是 6379,6380 只是宿主机映射出来的门牌号 。宿主机程序连 localhost:6380,容器之间互连则用 redis:6379。

2. 用 ioredis 连接(Node.js)

js 复制代码
import Redis from 'ioredis';

const redis = new Redis({
  host: 'localhost',
  port: 6380,
  db: 0,
});

redis.on('connect', () => console.log('链接成功'));
redis.on('error', (err) => console.log('链接失败', err));

async function runRedisDemo() {
  await redis.set('name', '文强');
  await redis.set('code', '666666', 'EX', 300);   // 5 分钟后过期
  console.log('String name:', await redis.get('name'));

  await redis.hset('user:1011', 'name', '李四', 'age', 28);
  console.log('Hash user:', await redis.hgetall('user:1011'));

  await redis.quit();   // 关键:主动断开,否则进程不退出
}

runRedisDemo();

这三个点最容易踩:

  • 漏写 redis.quit():ioredis 会保持长连接,Node 认为还有待处理事件,脚本跑完也不退出,看起来像"卡死"。
  • 多写一个 password :只有服务端配了 requirepass 才需要。没配却传 password,会报 ERR AUTH called without any password configured。
  • 用 npm run 跑文件 :npm run 只能跟 package.json 里 scripts 定义的名字,直接跑脚本要用 node src/redis-test.mjs。

四、五种核心数据类型

Redis 的 key 只是名字,类型绑定在 key 上 ,第一次写入时确定,之后不能改;用错命令会报 WRONGTYPE。类型决定了底层结构和命令复杂度,所以选型本质是"看你怎么读"。

1. String ------ 一个变量

最简单的类型,一个 key 对应一个值(文本或数字,二进制安全)。

场景:验证码、token、登录会话、计数器、配置项、文章阅读数、文本类短期记忆(摘要)。

redis 复制代码
SET code:13800138000 666666 EX 300   -- 验证码,5 分钟过期
GET code:13800138000
INCR counter:article:1024            -- 阅读数 +1,原子
DECR counter:article:1024
INCRBY counter:article:1024 10       -- 按步长增加
SET lock:order:2001 "locked" NX EX 10  -- 简易分布式锁

ioredis:

js 复制代码
await redis.set('code:13800138000', '666666', 'EX', 300);
await redis.get('code:13800138000');
await redis.incr('counter:article:1024');

要点 :EX 单位是秒(PX 是毫秒);NX 表示"不存在才写",配合 EX 就是最常用的锁写法。

2. Hash ------ 一个对象

字段 → 值的映射,相当于一个 Python dict。字段少时会用紧凑编码,比拆成多个 String key 省内存。

场景:用户信息、商品资料、购物车、会话元信息、结构化的对话上下文。

redis 复制代码
HSET user:1011 name 李四 age 28        -- 一次写多个字段
HMSET user:1011 name 李四 age 28       -- 老写法,效果同上
HGETALL user:1011                      -- 取全部字段和值
HKEYS user:1011                        -- 只取字段名
HVALS user:1011                        -- 只取值
HINCRBY user:1011 age 1                -- 某个字段自增

ioredis:

js 复制代码
await redis.hset('user:1011', 'name', '李四', 'age', 28);
const user = await redis.hgetall('user:1011');   // { name: '李四', age: '28' }
console.log(user.name);

要点 :hgetall 返回的值全是字符串 ,age 拿到的是 '28' 而不是 28,需要自己转换。

3. List ------ 一个队列 / 栈

双向链表,有序、允许重复,两端插入极快。

场景:消息队列、任务队列、操作日志、聊天历史、按时间追加的记录。

redis 复制代码
LPUSH chat:1001 "你好"          -- 从左边插入(新的在前)
RPUSH chat:1001 "在吗"          -- 从右边插入(新的在后)
LRANGE chat:1001 0 -1           -- 取全部,-1 表示最后一个
LRANGE chat:1001 0 9            -- 取最近 10 条
LPOP chat:1001                  -- 左边弹出
RPOP chat:1001                  -- 右边弹出
LLEN chat:1001                  -- 长度

要点 :LRANGE key 0 9 是最常用的"取最近 N 条",前提是写入时用 LPUSH(新消息在头部)。顺序搞反了会拿到最旧的 N 条。

4. Set ------ 一个去重集合

无序、自动去重,支持交集、并集、差集运算,且都在服务端完成。

场景:数组去重、每日签到、IP 黑名单、共同好友、权限标签。

redis 复制代码
SADD tags:post:1 "python" "redis"   -- 添加(重复的会被忽略)
SMEMBERS tags:post:1                -- 取全部成员
SISMEMBER tags:post:1 "redis"       -- 判断是否在集合里,O(1)
SINTER tag:a tag:b                  -- 交集
SDIFF  tag:a tag:b                  -- 差集
SUNION tag:a tag:b                  -- 并集

ioredis:

js 复制代码
await redis.sadd('tags:post:1', 'python', 'redis');
const members = await redis.smembers('tags:post:1');
const isMember = await redis.sismember('tags:post:1', 'redis');  // 0 或 1

要点 :SISMEMBER 返回 1/0 而不是 true/false。

5. ZSet ------ 带分数的有序集合

每个成员绑定一个 score,按 score 自动排序。这是 Redis 里唯一"能排序又能快速范围查询"的结构。

场景:各类排行榜、内容热度排序、用户积分排名、按时间排序的记录、延迟队列。

redis 复制代码
ZADD rank:score 100 "张三" 80 "李四"   -- 写入成员和分数
ZRANGE rank:score 0 -1                 -- 按分数从小到大取
ZREVRANGE rank:score 0 9 WITHSCORES    -- 取前 10 名(分数从高到低)
ZSCORE rank:score "张三"               -- 查某个成员的分数
ZREVRANK rank:score "张三"             -- 查排名(从 0 开始)

要点 :ZREVRANGE 配合 WITHSCORES 是排行榜的标准写法;把时间戳当 score,就变成"按时间窗口查询"的索引。

6. 另外三种(了解即可)

类型 用途 核心命令
Bitmap 签到打卡、活跃统计,一位一个状态 SETBIT / GETBIT / BITCOUNT
HyperLogLog 用 12KB 估算上亿 UV,有误差 PFADD / PFCOUNT
GEO 附近门店、附近用户、两点距离 GEOADD key 经度 纬度 名称 / GEODIST key A B km

GEO 的坐标顺序是先经度后纬度,写反了位置会跑到南极。

五、选型速查

你的需求 选它 原因
存一个整体值(JSON、token、计数) String 整块读写最直接
要单独改某个字段 Hash 不用读全量再写回
按顺序追加、取最近 N 条 List 两端插入 O(1)
去重、判断存在、集合运算 Set 自动去重 + O(1) 查询
要排序、要排名、按分数范围查 ZSet 唯一有序结构
按位记录状态(签到) Bitmap 极省内存
统计 UV / 附近的人 HyperLogLog / GEO 专用算法

一句话判断标准:先想"我要怎么读",再选结构,而不是先想"数据长什么样"。

六、Agent 短期记忆的 Key 设计

把上面的结构组合起来,一套可直接落地的命名方案:

text 复制代码
memory:session:{sid}:messages   →  List    对话历史,LPUSH + LRANGE 0 19
memory:session:{sid}:meta       →  Hash    user_id、created_at、token 数、当前任务步
memory:session:{sid}:summary    →  String  压缩后的会话摘要
memory:article:{id}:views       →  String  INCR 阅读数
memory:tags                     →  Set     去重标签
memory:rank:hot                 →  ZSet    热度排行

配套的过期策略:

js 复制代码
// 写入消息时同步续期,闲置 30 分钟自动清理
await redis.lpush(`memory:session:${sid}:messages`, JSON.stringify(msg));
await redis.expire(`memory:session:${sid}:messages`, 1800);

三个工程建议:

  1. key 带冒号分层 ,方便 SCAN 前缀遍历,也方便 RedisInsight 里按模式筛选;
  2. 每个 key 都设 TTL,短期记忆的价值随时间衰减,不清理会无限膨胀;
  3. 别用 KEYS * ,它会阻塞整个单线程服务,线上用 SCAN 增量遍历。

七、结语

Redis 的价值不在"快"这一个字,而在把数据结构做成了服务端原语:你在客户端只需发一条命令,排序、去重、取交集、按分数截取全部在服务端完成,只传指令不传数据。

放到 Agent 场景里,它承担的角色很明确------管当前会话的内存:TTL 负责遗忘,数据结构负责组织,多实例共享负责横向扩展。跨会话的长期记忆交给 PostgreSQL + 向量检索,两者拼接成 Prompt,才是完整的记忆系统。

相关推荐
艾思控驱动器1 小时前
手写一个电流环采样时序校核工具:为什么你的 FOC 电流总差几个百分点
后端
智驭未来掌门人1 小时前
桌面程序里的“1K 以内”:到底该限制哪一层像素(C++ Qt 版)
后端
reesun1 小时前
分层干不干净,要等你想换掉一层才见分晓:DuckDB 2.0 换掉 parser 靠的那道缝
后端
逸风尊者2 小时前
智能调度系统从0到1:系统架构设计方法
后端
苏三说技术2 小时前
对话式导购 + RAG + MCP 智能商品推荐系统
后端
dora2 小时前
接口被疯狂调用怎么办?聊聊接口限流
后端·安全
136096757233 小时前
评测结果怎么用:从失败样本到提示词规则
后端
不一样的少年_3 小时前
为什么你写的 Agent 一报错就死循环?很多人都漏掉了这层“错误自救与熔断”机制
前端·后端·agent