Files
Mofox-Core/docs/database_cache_guide.md
2025-12-08 17:42:57 +08:00

310 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 数据库缓存系统使用指南
## 概述
MoFox Bot 数据库系统集成了可插拔的缓存架构,支持多种缓存后端:
- **内存缓存Memory**: 多级 LRU 缓存,适合单机部署
- **Redis 缓存**: 分布式缓存,适合多实例部署或需要持久化缓存的场景
## 缓存后端选择
`bot_config.toml` 中配置:
```toml
[database]
enable_database_cache = true # 是否启用缓存
cache_backend = "memory" # 缓存后端: "memory" 或 "redis"
```
### 后端对比
| 特性 | 内存缓存 (memory) | Redis 缓存 (redis) |
|------|-------------------|-------------------|
| 部署复杂度 | 低(无额外依赖) | 中(需要 Redis 服务) |
| 分布式支持 | ❌ | ✅ |
| 持久化 | ❌ | ✅ |
| 性能 | 极高(本地内存) | 高(网络开销) |
| 适用场景 | 单机部署 | 多实例/集群部署 |
---
## 内存缓存架构
### 多级缓存Multi-Level Cache
- **L1 缓存(热数据)**
- 容量1000 项(可配置)
- TTL300 秒(可配置)
- 用途:最近访问的热点数据
- **L2 缓存(温数据)**
- 容量10000 项(可配置)
- TTL1800 秒(可配置)
- 用途:较常访问但不是最热的数据
### LRU 驱逐策略
两级缓存都使用 LRULeast Recently Used算法
- 缓存满时自动驱逐最少使用的项
- 保证最常用数据始终在缓存中
---
## Redis 缓存架构
### 特性
- **分布式**: 多个 Bot 实例可共享缓存
- **持久化**: Redis 支持 RDB/AOF 持久化
- **TTL 管理**: 使用 Redis 原生过期机制
- **模式删除**: 支持通配符批量删除缓存
- **原子操作**: 支持 INCR/DECR 等原子操作
### 配置参数
```toml
[database]
# Redis缓存配置cache_backend = "redis" 时生效)
redis_host = "localhost" # Redis服务器地址
redis_port = 6379 # Redis服务器端口
redis_password = "" # Redis密码留空表示无密码
redis_db = 0 # Redis数据库编号 (0-15)
redis_key_prefix = "mofox:" # 缓存键前缀
redis_default_ttl = 600 # 默认过期时间(秒)
redis_connection_pool_size = 10 # 连接池大小
```
### 安装 Redis 依赖
```bash
pip install redis
```
---
## 使用方法
### 1. 使用 @cached 装饰器(推荐)
最简单的方式,自动适配所有缓存后端:
```python
from src.common.database.utils.decorators import cached
@cached(ttl=600, key_prefix="person_info")
async def get_person_info(platform: str, person_id: str):
"""获取人员信息带10分钟缓存"""
return await _person_info_crud.get_by(
platform=platform,
person_id=person_id,
)
```
#### 参数说明
- `ttl`: 缓存过期时间None 表示永不过期
- `key_prefix`: 缓存键前缀,用于命名空间隔离
- `use_args`: 是否将位置参数包含在缓存键中(默认 True
- `use_kwargs`: 是否将关键字参数包含在缓存键中(默认 True
### 2. 手动缓存管理
需要更精细控制时,可以手动管理缓存:
```python
from src.common.database.optimization import get_cache
async def custom_query():
cache = await get_cache()
# 尝试从缓存获取
result = await cache.get("my_key")
if result is not None:
return result
# 缓存未命中,执行查询
result = await execute_database_query()
# 写入缓存(可指定自定义 TTL
await cache.set("my_key", result, ttl=300)
return result
```
### 3. 使用 get_or_load 方法
简化的缓存加载模式:
```python
cache = await get_cache()
# 自动处理:缓存命中返回,未命中则执行 loader 并缓存结果
result = await cache.get_or_load(
"my_key",
loader=lambda: fetch_data_from_db(),
ttl=600
)
```
### 4. 缓存失效
更新数据后需要主动使缓存失效:
```python
from src.common.database.optimization import get_cache
from src.common.database.utils.decorators import generate_cache_key
async def update_person_affinity(platform: str, person_id: str, affinity_delta: float):
# 执行更新
await _person_info_crud.update(person.id, {"affinity": new_affinity})
# 使缓存失效
cache = await get_cache()
cache_key = generate_cache_key("person_info", platform, person_id)
await cache.delete(cache_key)
```
---
## 已缓存的查询
### PersonInfo人员信息
- **函数**: `get_or_create_person()`
- **缓存时间**: 10 分钟
- **缓存键**: `person_info:args:<hash>`
- **失效时机**: `update_person_affinity()` 更新好感度时
### UserRelationships用户关系
- **函数**: `get_user_relationship()`
- **缓存时间**: 5 分钟
- **缓存键**: `user_relationship:args:<hash>`
- **失效时机**: `update_relationship_affinity()` 更新关系时
### ChatStreams聊天流
- **函数**: `get_or_create_chat_stream()`
- **缓存时间**: 5 分钟
- **缓存键**: `chat_stream:args:<hash>`
- **失效时机**: 流更新时(如有需要)
## 缓存统计
### 内存缓存统计
```python
cache = await get_cache()
stats = await cache.get_stats()
if cache.backend_type == "memory":
print(f"L1: {stats['l1'].item_count}项, 命中率 {stats['l1'].hit_rate:.2%}")
print(f"L2: {stats['l2'].item_count}项, 命中率 {stats['l2'].hit_rate:.2%}")
```
### Redis 缓存统计
```python
if cache.backend_type == "redis":
print(f"命中率: {stats['hit_rate']:.2%}")
print(f"键数量: {stats['key_count']}")
```
### 检查当前后端类型
```python
from src.common.database.optimization import get_cache_backend_type
backend = get_cache_backend_type() # "memory" 或 "redis"
```
---
## 最佳实践
### 1. 选择合适的 TTL
- **频繁变化的数据**: 60-300 秒(如在线状态)
- **中等变化的数据**: 300-600 秒(如用户信息、关系)
- **稳定数据**: 600-1800 秒(如配置、元数据)
### 2. 缓存键设计
- 使用有意义的前缀:`person_info:`, `user_rel:`, `chat_stream:`
- 确保唯一性:包含所有查询参数
- 避免键冲突:使用 `generate_cache_key()` 辅助函数
### 3. 及时失效
- **写入时失效**: 数据更新后立即删除缓存
- **批量失效**: 使用通配符或前缀批量删除相关缓存
- **惰性失效**: 依赖 TTL 自动过期(适用于非关键数据)
### 4. 监控缓存效果
定期检查缓存统计:
- 命中率 > 70% - 缓存效果良好 ✅
- 命中率 50-70% - 可以优化 TTL 或缓存策略 ⚠️
- 命中率 < 50% - 考虑是否需要缓存该查询
---
## 性能提升数据
基于测试结果
- **PersonInfo 查询**: 缓存命中时减少 **90%+** 数据库访问
- **关系查询**: 高频场景下减少 **80%+** 数据库连接
- **聊天流查询**: 活跃会话期间减少 **75%+** 重复查询
## 注意事项
1. **缓存一致性**: 更新数据后务必使缓存失效
2. **内存占用**: 监控缓存大小避免占用过多内存
3. **序列化**: 缓存的对象需要可序列化
- 内存缓存直接存储 Python 对象
- Redis 缓存默认使用 JSON复杂对象自动回退到 Pickle
4. **并发安全**: 两种后端都是协程安全的
5. **无自动回退**: Redis 连接失败时会抛出异常不会自动回退到内存缓存确保配置正确
---
## 故障排除
### 缓存未生效
1. 检查 `enable_database_cache = true`
2. 检查是否正确导入装饰器
3. 确认 TTL 设置合理
4. 查看日志中的缓存消息
### 数据不一致
1. 检查更新操作是否正确使缓存失效
2. 确认缓存键生成逻辑一致
3. 考虑缩短 TTL 时间
### 内存占用过高(内存缓存)
1. 检查缓存统计中的项数
2. 调整 L1/L2 缓存大小
3. 缩短 TTL 加快驱逐
### Redis 连接失败
1. 检查 Redis 服务是否运行
2. 确认连接参数host/port/password
3. 检查防火墙/网络设置
4. 查看日志中的错误信息
---
## 扩展阅读
- [缓存后端抽象](../src/common/database/optimization/cache_backend.py)
- [内存缓存实现](../src/common/database/optimization/cache_manager.py)
- [Redis 缓存实现](../src/common/database/optimization/redis_cache.py)
- [缓存装饰器](../src/common/database/utils/decorators.py)