首页 文章 精选 留言 我的

精选列表

搜索[工程化],共3385篇文章
优秀的个人博客,低调大师

缓存三大经典问题:穿透、击穿、雪崩的工程化防护

## 三种故障,三种根因,别混着防 缓存体系的三类经典故障经常被混为一谈,但根因完全不同:**穿透**是「查不存在的数据」(请求绕过缓存直打数据库);**击穿**是「热点 key 恰好过期」(瞬间高并发全打到 DB);**雪崩**是「大批 key 同时失效或缓存集群宕机」(DB 被洪峰冲垮)。防护矩阵必须分开设计,一套方案包打天下必然顾此失彼。一、穿透:把「不存在」也缓存住 1. **空值缓存**:DB 查不到时,在缓存写一个短 TTL(30~60 秒)的空标记,同 key 的后续请求被缓存挡住——简单有效,成本是少量无效 key 占用; 1. **布隆过滤器**:在缓存前加一层布隆过滤器,把全量合法 key 预热进去,「过滤器说不存在」的一定不存在,直接拒绝。适合 key 集合相对固定(商品 ID、用户 ID)的场景;注意布隆过滤器有误判率(说存在的可能不存在),且删除困难,重建要预设计划; 1. **请求侧风控**:穿透往往来自恶意扫描(遍历 ID 段),网关层对单 IP/单用户的高频 miss 限流,比在缓存层硬扛更划算。 ## 二、击穿:热点 key 的「永不过期」策略 1. **逻辑过期**:热点 key 物理上不设 TTL,value 里带逻辑过期时间;发现逻辑过期后由一个线程去重建,其他线程继续用旧值——牺牲一点实时性,换来零击穿; 1. **互斥重建**:过期后只放一个请求去 DB 加载(Redis SETNX 锁 + 短等待),其余请求自旋等待或返回旧值。实现简单,但要防锁未释放的死等; 1. **热点探测**:通过访问统计识别 Top 热点 key,提前预热并延长 TTL;大促前对已知热点(爆款商品)做人工预热是标准动作。 ![](https://developer.qcloudimg.com/http-save/yehe-1436064/ab2c885f9126581bf901dea50f5cf05e.jpg) ## 三、雪崩:打散失效时间 + 双层兜底 1. **TTL 加随机抖动**:批量写入的 key 统一 TTL 加 0~300 秒随机偏移,避免同一时刻集中失效——一行代码消灭最常见的雪崩源; 1. **多级缓存**:本地缓存(Caffeine)+ 分布式缓存(Redis)双层,Redis 抖动时本地层兜住读流量; 1. **DB 侧限流兜底**:真到缓存全挂的时刻,DB 前的连接池上限 + 限流就是最后防线——宁可部分请求失败,不能让 DB 被打挂导致全站不可用; 1. **集群高可用**:Redis 主从 + 哨兵/集群模式,故障自动切换;切换期间的部分失败要有降级预案(读旧数据/静态兜底页)。 ![](https://developer.qcloudimg.com/http-save/yehe-1436064/901adc5e27f09b1507660de5016bd3e2.jpg) ## 四、验收:用故障演练验证 防护写完要演练:模拟空值批量请求(验穿透防护)、删除热点 key(验击穿防护)、重启 Redis(验雪崩兜底与恢复时间)。没有演练过的防护,和没有一样。 缓存防护的本质是「为失败设计」:默认假设缓存会 miss、会过期、会宕机,然后为每一种失败准备好提前量。三张防护矩阵表贴在监控面板旁,比事后复盘有用得多。 ![](https://developer.qcloudimg.com/http-save/yehe-1436064/3eee0e23c7245a150f4eb4121aeed7ed.jpg)

优秀的个人博客,低调大师

AI 产品创造:从需求洞察到付费闭环的工程化路径

AI 产品创造的最大误区,是先造技术再找场景。专业做法恰恰相反:先锁定一个高频、可量化、愿付费的痛点,再用最小技术栈把"输入→生成→交付→计费"跑通。本文以一个垂直场景——跨境电商 listing 生成器——为例,给出从架构到代码的完整落地路径。 ## 一、产品定位:三维度筛选 不是所有场景都适合做 AI 产品。用三个维度筛选:**高频**(用户每天或每周都用)、**可量化**(结果好坏能判断)、**愿付费**(节省的时间或成本可折算成钱)。跨境电商卖家写一条 listing 平均耗时 40 分钟,外包成本 50-200 元,这就是典型的可付费场景。 技术栈选择最小化:Next.js 部署 Vercel,模型调 OpenAI,缓存用 Upstash Redis,计费用 Stripe。一套栈覆盖前后端与商业化,一个人两周可上线。 ## 二、核心架构:四层分离 ``` 用户输入 → API 网关(鉴权/限流) → 生成服务(模型+缓存) → 用量计费 → 交付 ``` 关键设计原则:**模型调用必须可缓存、可限流、可计量**。下面用 Next.js App Router 实现核心生成接口。 ``` // app/api/generate/route.ts import { NextRequest, NextResponse } from "next/server"; import OpenAI from "openai"; import { Redis } from "@upstash/redis"; import { z } from "zod"; const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); const redis = Redis.fromEnv(); const Input = z.object({ product: z.string().min(2).max(500), tone: z.enum(["professional", "casual", "luxury"]).default("professional"), }); const FREE_QUOTA = 10; // 每月免费次数 export async function POST(req: NextRequest) { const userId = req.headers.get("x-user-id"); if (!userId) return NextResponse.json({ error: "未登录" }, { status: 401 }); const parsed = Input.safeParse(await req.json()); if (!parsed.success) { return NextResponse.json({ error: "参数非法" }, { status: 400 }); } const { product, tone } = parsed.data; // 1. 用量检查 const monthKey = `quota:${userId}:${new Date().toISOString().slice(0, 7)}`; const used = (await redis.get(monthKey)) ?? 0; if (used >= FREE_QUOTA) { return NextResponse.json( { error: "免费额度已用完", paywall: true }, { status: 402 }); } // 2. 缓存命中直接返回,节省成本 const cacheKey = `gen:${tone}:${Buffer.from(product).toString("base64")}`; const cached = await redis.get(cacheKey); if (cached) { return NextResponse.json({ listing: cached, source: "cache" }); } // 3. 调用模型,温度与 max_tokens 严格控制成本 const resp = await openai.chat.completions.create({ model: "gpt-4o-mini", messages: [ { role: "system", content: `你是跨境电商文案专家。语气:${tone}。` + "输出格式:标题一行,五点描述,每点不超过30词。" }, { role: "user", content: product }, ], temperature: 0.7, max_tokens: 400, }); const listing = resp.choices[0].message.content ?? ""; // 4. 写入缓存与用量 await redis.setex(cacheKey, 86400, listing); await redis.incr(monthKey); await redis.expire(monthKey, 60 * 60 * 24 * 32); return NextResponse.json({ listing, source: "llm", remaining: FREE_QUOTA - used - 1, }); } ``` 这段代码体现了 AI 产品的四个工程要点:**Schema 校验输入**(防止滥用)、**额度前置检查**(控制成本)、**缓存优先**(降低延迟与费用)、**用量原子累加**(为计费提供依据)。 ## 三、计费闭环:Stripe 一次性接入 免费额度用完必须能立刻付费,转化窗口只有几秒。用 Stripe Checkout 是最快路径。 ``` // app/api/checkout/route.ts import { NextRequest, NextResponse } from "next/server"; import Stripe from "stripe"; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); export async function POST(req: NextRequest) { const { userId } = await req.json(); const session = await stripe.checkout.sessions.create({ mode: "payment", line_items: [{ price: process.env.STRIPE_PRICE_ID!, quantity: 1 }], success_url: `${process.env.APP_URL}/success?uid=${userId}`, cancel_url: `${process.env.APP_URL}/pricing`, metadata: { userId }, }); return NextResponse.json({ url: session.url }); } ``` 支付成功后通过 Webhook 提升额度,而不是在主流程里轮询: ``` // app/api/webhook/route.ts import { NextRequest, NextResponse } from "next/server"; import Stripe from "stripe"; import { Redis } from "@upstash/redis"; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); const redis = Redis.fromEnv(); export async function POST(req: NextRequest) { const sig = req.headers.get("stripe-signature")!; const body = await req.text(); const event = stripe.webhooks.constructEvent( body, sig, process.env.STRIPE_WEBHOOK_SECRET!); if (event.type === "checkout.session.completed") { const userId = (event.data.object as any).metadata.userId; const monthKey = `quota:${userId}:${new Date().toISOString().slice(0, 7)}`; await redis.set(monthKey, -100); // 负数表示已付费,额度充足 } return NextResponse.json({ received: true }); } ``` 幂等性由 Stripe 事件 ID 保证,重复回调不会重复加额度。 ## 四、体验优化:流式输出与错误兜底 用户等待超过 3 秒就会流失。用流式输出把首字延迟压到 0.5 秒内: ``` export async function POST(req: NextRequest) { const stream = await openai.chat.completions.create({ model: "gpt-4o-mini", messages: [{ role: "user", content: product }], stream: true, }); const encoder = new TextEncoder(); const readable = new ReadableStream({ async start(controller) { for await (const chunk of stream) { const text = chunk.choices[0]?.delta?.content ?? ""; if (text) controller.enqueue(encoder.encode(text)); } controller.close(); }, }); return new Response(readable, { headers: { "Content-Type": "text/plain; charset=utf-8" }, }); } ``` 同时必须设置降级:模型超时或限流时返回缓存或规则文案,绝不让用户看到 500。 ## 五、成本与迭代纪律 **成本红线**:单次生成成本控制在 1 分钱内。小模型 + `max_tokens` 限制 + 缓存命中率 40%,足以支撑免费额度。 **迭代节奏**:第一周做 MVP 并部署,第二周找 10 个真实用户付费验证。先验证付费意愿,再优化体验。 **合规最小集**:隐私政策、数据保留周期、AI 输出免责声明,缺一不可。 总结:AI 产品创造的胜负手不在模型,而在场景选择的精准、工程实现的克制、付费闭环的顺畅。代码不到两百行,却包含校验、限流、缓存、计费、流式的完整链路。先用最小架构赚到第一块钱,再谈规模化。

优秀的个人博客,低调大师

从能跑到能观察——AI 服务的工程化分层与日志

前几篇我们把 Agent 包成了 FastAPI 接口(第 4 篇),加了字段校验、接了错误处理。功能都齐了。但有两个问题一直卡着我: **一是看不清。** 有一次模型调用失败,前端只返回一句"模型调用失败",我盯着屏幕却不知道:请求到底进没进来?Service 执行到哪一步?具体炸在哪一行?服务能跑,但看不清它怎么跑。 **二是改不动。** 想给分析加个缓存,发现 Agent 调用、错误处理、日志全挤在一块,只能往 `routers.py` 里硬塞。能跑,但不等于好维护。 所以这一篇同时解决这两件事:把项目分层,再加日志。 > **能跑只是 Demo,能分层、能观察,才开始像可维护的服务。** --- ## 一、项目现在的分层结构 分层之后,目录是这样: ``` app/ ├── main.py 应用入口 ├── routers.py API 接口层 ├── schemas.py 请求和响应模型 └── services.py 业务服务层 resume_analyzer/ ├── agent.py PydanticAI Agent 调用 ├── models.py Agent 输出结构 └── tools.py Agent 工具函数 ``` `app/` 管 Web 服务和接口,`resume_analyzer/` 管 AI 能力本身。两条线分开,后面换模型、加工具都不动接口层。 --- ## 二、每一层分别管什么 ### 1. `main.py` —— 启动器 不碰业务。只做:创建 FastAPI app、挂载 router、配全局日志、提供 health check。 ### 2. `routers.py` —— 接口层 只关心 HTTP:地址是什么、请求/响应模型是什么、异常返回什么状态码。不该知道 Agent 内部细节。 ### 3. `schemas.py` —— 契约层 放 `AnalyzeRequest` / `AnalyzeResponse`。它告诉调用方:该传什么、会返回什么。前端对接就靠它。 ### 4. `services.py` —— 业务层 负责业务编排:调 Agent、记日志,以后还能加缓存、重试、数据库、权限。 ### 5. `agent.py` —— AI 能力层 只管 PydanticAI:定义 Agent、调模型、回结构化结果。 **一句话类比**:`main` 是门面,`routers` 是前台,`schemas` 是表单,`services` 是后台办事员,`agent` 是里面真正干活的专家。各司其职,谁也别越界。 --- ## 三、最该理解的一层:services.py 它可能现在只是这样: ``` def analyze_resume_service(resume_text: str) -> ResumeAnalysis: return analyze_resume(resume_text) ``` 看着只是"包了一层",没干啥。但它的价值不在今天,在以后: ``` 日志 → 放这 缓存 → 放这 重试 → 放这 数据库保存 → 放这 权限判断 → 放这 调用耗时 → 放这 ``` 它的存在,让 `routers.py` 永远只说"收到请求、返回结果",不用面对复杂业务。 --- ## 四、为什么需要日志 解决了分层,还得能看清服务内部。AI 服务跑起来后,出问题的地方比普通接口多: - API Key 没配置 - 模型调用失败 - 网络超时 - Agent 输出异常 - 用户输入异常 没有日志时,用户只看到: ``` { "detail": { "code": "MODEL_CALL_FAILED", "message": "模型调用失败" } } ``` 这对用户友好,对开发者不够。开发者还需要知道:哪一次请求失败了、失败前执行到了哪、具体异常堆栈是什么。日志就是干这个的。 > **API 返回给用户看,日志留给开发者看。** 两者面对的人不同,该说的内容也不同。 --- ## 五、main 定规则,services 记业务 ### 1. `app/main.py` —— 定规则,不写日志 `main.py` 是入口,在这里配置全局日志格式: ``` import logging logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s - %(message)s", ) ``` 它规定的格式是:**时间 等级 模块名 - 内容**。比如一次真实运行: ``` 2026-08-14 10:20:01 INFO app.services - 开始分析简历,文本长度=61 ``` ### 2. `app/services.py` —— 记业务,才真正写日志 业务层拿到 logger 后,按执行过程记录: ``` import logging logger = logging.getLogger(__name__) ``` ``` logger.info("开始分析简历,文本长度=%s", len(resume_text)) # ... 调用 Agent ... logger.info("简历分析成功") ``` 失败处: ``` logger.exception("简历分析失败") ``` 这里有个容易混的点。`main.py` 的 `basicConfig(...)` **不是在写日志,是在定规则**——它告诉整个项目显示什么等级、长什么样、带不带模块名。而 `services.py` 的 `logger.info(...)` 才记录具体的事。 ``` main.py 配置日志规则(全局一次) services.py 记录业务日志(每次执行) ``` 这是 Python 服务端最常见的组织方式:规则集中定,业务分散记。 --- ## 六、别再用 print() 调试 `print()` 是临时调试,能看但不适合长期维护: ``` print("开始分析") ``` `logging` 是正式记录,优势是结构化的: ``` logger.info("开始分析简历,文本长度=%s", len(resume_text)) ``` 它有四样 `print` 没有的东西: - 日志等级(INFO/WARNING/ERROR) - 时间戳 - 模块名(知道谁打的) - 能记异常堆栈,以后可落文件或接日志平台 服务端项目里,优先用 `logging`。 --- ## 七、logger.exception() 是排查利器 这个方法必须放在 `except` 里: ``` try: result = analyze_resume(resume_text) except Exception: logger.exception("简历分析失败") raise ``` 它的特别之处:不仅记错误信息,还**自动带上 traceback**。逻辑是——先把详细错误写进日志,再把异常继续抛给上层。`routers.py` 再把它转成结构化的 API 错误。 一句记住:**`logger.exception**`** = 记错误 + 记堆栈 + 重新抛出。** --- ## 八、错误响应和日志的分工 今天最重要的理解就一句: ``` 错误响应:给用户 / 前端看 日志记录:给开发者看 ``` 模型调用失败时,前端看到的是干净的: ``` { "detail": { "code": "MODEL_CALL_FAILED", "message": "模型调用失败", "hint": "请检查 API Key、模型名称、网络连接或上游模型服务状态" } } ``` 开发者在终端看到的是原始的: ``` ERROR app.services - 简历分析失败 Traceback (most recent call last): ... ``` 这就是分工的价值:用户体验不破,排查线索不丢。 --- ## 九、今天的核心收获 > **一个 AI 服务不能只会调模型,还要有清晰的分层、稳定的错误边界、能观察运行过程的日志。** 回顾这 5 篇走过的路,项目已经具备: ``` 结构化输出 (1) output_type 字段校验 (2) field_validator 工具调用 (2) Tool Schema/Evidence (3) API 封装 (4) FastAPI 错误处理 (4) HTTPException 工程分层 (5) services.py 日志记录 (5) logging ``` 这已经是一个小型 AI 后端项目的雏形。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册