ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

深入理解 GraphQL 服务端:执行算法、Resolver 与批量解析优化

深入理解 GraphQL 服务端:执行算法、Resolver 与批量解析优化 【免费下载链接】howtographqlThe Fullstack Tutorial for GraphQL项目地址https://gitcode.com/gh_mirrors/ho/howtographql点击查看免费下载GraphQL 常被视为一门面向前端的技术因为它让客户端获取数据的方式变得优雅但真正承载 GraphQL 价值的是服务端实现。本指南围绕 GraphQL 服务端的两大核心主题展开GraphQL 规范定义的查询执行算法如何将查询字段逐一解析为结果以及针对朴素执行策略的批量解析优化如何避免 N1 请求问题。读完本文你将掌握 resolver 的调用链与四参数模型、默认 resolver 的触发条件以及基于 DataLoader 的批处理与缓存思想并能直接套用到 JavaScript/TypeScript 等主流 GraphQL 服务端实现中。GraphQL 执行算法从查询到结果的转换GraphQL 不仅仅规范了描述 Schema 的 SDL 语法和检索数据的查询语言它还定义了一套实际的执行算法用于说明查询是如何被转换成结果的。这套算法核心非常简单查询被逐个字段地遍历为每个字段执行对应的 resolver解析函数。假设我们有如下 Schematype Query { author(id: ID!): Author } type Author { posts: [Post] } type Post { title: String content: String }针对该 Schema客户端可以发送如下查询query { author(id: abc) { posts { title content } } }首先要认识到的一点是查询中的每一个字段都可以与一个类型相关联如下所示query: Query { author(id: abc): Author { posts: [Post] { title: String content: String } } }有了这种字段与类型的对应关系服务端就能轻松地为每个字段找到需要运行的 resolver。执行从Query类型开始并且以广度优先breadth-first的顺序展开也就是说先运行Query.author的 resolver然后取出该 resolver 的结果将其传递给子级 ——Author.posts的 resolver。到了下一层结果是一个列表此时执行算法会逐项处理列表中的每个元素。整个执行过程如下Query.author(root, { id: abc }, context) - author Author.posts(author, null, context) - posts for each post in posts Post.title(post, null, context) - title Post.content(post, null, context) - content执行结束时执行算法会将所有结果按查询的形状组装起来返回给客户端。resolver 的通用签名parent、args、context、info从上面的伪代码可以看到每个 resolver 都会被调用并接收若干参数。绝大多数 GraphQL 服务端实现中resolver 函数统一接收四个输入参数parent也称root上一层 resolver 执行的结果。例如在Author.posts(author, ...)中author就是Query.author解析出的对象。args本次查询中传给该字段的参数例如author(id: abc)中的{ id: abc }。context跨 resolver 共享的上下文对象通常用于存放数据库连接、认证信息等。info关于当前查询字段的元信息如 AST、返回类型等。本仓库的实战教程印证了这一模型。在 graphql-js 教程的查询章节中feed查询的 resolver 被命名为与 Schema 字段完全一致的名字并依赖parent参数传递上一层的结果const resolvers { Query: { info: () This is the API of a Hackernews Clone, feed: () links, }, Link: { id: (parent) parent.id, description: (parent) parent.description, url: (parent) parent.url, } }教程中明确指出GraphQL 查询可以嵌套每一层嵌套即每一层花括号对应一个 resolver 执行层级。第一层调用feedresolver 返回整个links数据第二层时服务端借助 Schema 知道feed返回的是Link元素列表于是对列表中的每个元素逐一调用Link类型的 resolver此时传入的parent正是列表中的单个元素。在 typescript-apollo 教程中Nexus 的resolve(parent, args, context, info)签名与此完全一致可见这是跨语言、跨框架通用的约定。默认 resolver并非每个字段都需要手写解析函数值得注意的是大多数 GraphQL 服务端实现都提供了 默认 resolver因此你并不需要为每一个字段都编写 resolver 函数。以 GraphQL.js 为例当 resolver 的parent对象里包含一个同名字段时你就不必显式指定该字段的 resolver。从仓库的 schema 也可以直观地看出这种约定背后的理由。以 meta/structure.graphql 中的Link类型为例type Link implements Node { id: ID! isUnique createdAt: DateTime! url: String! description: String! postedBy: User! relation(name: UsersLinks) votes: [Vote!]! relation(name: VotesOnLink) }只要数据源对象本身带有id、url、description等属性服务端就能自动推断并返回这些字段无需编写形如(parent) parent.url的平凡 resolver。这正是 graphql-js 教程末尾所强调的因为Link的三个 resolver 实现过于琐碎完全可以省略服务端行为不变。批量解析Batched Resolving消除 N1 请求朴素执行策略的问题上面描述的逐字段执行策略有一个明显的短板它相当朴素naive。如果一个 resolver 需要从后端 API 或数据库取数那么单次 GraphQL 查询执行期间后端可能被调用很多次。想象我们需要获取多篇文章的作者信息query { posts { title author { name avatar } } }如果是博客场景很多文章的作者往往是同一个人。如果获取每个 author 对象都需要一次 API 调用就可能对同一位作者发起多次冗余请求fetch(/authors/1) fetch(/authors/2) fetch(/authors/1) fetch(/authors/2) fetch(/authors/1) fetch(/authors/2)这就是经典的 N1 查询问题一次查询触发了 N 次数据源访问。解决方案一去重 合并Loader 模式解决办法是让取数逻辑变得更聪明将取数函数包装进一个工具loader中让它等待所有 resolver 都运行完毕后再确保每个数据项只被获取一次authorLoader new AuthorLoader() // 排队一批取数请求 authorLoader.load(1); authorLoader.load(2); authorLoader.load(1); authorLoader.load(2); // 然后loader 只做最小量的工作 fetch(/authors/1); fetch(/authors/2);这种模式有两个核心收益合并去重在同一轮执行中相同的 ID 只会触发一次真实请求上面示例中1和2各被请求三次最终各自只 fetch 一次。短时间窗口批处理loader 会在当前事件循环内收集所有load调用然后统一发起请求避免逐个请求造成的延迟叠加。解决方案二后端批处理接口如果后端 API 本身就支持批处理请求还能做得更好 —— 只需一次对后端的请求fetch(/authors?ids1,2)这种能力同样可以封装进上述 loader 中loader 收集到 ID 列表后拼成一条批处理请求发出再把结果按 ID 分发给各个等待中的 resolver。DataLoaderJavaScript 生态的标准答案在 JavaScript 中上述策略可以通过一个名为DataLoader的工具来实现其他语言也有功能类似的对应工具。DataLoader 提供的正是在单个执行周期内批量加载 按主键缓存的组合能力是解决 GraphQL 服务端 N1 问题的标准实践。从仓库中可以找到真实世界中 resolver 访问数据库的印证在 graphql-go 教程的创建与检索章节以及 graphql-java 教程的连接器章节中resolver 与数据库/API 的交互正是上述执行模型的具体落地 —— 每层 resolver 独立取数因此批量解析优化对于任何语言的 GraphQL 服务端都同样重要。小结GraphQL 服务端远不止实现一组端点那么简单执行算法查询按广度优先、字段级粒度遍历每个字段对应一个 resolverresolver 统一接收(parent, args, context, info)四个参数。默认 resolver当parent对象含同名字段时服务端自动推断取值平凡字段无需手写解析函数。批量解析用 loader 包装取数函数等待所有 resolver 运行后合并去重或直接对接后端批处理接口从根本上消除 N1 请求。把这两部分结合起来你就拥有了设计高并发、低延迟 GraphQL 服务端的核心知识先按规范实现正确的解析模型再针对数据访问层做批量优化。仓库中 graphql-js、typescript-apollo、graphql-go 等后端教程正是这套执行模型在不同语言与框架中的完整实战演示。赞分享【免费下载链接】howtographqlThe Fullstack Tutorial for GraphQL项目地址https://gitcode.com/gh_mirrors/ho/howtographql点击查看免费下载相关推荐Golem批量处理优化任务队列与并行执行策略Golem批量处理优化任务队列与并行执行策略 在分布式计算场景中批量处理任务的效率直接影响系统吞吐量和资源利用率。Golem作为支持多语言WebAssemb思源笔记入门实操5 步搭好本地块级知识库再学会 CLI 自动化思源笔记入门实操5 步搭好本地块级知识库再学会 CLI 自动化 思源笔记SiYuan是一个本地优先的开源个人知识管理系统核心能力是把每篇文档拆成独立的知识管理知识库GraphQL 规范执行篇从请求到响应的完整执行算法解析graphql-specGraphQL 规范执行篇从请求到响应的完整执行算法解析graphql spec 本篇技术指南围绕 GraphQL 规范graphql spec 仓库API设计后端上一篇NVIDIA Profile Inspector完全指南解锁显卡隐藏性能的终极指南下一篇如何开始使用Arend10分钟快速入门教程与安装配置指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表