容量、并发、缓存与 FinOps
正在读取学习记录…
资料助手每秒收到10个问题,每次模型调用平均占用2秒。容量表示系统在约定质量和响应时间下能承担多少业务;并发表示某一时刻正在处理多少请求;缓存保存可复用结果,让部分请求直接取得答案。FinOps是把资源费用与业务使用关联起来,让技术、财务和业务一起决定投入与收益。
先看正常输入:缓存命中率50%,10次/秒中有5次需要模型,每次占用2秒,平均有10次模型调用在进行。模型入口提供12个并发槽位,这组平均输入可装入;如果缓存完全不命中,平均在途增到20,就超过12。
把请求率、耗时和成本放在同一张纸上
λ 表示每秒需要模型的请求数,W 表示平均占用秒数,L 表示平均在途调用数。在稳定、统计窗口一致的条件下,用 Little 定律 L = λ × W 估算平均并发。
就地代入:总请求率10次/秒,命中率0.50,模型率 λ = 10 × (1 − 0.50) = 5次/秒;L = 5 × 2 = 10。若结果为10.2,槽位数向上取整为11。平均值用于初步规划,突发流量和尾部耗时仍需预留空间并压测。
图按这组固定输入绘制:12个容量格中10格被平均在途占用;无缓存时需要20格,其中8格超出限制。颜色表达计算关系,数字来自输入公式。
| 命中率 | 模型请求率 | 2秒平均耗时下在途数 | 1000请求的假设费用 |
|---|---|---|---|
| 0% | 10次/秒 | 20 | 10.00元 |
| 50% | 5次/秒 | 10 | 5.00元 |
| 100% | 0次/秒 | 0 | 0.00元 |
费用列假设每次未命中调用0.01元:1000 × (1 − 0.50) × 0.01 = 5元。真实模型按输入、输出、缓存等项目计价时,应改成各项数量乘对应单价;存储、服务器和日志费用再分别加入。
缓存命中之前先完成授权
缓存键是查找结果的标识。只用问题文本做键,会让“报销上限”在不同租户间共用答案。示例键包括租户、文档版本、模型、提示词版本和问题。权限改变后应失效缓存或加入权限版本;查缓存之前仍要核验当前身份和资源访问权限。
Google SRE 的过载处理讨论了在容量不足时控制进入系统的工作量。缓存之外还需要并发限制、有界队列、超时、降级和公平分配。否则一次模型拥堵可能通过无限重试扩散到整个服务。
建立新文件夹 l8-capacity,保存 capacity_plan.py 和 test_capacity_plan.py。程序用十进制数计算费用,用向上取整计算槽位,不进行真实模型调用或计时。
先在 PowerShell 创建并进入目录,再保存下方两份文件:
New-Item -ItemType Directory -Force l8-capacity
cd l8-capacitymacOS 终端:
mkdir -p l8-capacity
cd l8-capacity"""用固定业务输入计算并发与费用;没有计时或真实模型调用。"""
from decimal import Decimal
from math import ceil
HIT_RATE = Decimal("0.50")
def plan(rate, latency, hit_rate, slot_limit):
if rate < 0 or latency < 0 or not Decimal("0") <= hit_rate <= Decimal("1"):
raise ValueError("速率、耗时非负,命中率在0到1之间")
if slot_limit <= 0:
raise ValueError("并发上限必须为正")
# 仅未命中流量消耗模型并发,平均在途数为速率乘耗时。
model_rate = rate * (1 - hit_rate)
in_flight = model_rate * latency
slots = ceil(in_flight)
return model_rate, in_flight, slots, slots <= slot_limit
def cost(requests, hit_rate, call_price):
# 单价是教学假设,每次未命中固定一次调用。
return Decimal(requests) * (1 - hit_rate) * call_price
def cache_key(tenant, document_version, model, prompt_version, question):
# 租户与版本参与键,授权仍须在查缓存之前完成。
return tenant, document_version, model, prompt_version, question
def main():
rate, in_flight, slots, fits = plan(Decimal("10"), Decimal("2"), HIT_RATE, 12)
print(f"模型请求: {rate:.2f}次/秒")
print(f"平均在途: {in_flight:.2f} / 向上取整{slots}个并发")
print(f"12个槽位: {'容纳' if fits else '不足'}")
print(f"1000次请求假设费用: {cost(1000, HIT_RATE, Decimal('0.01')):.2f}元")
print("租户隔离:",
cache_key("A", "v1", "m1", "p1", "报销") !=
cache_key("B", "v1", "m1", "p1", "报销"))
if __name__ == "__main__":
main()测试文件 test_capacity_plan.py:
"""覆盖全命中、无命中、容量边界、版本隔离与费用计算。"""
from decimal import Decimal as D
import unittest
from capacity_plan import plan, cost, cache_key
class CapacityTests(unittest.TestCase):
def test_baseline(self):
self.assertEqual(plan(D(10), D(2), D("0.5"), 12), (D(5), D(10), 10, True))
def test_zero_and_full_hit(self):
self.assertEqual(plan(D(10), D(2), D(0), 12)[2:], (20, False))
self.assertEqual(plan(D(10), D(2), D(1), 12)[2:], (0, True))
def test_capacity_boundary(self):
self.assertTrue(plan(D(6), D(2), D(0), 12)[3])
self.assertFalse(plan(D("6.1"), D(2), D(0), 12)[3])
def test_invalid_inputs(self):
for hit in (D("-0.1"), D("1.1")):
with self.assertRaises(ValueError):
plan(D(10), D(2), hit, 12)
with self.assertRaises(ValueError):
plan(D(10), D(2), D(0), 0)
def test_cost_and_version_key(self):
self.assertEqual(cost(1000, D("0.5"), D("0.01")), D("5"))
self.assertNotEqual(cache_key("A", "v1", "m1", "p1", "q"),
cache_key("A", "v2", "m1", "p1", "q"))
if __name__ == "__main__":
unittest.main()文件保存后,在同一窗口核对文件名并运行:
ls
python -X utf8 capacity_plan.py
python -X utf8 -m unittest -v test_capacity_plan.pydir应显示这两个文件。运行程序后得到下面输出;测试末尾应显示 OK。
程序输出:
模型请求: 5.00次/秒
平均在途: 10.00 / 向上取整10个并发
12个槽位: 容纳
1000次请求假设费用: 5.00元
租户隔离: True将缓存命中率改成0
在 capacity_plan.py 找到 HIT_RATE = Decimal("0.50"),把这一行改成 HIT_RATE = Decimal("0.00")。保存并运行:
python -X utf8 capacity_plan.py模型请求变成10.00次/秒,平均在途20.00,需要20个槽位,12个槽位显示“不足”;1000次请求的假设费用变成10.00元。租户隔离仍为True。恢复 HIT_RATE = Decimal("0.50"),再次运行回到10个并发与5元。
这也说明缓存失效会同时改变容量和成本。部署新文档版本时,大量旧结果可能不能复用;应把低命中率作为发布前的规划场景。
从平均计算走向运营选择
并发限制要覆盖调用全生命周期,包括流式响应结束之前。请求率限制控制单位时间内进入多少工作,并发限制控制多少工作同时占据资源,二者不能互相替代。队列可以吸收短突发,队列长度与等待期限也要限制。
按租户记录请求量、缓存命中、模型令牌与总费用,再计算每个成功任务成本。一个失败后重试三次的任务可能比一次成功任务花费更多,所以归因应关联同一个任务ID。设置预算前,先预留可能费用,完成后对账;预算用尽时拒绝新调用或进入明确的低成本模式。
| 观察到的业务变化 | 优先检查 | 可尝试的动作 |
|---|---|---|
| 请求率不变,平均耗时从2秒升到4秒 | 模型耗时与在途数 | 将计划并发从10重新算到20 |
| 缓存命中骤降 | 文档、权限、提示版本变化 | 提前预热可公开且有权限的数据 |
| 队列等待越来越长 | 进入速率与处理速率 | 限流并缩短过期请求的排队 |
| 费用涨而成功任务数不涨 | 重试、无效生成与缓存键 | 减少重复调用,按任务统计 |
12个槽位容纳平均10个调用只是初步规划结论。压测需要测进入速率、成功率、延迟分位数、队列和资源占用,选择能达到服务目标的工作点。下一篇将这些目标写成可计算的SLO。
课后练习展开习题收起习题
正在读取本地记录…
不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。
选择题 · 先判断边界
关于“容量、并发、缓存与 FinOps”,哪种做法更稳妥?
对应知识点:L8-capacity
简答题 · 解释失败
写出一个失败输入并说明记录字段。
对应知识点:L8-capacity
代码题 · 边界测试
补充正常、边界、失败三组输入和预期结果。
对应知识点:L8-capacity
场景题 · 结果未知
证据不足时如何设计状态和人工接管?
对应知识点:L8-capacity
继续阅读SLO、SLA与错误预算。
描述会发送给本站维护者,用于修订文章,保存90天。请只填写文章问题,省略个人信息、密钥和内部资料。