一、Redis 安装
- 参考安装文档,安装文档最后一步修改密码是临时密码,重启后就会恢复默认密码
- 安装好 Redis 的默认密码为空
- 如果想永久修改密码,打开 Redis 安装目录下的
redis.windows-service.conf,找到# requirepass foobared ,设置密码
requirepass root
- Redis 安装完成后,默认有 16 个数据库,索引值从 0-15,默认选择第 0 个
- Redis 图形化界面工具可以下载 redis-desktop,或者直接用 navicat
1、基本使用
2、python API
- 通过 python 相关库来操作 Redis
- 安装
redis库
pip install -i https://repo.huaweicloud.com/repository/pypi/simple/ redis
2.1 连接
import redis
r = redis.Redis(
host="127.0.0.1", # Redis 服务器地址
port=6379, # Redis 端口号
db=0, # Redis 数据库编号
password="root", # Redis 密码
decode_responses=True, # 让 Redis 返回字符串,而不是字节
)
print(r.ping())
import redis
def redis_conn():
return redis.Redis(
host="127.0.0.1",
port=6379,
db=0,
password="root",
decode_responses=True, # 返回结果为字符串
)
2.2 字符串操作
- 字符串操作是 Redis 中用的最多的操作
- 常用方法
| 方法 |
功能 |
参数 |
说明 |
set(name, value, ex=None, px=None, nx=False, xx=False) |
设置字符串类型的 key-value |
name:keyvalue:要存的值(支持字符串、数字、bytes)ex:过期时间(秒)px:过期时间(毫秒)nx:只在 key 不存在时设置xx:只在 key 存在时设置 |
返回 True 设置成功,False 失败(用于 nx/xx) |
get(name) |
获取 key 对应的 value |
name:key |
返回字符串(如果 decode_responses=True)或 bytes(默认) |
getset(name, value) |
设置新值并返回旧值 |
name:keyvalue:新值 |
原子操作,返回旧值,如果 key 不存在返回 None |
mget(keys, *args) |
批量获取多个 key 的值 |
keys:key 列表 |
返回列表,对应每个 key 的值,key 不存在返回 None |
mset(mapping) |
批量设置多个 key-value |
mapping:字典 {"key1": "val1", "key2": "val2"} |
返回 True |
incr(name, amount=1) |
自增整数 |
name:keyamount:增加的值(默认1) |
返回新的整数值,如果 key 不存在,会先设置为 0 再自增 |
decr(name, amount=1) |
自减整数 |
name:keyamount:减少的值(默认1) |
返回新的整数值 |
| 属性/参数 |
说明 |
ex |
设置 key 的过期时间(秒) |
px |
设置 key 的过期时间(毫秒) |
nx |
仅当 key 不存在时设置 |
xx |
仅当 key 存在时设置 |
decode_responses(Redis 连接属性) |
如果为 True,get 返回字符串而非 bytes |
from sturedis import RedisUtil
r = RedisUtil.redis_conn()
# 设置 key
r.set("name", "Alice")
r.set("temp", "123", ex=10) # 10秒后过期
r.set("score", 100, nx=True) # 只有 score 不存在时才设置
r.set("score", 200, xx=True) # 只有 score 已存在时才修改
# 获取 key
print(r.get("name")) # Alice
print(r.get("score")) # 200
# 自增/自减
r.set("counter", 10)
r.incr("counter", 5)
print(r.get("counter")) # 15
r.decr("counter", 3)
print(r.get("counter")) # 12
![image-20251017151012500]()
2.3 哈希操作
- 类似 Python dict,适合存对象
- 常用方法
| 方法 |
功能 |
参数 |
说明 |
hset(name, key, value=None, mapping=None) |
设置 hash 中的字段 |
name:hash keykey:字段名value:字段值mapping:字典批量设置 |
返回被新增字段的数量 |
hget(name, key) |
获取字段值 |
name:hash keykey:字段名 |
返回字段值,如果字段不存在返回 None |
hgetall(name) |
获取整个 hash |
name:hash key |
返回字典,包含所有字段和值 |
hmget(name, keys, *args) |
批量获取字段 |
keys:字段列表 |
返回对应字段值列表,不存在字段返回 None |
hmset(name, mapping) |
批量设置字段 |
mapping:字典 |
返回 True(已废弃,可用 hset(name, mapping=mapping) 替代) |
hdel(name, *keys) |
删除字段 |
keys:字段名列表 |
返回成功删除字段数量 |
hlen(name) |
获取 hash 长度 |
name:hash key |
返回字段数量 |
hexists(name, key) |
判断字段是否存在 |
key:字段名 |
返回 True/False |
from sturedis import RedisUtil
r = RedisUtil.redis_conn()
r.hset("user:1001", mapping={"username": "Alice", "age": 25})
print(r.hget("user:1001", "username")) # Alice
print(r.hgetall("user:1001")) # {'username': 'Alice', 'age': '25'}
r.hdel("user:1001", "age")
![image-20251017151501535]()
2.4 列表操作
| 方法 |
功能 |
参数 |
说明 |
lpush(name, *values) |
从左侧推入元素 |
values:一个或多个元素 |
返回列表长度 |
rpush(name, *values) |
从右侧推入元素 |
values:一个或多个元素 |
返回列表长度 |
lpop(name) |
左侧弹出元素 |
- |
返回弹出的元素 |
rpop(name) |
右侧弹出元素 |
- |
返回弹出的元素 |
lrange(name, start, end) |
获取列表指定范围 |
start/end:索引 |
返回列表元素列表,索引可为负数 |
lindex(name, index) |
获取指定索引元素 |
index:位置 |
返回元素值 |
lrem(name, count, value) |
删除元素 |
count:删除个数value:值 |
count>0从左删除,<0从右删除,=0删除所有匹配值 |
llen(name) |
获取列表长度 |
- |
返回元素个数 |
from sturedis import RedisUtil
r = RedisUtil.redis_conn()
r.lpush("tasks", "task1", "task2")
r.rpush("tasks", "task3")
print(r.lrange("tasks", 0, -1)) # ['task2', 'task1', 'task3']
r.lpop("tasks") # task2
r.rpop("tasks") # task3
![image-20251017151619584]()
2.5 集合操作
| 方法 |
功能 |
参数 |
说明 |
sadd(name, *values) |
添加元素 |
values:一个或多个元素 |
返回成功添加元素数量 |
srem(name, *values) |
删除元素 |
values:一个或多个元素 |
返回删除元素数量 |
smembers(name) |
获取所有元素 |
- |
返回集合(set) |
sismember(name, value) |
判断元素是否存在 |
value:元素 |
返回 True/False |
scard(name) |
获取集合大小 |
- |
返回元素数量 |
sinter(keys, *args) |
求交集 |
key 列表 |
返回交集元素集合 |
sunion(keys, *args) |
求并集 |
key 列表 |
返回并集元素集合 |
sdiff(keys, *args) |
求差集 |
key 列表 |
返回差集元素集合 |
from sturedis import RedisUtil
r = RedisUtil.redis_conn()
r.sadd("fruits", "apple", "banana", "orange")
print(r.smembers("fruits")) # {'apple', 'banana', 'orange'}
r.srem("fruits", "banana")
print(r.sismember("fruits", "banana")) # False
![image-20251017151828950]()
2.6 有序集合操作
| 方法 |
功能 |
参数 |
说明 |
zadd(name, mapping) |
添加元素及分数 |
mapping:字典 {element: score} |
返回成功新增元素数量 |
zrange(name, start, end, withscores=False) |
按分数从小到大获取元素 |
withscores=True返回分数 |
返回列表 |
zrevrange(name, start, end, withscores=False) |
按分数从大到小获取元素 |
- |
返回列表 |
zrem(name, *values) |
删除元素 |
- |
返回删除数量 |
zscore(name, value) |
获取元素分数 |
- |
返回元素分数 |
zcard(name) |
获取集合大小 |
- |
返回元素数量 |
zincrby(name, amount, value) |
对元素分数自增 |
amount:增加量 |
返回新的分数 |
from sturedis import RedisUtil
r = RedisUtil.redis_conn()
r.zadd("scores", {"Alice": 100, "Bob": 80})
print(r.zrange("scores", 0, -1, withscores=True)) # [('Bob', 80.0), ('Alice', 100.0)]
r.zincrby("scores", 20, "Bob")
print(r.zscore("scores", "Bob")) # 100.0
![image-20251017152016718]()
二、LLM 多轮对话
- 我们可以把 LLM 多轮对话中的历史对话信息存储在 Redis 中,推荐使用列表存储,原因如下:
- List 的特点:
- 有序(按插入顺序)
- 可以重复元素
- 左右推入弹出操作很方便
- 非常适合存储多条消息的时间顺序
1、一次存储整个对话
import redis, json
r = redis.Redis(decode_responses=True)
conversation = [
{"role": "user", "content": "你好"},
{"role": "assistant", "content": "你好呀,有什么可以帮你?"}
]
# 存储
r.set("chat:1001", json.dumps(conversation))
# 获取
history = json.loads(r.get("chat:1001"))
![image-20251017152949155]()
![image-20251017152955146]()
| 问题 |
说明 |
| 无法增量追加 |
如果用户又发了一条消息,你必须先取出整个列表,append 再存回去。多线程或并发场景容易覆盖数据。 |
| 性能问题 |
对话历史长时,每次都要序列化/反序列化整个列表,消耗大。 |
| 无法限制长度 |
不能直接用 Redis 的 ltrim 等方法限制条数,只能自己处理。 |
| 原子性差 |
在高并发下容易出现“最后写入覆盖先前消息”的问题。 |
2、List + JSON 每条消息
- 直接向 Redis 中的 List 中加入新数据即可,不需要全部存储进去
from sturedis import RedisUtil
import redis, json
r = RedisUtil.redis_conn()
# 存储
r.rpush("chat2:1001", json.dumps({"role": "user", "content": "你好"}))
r.rpush("chat2:1001", json.dumps({"role": "assistant", "content": "你好呀,有什么可以帮你?"}))
r.rpush("chat2:1001", json.dumps({"role": "user", "content": "新消息"}))
r.rpush("chat2:1001", json.dumps({"role": "assistant", "content": "新消息的回复"}))
# 获取
history = [json.loads(item) for item in r.lrange("chat2:1001", 0, -1)]
print(history)
![image-20251017153601048]()
![image-20251017153537556]()
3、实战
- 搭建 FastAPI 项目,引入一个大模型,实现多轮对话
- 在多轮对话中,服务器在将用户新输入发送给大模型前,会先将其追加到对应用户或会话的 Redis 历史记录中(key 唯一保证数据隔离),并将完整历史作为模型输入生成连续回复
- 代码实现:不想写了。。。