You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
9.7 KiB
9.7 KiB
RFC 138: 动态定价集成
状态: 已完成
创建日期: 2026-01-27
作者: System
类型: 功能增强
概述
将 Credit Service 的定价计算与 AI Service 集成,优先使用 ai_models 表的动态定价,并实现 Redis 缓存和降级策略。
背景
当前 calculate_credits() 方法使用 credit_pricing 表的静态定价规则:
- 维护成本高: 需要手动维护两套定价数据(
credit_pricing和ai_models) - 数据不一致: 两个表的定价可能不同步
- 扩展性差: 添加新模型需要同时更新两个表
- 缺少缓存: 每次计算都查询数据库
问题
- AI Service 的
ai_models表已包含credits_per_unit字段,但未被使用 - Credit Service 和 AI Service 的定价逻辑分离
- 没有缓存机制,性能不佳
- 缺少降级策略,AI Service 不可用时无法计算
解决方案
设计原则
- 优先级: AI Service > credit_pricing 表
- 缓存优化: 使用 Redis 缓存模型定价(TTL 1 小时)
- 降级策略: AI Service 不可用时使用 credit_pricing 表
- 向后兼容: 保留原有定价逻辑作为降级方案
实现细节
1. Service 层 - 注入 AI Model Repository
# app/services/credit_service.py
class CreditService:
def __init__(self, session: AsyncSession):
self.repository = CreditRepository(session)
self.session = session
# AI Model Repository 用于动态定价(延迟导入避免循环依赖)
self._ai_model_repository = None
@property
def ai_model_repository(self):
"""延迟加载 AI Model Repository"""
if self._ai_model_repository is None:
from app.repositories.ai_model_repository import AIModelRepository
self._ai_model_repository = AIModelRepository(self.session)
return self._ai_model_repository
2. 从 AI Service 获取定价(带缓存)
async def _get_model_pricing_from_ai_service(
self,
feature_type: int,
model_name: Optional[str] = None
) -> Optional[Dict[str, Any]]:
"""从 AI Service 获取模型定价(带 Redis 缓存)"""
try:
from app.core.cache import cache
from app.models.ai_model import AIModelType
# 映射 feature_type 到 model_type
feature_to_model_type = {
FeatureType.IMAGE_GENERATION: AIModelType.IMAGE,
FeatureType.VIDEO_GENERATION: AIModelType.VIDEO,
FeatureType.TEXT_PROCESSING: AIModelType.TEXT,
FeatureType.AUDIO_GENERATION: AIModelType.AUDIO,
FeatureType.SOUND_GENERATION: AIModelType.AUDIO,
FeatureType.VOICE_GENERATION: AIModelType.AUDIO,
FeatureType.SUBTITLE_GENERATION: AIModelType.AUDIO,
}
model_type = feature_to_model_type.get(feature_type)
if not model_type:
return None
# 尝试从缓存获取
cache_key = f"ai_model:pricing:{model_name or f'default_{model_type}'}"
cached = await cache.get(cache_key)
if cached:
import json
return json.loads(cached)
# 从数据库查询
if model_name:
model = await self.ai_model_repository.get_by_name(model_name)
else:
model = await self.ai_model_repository.get_default_model(model_type)
if not model or not model.is_active:
return None
pricing_info = {
'model_id': model.model_id,
'model_name': model.model_name,
'credits_per_unit': model.credits_per_unit,
'unit_type': model.unit_type,
'cost_per_unit': float(model.cost_per_unit)
}
# 缓存 1 小时
await cache.set(cache_key, pricing_info, ttl=3600)
return pricing_info
except Exception as e:
logger.error(f"从 AI Service 获取定价失败: {e}")
return None
3. 重构 calculate_credits 方法
async def calculate_credits(
self,
feature_type: int,
params: Dict[str, Any]
) -> int:
"""计算所需积分(优先使用 AI Service 定价,降级到 credit_pricing 表)"""
self._validate_feature_type(feature_type)
# 尝试从 AI Service 获取定价
model_name = params.get('model')
ai_pricing = await self._get_model_pricing_from_ai_service(feature_type, model_name)
if ai_pricing:
# 使用 AI Service 的定价
credits_per_unit = ai_pricing['credits_per_unit']
# 根据功能类型计算单位数
if feature_type == FeatureType.IMAGE_GENERATION:
return credits_per_unit
elif feature_type == FeatureType.VIDEO_GENERATION:
duration = params.get('duration', 5)
return credits_per_unit * duration
elif feature_type == FeatureType.TEXT_PROCESSING:
char_count = params.get('char_count', 0)
units = max(1, (char_count // 1000))
return credits_per_unit * units
# ... 其他类型
# 降级:使用 credit_pricing 表(原有逻辑)
pricing = await self.repository.get_pricing(feature_type)
if not pricing:
raise NotFoundError(f"未找到定价配置")
# 原有的复杂定价逻辑...
架构图
┌─────────────────┐
│ Credit Service │
└────────┬────────┘
│
├─────────────────────────────────┐
│ │
▼ ▼
┌────────────────────┐ ┌──────────────────┐
│ AI Model Repository│ │ Credit Repository│
│ (ai_models 表) │ │ (credit_pricing) │
└─────────┬──────────┘ └──────────────────┘
│ ▲
│ │
▼ │
┌──────────┐ │
│ Redis │ │
│ 缓存 │ │
└──────────┘ │
│ │
│ 不可用时 │
└──────────────────────────────┘
降级策略
优势
- 单一数据源: AI Service 的
ai_models表作为主要定价来源 - 性能优化: Redis 缓存减少数据库查询(缓存命中率预计 >90%)
- 高可用性: 降级策略保证 AI Service 不可用时仍可计算
- 易于维护: 只需维护
ai_models表,credit_pricing作为备份 - 向后兼容: 保留原有逻辑,平滑迁移
性能对比
| 场景 | 原方案 | 新方案 | 提升 |
|---|---|---|---|
| 首次查询 | 1 次 DB 查询 | 1 次 DB 查询 + 写缓存 | 持平 |
| 缓存命中 | 1 次 DB 查询 | 0 次 DB 查询 | 100% |
| AI Service 不可用 | N/A | 降级到 credit_pricing | 100% 可用性 |
风险与缓解
| 风险 | 影响 | 缓解措施 |
|---|---|---|
| AI Service 不可用 | 无法获取定价 | 降级到 credit_pricing 表 |
| Redis 不可用 | 性能下降 | 直接查询数据库 |
| 缓存数据过期 | 定价不准确 | TTL 设置为 1 小时,定期刷新 |
| 循环依赖 | 导入失败 | 使用延迟导入和 @property |
实施步骤
- ✅ 在
CreditService添加ai_model_repository属性 - ✅ 实现
_get_model_pricing_from_ai_service()方法 - ✅ 重构
calculate_credits()方法 - ✅ 添加 feature_type 到 model_type 的映射
- ⏳ 测试缓存机制
- ⏳ 测试降级策略
- ⏳ 性能测试和优化
测试策略
单元测试
async def test_calculate_credits_with_ai_service():
"""测试使用 AI Service 定价"""
# Mock AI Model Repository
# 调用 calculate_credits
# 验证使用了 ai_models 表的定价
async def test_calculate_credits_fallback():
"""测试降级到 credit_pricing 表"""
# Mock AI Service 不可用
# 调用 calculate_credits
# 验证使用了 credit_pricing 表的定价
async def test_pricing_cache():
"""测试 Redis 缓存"""
# 第一次查询(写缓存)
# 第二次查询(读缓存)
# 验证缓存命中
集成测试
async def test_end_to_end_pricing():
"""端到端测试定价计算"""
# 创建 AI 模型记录
# 调用 calculate_credits
# 验证返回正确的积分数
监控指标
- 缓存命中率: Redis 缓存的命中率
- 降级次数: 使用 credit_pricing 表的次数
- 查询耗时: 定价计算的平均耗时
- 错误率: 定价计算失败的比例
迁移计划
阶段 1:并行运行(1 周)
- 新旧逻辑同时运行
- 对比结果差异
- 记录日志分析
阶段 2:灰度发布(1 周)
- 10% 流量使用新逻辑
- 监控性能和错误率
- 逐步提升到 100%
阶段 3:完全切换(1 周)
- 100% 流量使用新逻辑
- 保留降级策略
- 持续监控
相关文档
变更日志
- 2026-01-27: 初始版本,实现动态定价集成