
企业级 Token 限流与计费实战从根本解决大模型账单爆炸的架构设计兄弟们说实话搞这些技术坑是真的多。咱们做技术的说白了就是要不断踩坑、不断成长。前言随着大模型应用的爆发式增长API 费用管理成为研发团队必须面对的现实问题。如果缺乏精细的监控与限流手段测试账号或死循环调用等异常行为极易引发预算烧穿。本文将立足架构设计从限流与计费两个维度阐述如何利用 Redis 令牌桶和拦截器实现 Token 级别的限流与异步精准计费方案。一、底层原理1.1 核心机制大模型计费的核心单位是 Token。一个汉字大概 1.5 到 2 个 Token。一个英文单词大概 1.3 个 Token。我们要做的就是在请求进来时先数一数“水”有多少再决定放不放行。监控指标体系主要盯住三件事Token 消耗量输入多少输出多少。QPS/TPS每秒请求数每秒 Token 处理数。延迟与错误率接口响应慢不慢有没有报错。下面这张图展示了流量如何经过我们的“计费闸机”。graph TD A[用户请求] -- B{限流网关} B -- 超过阈值 -- C[拒绝服务 429] B -- 通过 -- D[拦截器计数] D -- E[调用大模型 API] E -- F[返回结果] F -- G[记录 Token 用量] G -- H[Redis 累加计费] H -- I[异步写入数据库] subgraph 监控面板 J[实时 Token 消耗] K[用户账单预览] end G -.- J H -.- K设计优势很明显。流量在网关层就被卡住不会打到后端。计费数据实时写入 Redis查询快不阻塞主流程。异步落库保证高性能。1.2 与同类方案的对比很多团队一开始是用 Nginx 做限流。但这不够。Nginx 只能限请求次数不知道请求里包含了多少 Token。有些团队在业务代码里写计数。这样耦合太重代码里全是 if-else维护起来像还债。我们采用拦截器 Redis 的方案。方案限流精度计费能力性能损耗推荐指数Nginx 限流请求级无低⭐⭐业务代码计数请求级手动计算高⭐拦截器 RedisToken 级自动实时中⭐⭐⭐⭐⭐二、快速上手别被架构吓到。核心逻辑其实就三行。我们用一个简单的 Java Filter 来演示。假设你已经有了 Redis 连接。// 定义一个过滤器拦截所有 /api/chat 开头的请求 WebFilter(urlPatterns /api/chat) public class TokenRateLimitFilter implements Filter { // 注入 Redis 模板用来计数 private RedisTemplateString, Object redisTemplate; // 定义每个用户每分钟的 Token 配额比如 1000 个 private static final long TOKEN_LIMIT_PER_MINUTE 1000L; Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest (HttpServletRequest) request; HttpServletResponse httpResponse (HttpServletResponse) response; // 1. 获取用户标识比如从 Header 里拿 String userId httpRequest.getHeader(X-User-ID); if (userId null) { httpResponse.sendError(401, 未授权); return; } // 2. 估算本次请求的输入 Token 数 (简单按字符数估算生产环境建议用 tokenizer) String prompt httpRequest.getParameter(prompt); long inputTokens estimateTokenCount(prompt); // 3. 检查 Redis 中的当前用量 String key token_usage: userId : getCurrentMinute(); Long currentUsage redisTemplate.opsForValue().increment(key, inputTokens); // 4. 如果超过配额直接拒绝 if (currentUsage TOKEN_LIMIT_PER_MINUTE) { httpResponse.sendError(429, Token 配额已耗尽请稍后再试); // 注意这里要回滚 Redis 的计数因为请求没成功 redisTemplate.opsForValue().decrement(key, inputTokens); return; } // 5. 设置过期时间防止键永不过期 redisTemplate.expire(key, 60, TimeUnit.SECONDS); // 6. 放行请求 chain.doFilter(request, response); } // 简单的估算方法实际请用 tiktoken 等库 private long estimateTokenCount(String text) { if (text null) return 0; // 中文大概 1.5 个 token 一个字英文 1.3 个 return (long) (text.length() * 1.5); } // 获取当前分钟数作为 Key 的一部分实现每分钟重置 private String getCurrentMinute() { return LocalDateTime.now().format(DateTimeFormatter.ofPattern(yyyyMMddHHmm)); } }三分钟就能跑起来。先拦截再计数最后判断。这就是最基础的“水表”。三、核心 API / 深水区生产环境不能只靠估算。我们需要更精准的 API 和更复杂的配置。3.1 核心方法速查我们在内部封装了一个BillingService。方法名功能描述输入参数返回值recordRequest记录请求开始userId, promptrequestIdrecordResponse记录响应结束requestId, completioncostgetUserBalance查询用户余额userIddoublefreezeAccount冻结异常账号userId, reasonboolean3.2 生产级配置光有代码不行配置得跟上。异常处理必须做。Redis 挂了怎么办我们要设置熔断。Service public class BillingService { Autowired private RedisTemplateString, Object redisTemplate; // 定义熔断阈值Redis 错误次数超过 5 次就降级 private final AtomicLong redisErrorCount new AtomicLong(0); private static final int CIRCUIT_BREAKER_THRESHOLD 5; public void recordUsage(String userId, long tokens) { // 如果熔断器开启直接跳过计费记录日志告警 if (redisErrorCount.get() CIRCUIT_BREAKER_THRESHOLD) { log.warn(Redis 计费服务熔断跳过 Token 记录用户: {}, userId); return; } try { String key usage: userId; // 使用 Redis Pipeline 批量操作减少网络开销 redisTemplate.executePipelined((RedisCallbackObject) connection - { connection.increment(key.getBytes(), tokens); return null; }); } catch (Exception e) { log.error(计费记录失败, e); redisErrorCount.incrementAndGet(); // 触发告警 alertService.sendAlert(计费服务异常); } } }超时控制也很关键。调用大模型 API 本身就很慢。如果计费逻辑卡住了整个接口就卡住了。所以计费必须是异步的或者至少是非阻塞的。3.3 高级定制有些用户是 VIP配额要高。有些接口是内部测试不要钱。我们需要一个动态规则引擎。// 伪代码动态规则匹配 public QuotaRule getRule(String userId, String modelId) { // 1. 先查缓存 QuotaRule rule cache.get(userId); if (rule ! null) return rule; // 2. 查数据库 rule db.findRuleByUser(userId); // 3. 默认规则兜底 if (rule null) { rule defaultRule; } // 4. 缓存起来 cache.put(userId, rule); return rule; }这样运营人员改个配置不用重启服务配额就变了。四、实战演练我们来模拟一个真实场景。公司有个“智能客服”系统接入了大模型。老板要求每个部门每月 Token 预算 10 万。超过预算的部门自动降级为小模型。这是完整代码。RestController RequestMapping(/api/v1/chat) public class ChatController { Autowired private LlmClient llmClient; Autowired private BillingService billingService; Autowired private QuotaService quotaService; PostMapping(/complete) public ResponseEntityMapString, Object complete(RequestBody ChatRequest request) { String userId SecurityContextHolder.getUserId(); // 1. 检查配额状态 QuotaStatus status quotaService.checkStatus(userId); MapString, Object result new HashMap(); try { // 2. 如果配额耗尽强制切换模型或拒绝 if (status.isExhausted()) { result.put(error, 部门预算已用完已切换至基础模型); request.setModel(basic-model-v1); // 降级策略 } // 3. 预扣费乐观锁 long estimatedTokens tokenizer.count(request.getPrompt()); boolean locked quotaService.lockQuota(userId, estimatedTokens); if (!locked) { throw new BusinessException(配额不足操作失败); } // 4. 调用大模型 String response llmClient.generate(request); long actualTokens tokenizer.count(response); // 5. 结算实际费用 billingService.settle(userId, estimatedTokens, actualTokens); result.put(data, response); result.put(usage, actualTokens); } catch (Exception e) { // 6. 异常回滚配额 quotaService.rollbackQuota(userId, estimatedTokens); result.put(error, e.getMessage()); } return ResponseEntity.ok(result); } }运行结果分析。正常请求时返回 200包含实际 Token 数。预算耗尽时返回提示且自动切换模型。异常发生时配额自动回滚不会扣冤枉钱。五、避坑指南与最佳实践这几年踩过的坑都给你们填上了。技巧Token 统计一定要用官方库。别自己数字符。不同模型对 Token 的切分不一样。用tiktoken或者模型厂商提供的 SDK 去算。⚠️警告Redis 单点故障。计费是核心链路。Redis 挂了服务不能挂。一定要做本地缓存兜底或者异步队列缓冲。✅推荐定期核对账单。每天凌晨跑一个对账任务。对比 API 厂商的账单和我们记录的账单。误差超过 1% 就报警。有个坑是“流式响应”。SSE 流式输出时Token 是分批回来的。你不能等全部流完再计费。要在每个数据块回来时累加 Token 数。六、综合实战演示最后给一套精简的闭环代码。这是一个 Spring Boot 的 Starter 组件思路。// 核心拦截器实现 Component Order(1) // 确保最先执行 public class LlmBillingInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 排除静态资源 if (request.getRequestURI().contains(.css) || request.getRequestURI().contains(.js)) { return true; } String userId request.getHeader(X-User-ID); if (StringUtils.isEmpty(userId)) { response.sendError(400, 缺少用户 ID); return false; } // 请求开始时间记录 request.setAttribute(startTime, System.currentTimeMillis()); // 这里可以做一个轻量级的配额预检查 // 避免无效请求浪费资源 if (!quotaService.hasMinimumQuota(userId)) { response.sendError(402, 余额不足); return false; } return true; } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 无论成功失败都要记录日志和最终计费 long startTime (Long) request.getAttribute(startTime); long costTime System.currentTimeMillis() - startTime; // 异步上报监控数据 monitoringService.recordLatency(costTime); if (ex ! null) { monitoringService.recordError(ex.getClass().getSimpleName()); } } }这套代码配合前面的 Redis 逻辑就是一个完整的企业级方案。它能扛住高并发能算清楚账还能防住恶意调用。七、总结大模型服务监控本质是管钱。Token 限流本质是管资源。不要等账单来了再哭。把监控和限流做到代码的第一行。实时计费异步落库熔断兜底。这就是企业级 AI 网关的标配。技术是为业务服务的。别让技术债变成财务债。