news 2026/10/28 15:26:33

T3 Stack 全栈实践:用 tRPC 打造类型安全代码片段管理工具

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
T3 Stack 全栈实践:用 tRPC 打造类型安全代码片段管理工具

前阵子给自己定了个小目标:不搞花活,用一套现代又克制的方案,把一直想做的代码片段管理工具真正落地。项目代号就叫t3code,技术底座选了社区里讨论度很高的T3 Stack,也就是 Next.js、TypeScript、Tailwind CSS、tRPC 这几件套,再加上 Prisma 和 NextAuth.js 完成数据层与登录鉴权。折腾完一个完整版本之后,我把选型逻辑、核心实现、部署过程以及踩过的坑全部复盘一遍,给正在研究全栈类型安全,或者想找一个可复现参考项目的人做份详细的实践记录。

t3code 本身解决的是很实际的问题:每个人的代码片段散落在本地文件、聊天记录、临时笔记里,真要用的时候永远找不到。把它做成一个支持标签分类、语言筛选、公开/私密切换、复制即用的在线工具,既能满足日常自用,也能作为学习 T3 Stack 的最佳练手项目。如果你对 tRPC 这种免写 REST API 的开发方式好奇,或者想在 Next.js 项目里把前后端类型彻底打通,这篇文章的示例代码和踩坑点基本都是可以直接照着改的。

1. 项目概述与定位

1.1 t3code 要解决什么问题

很多开发者都会遇到一个尴尬的场景:昨天刚调试通的加密逻辑,今天就忘了放在哪个目录;同事分享的一段 SQL 优化写法,翻半天聊天记录只找到半个;项目里积累的公共函数越来越散,最后只能靠搜索引擎重新找答案。t3code 最初就是冲着这个痛点去的,做一个自托管的代码片段管理工具,让片段可以集中管理、随时检索、一键复制。

功能层面,我给它规划的是最小可用集:用户注册登录后可以创建片段,片段包含标题、语言类型、正文、标签、可见性这几个核心字段;首页支持按语言筛选和关键词搜索;公开片段可以被其他登录用户收藏,收藏之后在个人中心快速查看。这个范围刚好覆盖 CRUD、搜索、鉴权、社交化收藏这几个全栈项目最常见的模块,不至于太小没代表性,也不会大到一个人做不完。

和 Gist、CodePen 这类在线服务相比,t3code 更强调 "自己能完全掌控" 这件事。它跑在自己的服务器或 Vercel 上,数据在自有数据库里,字段规则、权限逻辑、展示风格都由自己说了算。对于想深入学习 Next.js App Router 和 tRPC 的开发者来说,这个项目比照着文档敲 demo 有价值得多,因为每一步都会遇到真实业务带来的边界情况。

1.2 为什么选择 T3 Stack 作为底座

T3 Stack 这个名字来自 Theo 常用的 ID,社区里也喜欢把它解释成 TypeScript、Tailwind、tRPC 这三个 "T" 开头的组合。它强调的不是"必须用这几种技术",而是"用最简单直接的方式把类型安全贯穿到底"。选它作为 t3code 的底座,主要是因为三层考量。

第一,类型安全闭环。传统前后端分离项目里,接口请求和响应类型经常要靠手写 d.ts 维护,改一个字段可能两边都漏掉。T3 Stack 用 tRPC 让前端直接调用后端函数,类型定义是同一个源头,业务里增加参数或者改返回值,前端立即就能看到编译错误。这种体验用熟了之后,再回去手写 API 会感觉像在裸奔。

第二,渐进式上手。Next.js 同时负责页面渲染和 API,Tailwind 做样式,Prisma 管数据库,每个环节都是当前生态里学习资料最丰富、坑最少的选项。即使没有 T3 Stack 经验的人,看到目录结构也能大致猜出每个文件是干嘛的,这比引入复杂的微前端或者独立 BFF 层友好得多。

第三,部署简单。Next.js 应用可以直接部署到 Vercel,Prisma 对接托管 Postgres 数据库,环境变量配置好之后基本是流水线式发布。个人项目最怕运维成本拖垮积极性,这一套方案能把从开发到上线的路径压缩到最短。

2. 技术选型与架构设计

2.1 技术栈组成与分工

t3code 的核心技术栈是:Next.js 14 + App Router、TypeScript、Tailwind CSS、tRPC v11、Prisma、NextAuth.js。最后用的包管理器是 pnpm,因为它在依赖安装速度和磁盘占用上明显优于 npm,对于一个会长期迭代的项目来说是稳妥的选择。

每个组件在项目里的分工是这样:Next.js 负责页面路由、服务端渲染和 API 路由的宿主;TypeScript 提供全项目静态类型约束;Tailwind 处理所有 UI 样式,不写单独的 CSS 文件;tRPC 替代了手写 REST API,前端组件直接调用后端 router 里的 procedure;Prisma 负责数据库表结构定义、迁移和查询;NextAuth 处理 GitHub OAuth 登录和 Session 管理。

可能有人会问:已经有 REST 了,为什么还要引入 tRPC?这要从实际开发体验说。t3code 里删除一个片段,如果走 REST 就要定义 DELETE /api/snippets/:id,然后写前端 fetch、处理错误、维护类型。走 tRPC 的话,在后端写一个delete: protectedProcedure.input(...).mutation(...),前端通过生成的api.snippet.delete.useMutation()调用,参数类型和返回类型完全自动推断,省掉了一大堆样板代码。对于这种以 CRUD 为主的业务,tRPC 的开发效率优势非常明显。

2.2 目录结构与前后端边界

这是项目初始化后 App Router 模式下的核心目录结构,我稍微调整后加上了业务代码:

t3code/ ├── prisma/ │ ├── schema.prisma │ └── migrations/ ├── src/ │ ├── app/ │ │ ├── api/trpc/[trpc]/route.ts │ │ ├── (auth)/login/page.tsx │ │ ├── (main)/ │ │ │ ├── page.tsx │ │ │ ├── new/page.tsx │ │ │ ├── snippet/[id]/page.tsx │ │ │ └── dashboard/page.tsx │ ├── server/ │ │ ├── api/ │ │ │ ├── root.ts │ │ │ ├── routers/ │ │ │ │ ├── snippet.ts │ │ │ │ ├── tag.ts │ │ │ │ ├── star.ts │ │ │ │ └── user.ts │ │ │ └── trpc.ts │ │ ├── auth.ts │ │ └── db.ts │ ├── trpc/ │ │ ├── server.ts │ │ └── client.tsx │ └── styles/ └── package.json

这个结构的关键点在于把 server 代码和 client 代码物理分开。所有涉及数据库、鉴权、业务规则的逻辑都放在src/server下,浏览器端永远不直接接触 PrismaClient。src/trpc/server.ts和src/trpc/client.tsx是 tRPC 的桥接层,前者在服务端组件里调用 API,后者在客户端组件里挂载 Provider。

// src/trpc/client.tsx "use client"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { httpBatchLink } from "@trpc/client"; import { createTRPCReact } from "@trpc/react-query"; import { useState } from "react"; import type { AppRouter } from "@/server/api/root"; export const api = createTRPCReact<AppRouter>(); export function TRPCReactProvider(props: { children: React.ReactNode }) { const [queryClient] = useState(() => new QueryClient()); return ( <QueryClientProvider client={queryClient}> <api.Provider queryClient={queryClient} endpoint="/api/trpc" > {props.children} </api.Provider> </QueryClientProvider> ); }

2.3 从 Schema 到 tRPC 的类型闭环

T3 Stack 最让我满意的地方,是整个数据链路从数据库到 UI 是同一份类型。Prisma Schema 定义了模型,tRPC 的 procedure 返回数据库查询结果,前端组件直接接收这个返回类型。中间不需要手写 DTO,也不用背后维护 document 文档,基因层面的类型一致性让改造成本降到最低。

举个例子,t3code 里"获取我收藏的片段"这个需求,前端页面只需要这么写:

const { data, isLoading } = api.star.myStars.useQuery(); if (isLoading) return <p>加载中...</p>; return data?.map((snippet) => ( <div key={snippet.id}>{snippet.title}</div> ));

data的类型就是Snippet[],编辑器里会有完整的补全提示。如果数据库表结构变化,比如Snippet新增了字段,代码里马上会提示相应调整,不存在"数据返回了但前端不知道"这种状态。这套体验让我意识到,所谓类型安全不是单纯的上纲上线,而是实打实地减少排查 bug 的时间。

3. 核心实操:从零实现 t3code

3.1 用 create-t3-app 初始化项目

初始化是性价比最高的一步,不用手动拼装十几个依赖。我在空目录里执行:

pnpm create t3-app@latest t3code

创建过程中会有几个交互选项,全部勾选核心的几项:Next.js、TypeScript、Tailwind CSS、tRPC、Prisma、NextAuth.js,ESLint 也要选上。安装完成后直接pnpm dev就能启动,默认页面会引导你完成环境变量配置。

需要提醒的是,create-t3-app 生成的模板会用环境变量文件.env保存数据库连接和 NextAuth 配置,里面给了示例值。我第一次跑的时候,直接复制了.env.example为.env,然后忘了改DATABASE_URL,结果 Prisma 一直报连接错误。这类问题在项目初期最容易浪费时间的,建议开局先把环境变量逐项检查一遍。

DATABASE_URL="file:./db.sqlite" NEXTAUTH_SECRET="随便填一长串随机字符串" NEXTAUTH_URL="http://localhost:3000"

3.2 设计数据模型并完成首次迁移

create-t3-app 自带的 schema.prisma 已经包含 NextAuth 需要的 Account、Session、User、VerificationToken 四个模型,这部分我基本原样保留,然后在此基础上追加业务模型。核心是 Snippet 和 Star,Tag 用独立的模型维护,方便做标签聚合统计。

model User { id String @id @default(cuid()) name String? email String? @unique emailVerified DateTime? image String? accounts Account[] sessions Session[] snippets Snippet[] stars Star[] } model Snippet { id String @id @default(cuid()) title String description String? content String language String @default("plaintext") isPublic Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt authorId String author User @relation(fields: [authorId], references: [id], onDelete: Cascade) tags SnippetTag[] stars Star[] @@index([authorId, createdAt(sort: Desc)]) @@index([language, isPublic]) } model Tag { id String @id @default(cuid()) name String @unique snippets SnippetTag[] } model SnippetTag { snippetId String tagId String snippet Snippet @relation(fields: [snippetId], references: [id], onDelete: Cascade) tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade) @@id([snippetId, tagId]) } model Star { userId String snippetId String createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) snippet Snippet @relation(fields: [snippetId], references: [id], onDelete: Cascade) @@id([userId, snippetId]) }

这里给Snippet的authorId加外键级联删除,是为了用户注销时片段一并清理,避免留下孤儿数据。给authorId和createdAt建联合索引,是为了个人片段列表能快速按时间倒序拉取,后期数据量上来也不会瞬间变慢。数据库用 SQLite 起步开发足够,线上部署再切换 Postgres,Prisma 迁移能保证切换过程不至于太痛苦。

定义好模型后执行迁移:

pnpm prisma migrate dev --name init

这条命令会生成 migration 文件并同步数据库,之后每次改 schema 都用类似流程生成新的迁移版本。要注意的是,团队协作时 migration 文件要一起提交,否则其他人的表结构就和你对不上了。

3.3 用 tRPC 把业务逻辑收敛到 server router

tRPC 的好处是后端逻辑不是分散在多个 API 路由里,而是集中在src/server/api/routers下。t3code 的 snippet router 承担了最核心的增删改查逻辑,我用protectedProcedure保证未登录用户不能执行写操作。

import { z } from "zod"; import { createTRPCRouter, protectedProcedure, publicProcedure } from "@/server/api/trpc"; import { SnippetTag } from "@prisma/client"; const snippetCreateSchema = z.object({ title: z.string().min(1, "标题不能为空").max(80), description: z.string().max(200).optional(), content: z.string().min(1, "代码内容不能为空"), language: z.string().default("plaintext"), isPublic: z.boolean().default(true), tags: z.array(z.string()).max(10).default([]), }); export const snippetRouter = createTRPCRouter({ list: publicProcedure .input( z.object({ language: z.string().optional(), keyword: z.string().optional(), cursor: z.string().optional(), }) ) .query(async ({ ctx, input }) => { const where = { isPublic: true, ...(input.language && { language: input.language }), ...(input.keyword && { OR: [ { title: { contains: input.keyword, mode: "insensitive" } }, { description: { contains: input.keyword, mode: "insensitive" } }, ], }), }; return ctx.db.snippet.findMany({ where, take: 20, skip: input.cursor ? 1 : 0, cursor: input.cursor ? { id: input.cursor } : undefined, orderBy: { createdAt: "desc" }, include: { author: { select: { name: true, image: true } }, tags: { include: { tag: true } } }, }); }), create: protectedProcedure .input(snippetCreateSchema) .mutation(async ({ ctx, input }) => { const { tags, ...snippetData } = input; return ctx.db.snippet.create({ data: { ...snippetData, authorId: ctx.session.user.id, tags: { create: tags.map((tagName) => ({ tag: { connectOrCreate: { where: { name: tagName }, create: { name: tagName }, }, }, })), }, }, }); }), delete: protectedProcedure .input(z.object({ id: z.string() })) .mutation(async ({ ctx, input }) => { const existing = await ctx.db.snippet.findUnique({ where: { id: input.id } }); if (!existing || existing.authorId !== ctx.session.user.id) { throw new Error("无权操作该片段"); } return ctx.db.snippet.delete({ where: { id: input.id } }); }), });

有几个细节值得展开说。第一,关键词搜索用mode: "insensitive"是为了不区分大小写,英文关键词友好;中文场景下 Prisma 的 contains 也能正常工作,但 PostgreSQL 延迟会更大一些,所以搜索场景更适合用 pg_trgm 这类扩展来做索引,这是后话。第二,标签表用connectOrCreate是防止重复创建同名标签,一次性把标签和片段关联关系写好。第三,删除操作先查一次再做权限校验,虽然多一次数据库查询,但能避免直接注入删除条件绕过鉴权。

3.4 前端页面与样式细节

页面层面,t3code 主要拆成三块:首页片段瀑布流、新建/编辑页面、用户仪表盘。App Router 模式下,页面组件默认是服务端组件,但凡是需要交互的地方我都显式加了"use client"。比如首页的筛选下拉框,如果用服务端组件去接收表单请求,会经历一次完整页面刷新,体验上不如客户端筛选来得流畅,所以我让列表本身由服务端渲染,筛选动作通过 tRPC query 带着新参数重新请求,这样 URL 还能保持可分享的状态。

新建页面是典型的客户端表单,用 React Hook Form 管理状态成本太低,但 t3code 这种规模直接useState也够了。真正值得注意的是代码编辑器部分,我没有引入 monaco-editor,因为对单文件片段管理来说太重了,最后选的是 CodeMirror 6 的轻量方案,支持语法高亮:

"use client"; import CodeMirror from "@uiw/react-codemirror"; import { javascript } from "@codemirror/lang-javascript"; import { python } from "@codemirror/lang-python"; import { cpp } from "@codemirror/lang-cpp"; import { useState } from "react"; const languageMap = { javascript: javascript(), python: python(), cpp: cpp(), }; export function CodeEditor({ initialValue, onChange }: { initialValue?: string; onChange: (value: string) => void }) { const [value, setValue] = useState(initialValue ?? ""); return ( <CodeMirror value={value} extensions={[languageMap.javascript]} onChange={(val) => { setValue(val); onChange?.(val); }} className="rounded-lg border border-gray-200 text-sm" /> ); }

样式上,Tailwind 让我完全跳过了写 CSS 文件的过程。页面布局基于 flex 和 grid 完成,列表卡片用shadow-sm rounded-xl border-gray-100这类原子类即可。这里我养成了一个习惯:设计页面时先想信息层级,再想组件拆分,最后才写标签和类名,Tailwind 类名虽然多,但结构和视觉是可以在脑子里先对齐的。

3.5 部署与发布

最后一步是把 t3code 推到生产环境。个人项目我推荐 Vercel 配合托管 Postgres,路线最短。先在 Vercel 导入 Git 仓库,把 Prisma 相关的构建命令配置好,再设置环境变量,包括DATABASE_URL、NEXTAUTH_SECRET、NEXTAUTH_URL和 OAuth 的 Client ID、Client Secret。

部署前有一个关键点:Vercel 构建环境的 Prisma Client 需要重新生成。我在package.json里加了 postinstall 钩子:

{ "scripts": { "build": "next build", "postinstall": "prisma generate" } }

如果不加这个钩子,构建时会出现 "PrismaClient is not initialized" 或者版本不匹配的问题。生产环境迁移也要注意,不要在构建时自动跑prisma migrate deploy之外的命令,我会在发布前手动执行一次迁移,或者在 CI 里单独加一个 job 处理。

部署完成之后,OAuth 回调地址要改成线上域名。这个坑几乎每个用 NextAuth 的人都会踩一次:本地测试没问题,部署后一直登录失败,最后发现NEXTAUTH_URL没改成 https 域名,或者 GitHub OAuth App 的回调地址还写着 localhost。

4. 常见问题与排查实录

4.1 tRPC 报错与类型不收敛

tRPC 类型偶尔会在两种情况下出问题:一是某个 procedure 里返回了Prisma.PromiseReturnType之类的复杂类型,给前端用的时候推断不完整;二是泛型嵌套太深,编辑器直接卡住或者显示 "Type instantiation excessively deep and possibly infinite",这种一般是输入输出类型写的太花哨。

排查思路很简单:先用export type AppRouter = typeof appRouter确认 root router 的类型导出正常,再一层层缩小到一个 procedure。我遇到最多的是在 query 的返回值里用include: { author: true },前端拿到的 author 是对象类型,模板里想访问snippet.author.name,但类型上 author 可能是null。这是因为外键关系默认可空,Prisma 生成的类型是User | null,所以访问时要么非空断言,要么做安全判断,这是类型的正确工作方式,不是 bug。

4.2 NextAuth 登录回调失败

登录回调失败是高频问题,根因基本集中在三个地方:NEXTAUTH_SECRET不一致、OAuth 回调地址不对、Session 策略配置错误。

如果部署在 Vercel 上,NEXTAUTH_SECRET必须稳定,不能每次部署都重新生成。我见过有人把 secret 直接写在代码里读取process.env,然后本地和生产不一致,导致登录后 session 校验失败。解决方式是用openssl rand -base64 32生成一串固定值存到环境变量里。NextAuth 的AUTH_SECRET在 v5 里会自动尝试读取,如果缺失会有警告,不要忽略。

另外,如果启用了database: ctx.db存储 session,还要检查 Session 模型是否在 schema 里,以及 Session 表内字段名是否和 Prisma 默认一致。

4.3 Prisma 查询性能与连接数

开发时 SQLite 没压力,生产切到 Postgres 后,如果所有页面都无条件查表,数据量增长后容易慢。t3code 里我做了几层优化:列表查询总是配合take限制条数,用游标分页而不是skip大偏移量;统计标签使用量时直接groupBy而不是先把全部标签拉回内存再数。

Prisma 连接数还容易在 serverless 环境里爆掉。Vercel 每个函数实例都可能建立数据库连接,如果连接池上限是 10,流量一上来就会出现 "Too many connections"。解决方式是用连接池代理或者限制prisma-client的复用,把 PrismaClient 实例声明成全局单例:

import { PrismaClient } from "@prisma/client"; const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient }; export const db = globalForPrisma.prisma ?? new PrismaClient(); if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = db;

这段代码来自 create-t3-app 模板,但很多人不理解为什么要这样写。原因是 serverless 环境下模块可能是热加载的,如果不缓存 PrismaClient,每来一个请求就 new 一个实例,连接数会快速膨胀,最终拖垮数据库。

4.4 Tailwind 与动态类名

Tailwind 一个容易踩的坑是:动态拼接类名不生效。因为 Tailwind 在构建时会扫描源码里的完整类名字符串,运行时bg-${color}-500这种写法不会被识别。我在 t3code 里做语言标签颜色时最开始就是拼接,结果显示全部没有样式。

解决方案是把完整类名写进映射表:

const languageColor: Record<string, string> = { javascript: "bg-yellow-100 text-yellow-800", python: "bg-blue-100 text-blue-800", cpp: "bg-red-100 text-red-800", plaintext: "bg-gray-100 text-gray-800", };

这样 Tailwind 扫描时能看到完整的字符串,样式自然就生效了。

4.5 问题速查表

现象根因解法
本地正常部署后登录失败NEXTAUTH_URL 或 OAuth 回调地址未更新统一改为线上域名,检查 GitHub OAuth 配置
Prisma 报连接错误DATABASE_URL 写错或数据库未迁移核对连接串,执行prisma migrate deploy
点击按钮没有响应tRPC mutation 未触发或异常未捕获检查 Procedure 是否 protected,查看浏览器 network 请求体
某个 Tab 样式全无动态拼接 Tailwind 类名用完整类名映射表替代字符串拼接
图片头像不显示NextAuth 默认头像 URL 被屏蔽或自签名设置images域名白名单或直接本地存储头像
部署时构建失败postinstall 缺少 prisma generate在 package.json 增加 postinstall 钩子

4.6 排查问题时的工具与方法

遇到线上问题,我一般先看vercel logs或服务商日志,确认是不是构建阶段就挂了;如果构建正常,再看运行时错误,tRPC 的错误会封装在TRPCClientError里,浏览器 console 能直接看到 message。Prisma 查询慢的话,我会开启查询日志,通过PrismaClient({ log: ['query'] })查看每条语句的实际耗时,定位到底是缺索引还是数据量问题。

还有一个容易忽略的细节:Next.js 的静态优化会让部分页面在构建时执行服务端代码,如果某个页面调用了 tRPC 查询,而这个查询依赖 session,构建时没有 session 就会出错。遇到这种场景,需要把相关页面标记为动态渲染,或者把数据获取放在客户端组件里。

5. 写在最后的一点经验

t3code 这个项目从搭建框架到跑通核心流程,前后大概用了不到一周的业余时间,其中大部分时间不是在写业务逻辑,而是在调整环境、排查类型、处理部署细节。整个过程中我最大的体会是:技术栈选得稳,确实能省掉很多折腾。T3 Stack 给我的不是"哪个库更酷"的兴奋感,而是"所有环节都长在同一个语义体系下"的踏实感。这种踏实感在调试 bug 时特别明显,因为大部分错误不是藏在接口边界,而是出现在真实业务里,排查路径非常短。

如果之后继续迭代 t3code,我大概会先补上两件事:一是给片段加 Markdown 描述渲染,让说明文字能格式化展示,而不是纯文本;二是做一个公开 API,让其他工具可以按 token 方式读取片段。代码片段管理这种工具,真正的价值在于"用起来顺手",而这取决于你对细节的把控,比如搜索是否够快、收藏是否流畅、复制是否一步到位。对我个人来说,这也是做这个项目最上瘾的部分:每次改完一个小体验,都能立刻感受到自己写的东西在变好。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 15:26:30

LangGraph 入门:从 State、Node、Edge 到工具调用实战

1. 从零认识 LangGraph&#xff1a;它到底解决什么问题如果你已经用过 LangChain&#xff0c;大概率经历过这样的场景&#xff1a;想让大模型按固定流程走几步&#xff0c;中间还要根据结果决定下一步做什么&#xff0c;用 Chain 拼来拼去&#xff0c;代码很快就变成了一团乱麻…

作者头像 李华
网站建设 2026/10/28 15:24:58

MLA 配置调优先做兼容性检查:别复制来路不明的参数表

配置表越短&#xff0c;越容易显得可靠&#xff1a;某开关设 True&#xff0c;显存利用率调到 0.95&#xff0c;并发拉到 1024。问题是这些字段属于哪个引擎版本、哪个后端、哪个模型&#xff1f;若参数根本不存在&#xff0c;或者只在特定 Attention 路径生效&#xff0c;后面…

作者头像 李华
网站建设 2026/10/28 15:24:50

推理网关的重试预算:过载时别让客户端把洪峰翻倍

一次超时之后&#xff0c;客户端重试&#xff1b;网关也重试&#xff1b;上游 SDK 再重试。表面上只处理一个问题&#xff0c;后台可能已经出现多份相同请求。生成任务又往往耗时长&#xff0c;旧请求即使失去连接&#xff0c;也可能继续占着推理资源。 因此重试策略不能只写“…

作者头像 李华
网站建设 2026/10/28 15:24:28

显存高不等于该扩容:推理服务需要排队与预热联合信号

模型加载完后&#xff0c;即使一条请求也没有&#xff0c;显存占用也可能很高。反过来&#xff0c;显存还没满&#xff0c;排队的长提示已经能把首字延迟拖垮。用一个显存百分比控制扩容&#xff0c;很容易在这两种场景里做反决定。 10 月 19 日 vLLM 的 Elastic EP 介绍了把背…

作者头像 李华
网站建设 2026/10/28 15:24:01

cloudflare-os:用 Rust/WASM 在浏览器里实现云桌面与文件系统

说实话&#xff0c;我第一次看到 cloudflare-os 这个项目名的时候&#xff0c;第一反应是“谁这么大胆&#xff0c;敢用 OS 来命名一个浏览器项目”。但深入了解之后发现&#xff0c;这恰恰是它最值得玩味的地方&#xff1a;它把操作系统的交互范式整体搬到了浏览器里&#xff…

作者头像 李华