有域名如何搭建网站,网页制作制作公司,专业网络优化,织梦手机网站有广告开篇
原文出处
Graphql 是一种 API 查询语言和运行时环境#xff0c;可以帮助开发人员快速构建可伸缩的 API。然而#xff0c;尽管 Graphql 可以提供一些优秀的查询性能和数据获取的能力#xff0c;但是在使用 Graphql 的过程中#xff0c;开发人员也会遇到一些常见问题可以帮助开发人员快速构建可伸缩的 API。然而尽管 Graphql 可以提供一些优秀的查询性能和数据获取的能力但是在使用 Graphql 的过程中开发人员也会遇到一些常见问题其中最常见的一个问题是 N1 问题。
什么是 GraphQL 中的 N1 问题
在 GraphQL 中N1 问题指的是在一个查询语句中某个字段需要通过 N 次额外查询来获取其关联的数据导致查询效率低下的情况。这个问题的本质是由于 GraphQL 的数据模型本身的特性引起的。
在 GraphQL 中查询语句可以包含多个字段每个字段可能需要访问一个不同的数据源。当查询涉及到关联数据时如果不做特殊处理GraphQL 会逐个获取每个字段的数据这可能会导致大量的额外查询进而影响查询效率。
假设我们有一个电影网站它有电影和演员两个实体每部电影都有多个演员。我们可以用 GraphQL 定义如下的 schema
type Movie {id: ID!title: String!actors: [Actor!]!
}type Actor {id: ID!name: String!age: Int!
}type Query {movies: [Movie!]!
}现在我们想要查询所有电影及其演员。我们可以像这样编写 GraphQL 查询
query {movies {titleactors {name}}
}在这个查询中我们获取了所有电影的标题以及每部电影的所有演员的名称。然而如果我们没有采取任何措施来解决 N1 问题每个电影的演员都将需要单独查询。因此如果我们有 100 部电影就会产生 101 次查询1 次获取电影100 次获取演员这会严重影响性能。
解决方案
Data loader
Data loader 是一个常用的解决 N1 问题的工具它可以将多个查询合并成一个查询以减少查询次数。它的工作原理是在执行查询时将多个相同类型的查询合并成一个批量查询并将结果缓存起来以便在需要时快速获取。Data loader 可以轻松地与 GraphQL 集成并提供了许多可配置的选项以便根据应用程序的需要进行优化。
下面是一个使用 data loader 的示例代码
const DataLoader require(dataloader)
const { actorsByMovieId } require(./db)const actorsLoader new DataLoader(async (movieIds) {const actors await actorsByMovieId(movieIds)const actorsMap actors.reduce((acc, actor) {acc[actor.movieId] acc[actor.movieId] || []acc[actor.movieId].push(actor)return acc}, {})return movieIds.map((movieId) actorsMap[movieId] || [])
})const resolvers {Query: {movies: () getMovies(),},Movie: {actors: (movie, args, context, info) actorsLoader.load(movie.id),},
}在上面的代码中我们使用 data loader 来批量获取每个电影的演员。当 GraphQL 执行查询时它将调用 load 函数并将所有需要获取的电影 ID 传递给它。load 函数将所有电影 ID 作为参数并从数据库中获取所有与这些电影相关的演员。然后它将演员按电影 ID 分组并将结果返回到 GraphQL 查询中。由于使用了 data loader我们现在只需要进行一次查询来获取所有电影及其演员。
Join Monster
Join Monster 是一个解决 GraphQL N1 问题的工具它使用了 SQL 批量操作的思想。Join Monster 的主要思想是将多个 GraphQL 解析器的数据请求合并成一个 SQL 查询。这个 SQL 查询是经过优化的只会查询数据库中需要的数据。同时Join Monster 还使用了多级缓存来减少数据库的访问次数。
在代码层面使用 Join Monster 时我们需要先定义一个解析器然后在 GraphQL 的 schema 中使用该解析器来查询数据。以下是一个使用 Join Monster 的示例代码
const joinMonster require(join-monster).default
const { GraphQLObjectType, GraphQLList } require(graphql)
const db require(./db)
const { UserType } require(./userType)const CommentType new GraphQLObjectType({name: Comment,fields: {id: { type: GraphQLInt },content: { type: GraphQLString },user: {type: UserType,resolve: (parent, args, context, resolveInfo) {return joinMonster(resolveInfo, {}, (sql) {return db.query(sql)})},},},
})const Query new GraphQLObjectType({name: Query,fields: {comments: {type: new GraphQLList(CommentType),resolve: (parent, args, context, resolveInfo) {return joinMonster(resolveInfo, {}, (sql) {return db.query(sql)})},},},
})module.exports new GraphQLSchema({ query: Query })在上述代码中我们定义了一个 CommentType它包含了一个 user 字段该字段使用 Join Monster 进行了解析。同时我们还定义了一个 Query该 Query 包含了 comments 字段使用了 joinMonster 进行解析。在 resolve 函数中我们将 Join Monster 的解析器传入并在其中使用了 db.query 函数执行了查询。
假设我们有如下 GraphQL 查询
{comments {idcontentuser {idname}}
}在使用 Join Monster 之前该查询需要进行 N1 次 SQL 查询每个 comment 对应一次查询每个 user 对应一次查询。
在使用 Join Monster 之后我们的查询只需要进行一次 SQL 查询。Join Monster 会根据 GraphQL 查询中的字段生成相应的 SQL 查询语句并在数据库中执行该语句。以下是 Join Monster 生成的 SQL 语句的示例
SELECTComment.id,Comment.content,User.id AS user.id,User.name AS user.name
FROMComment
LEFT JOINUser
ONComment.userId User.id
这个 SQL 查询语句会同时返回 comments 和它们对应的 users 的信息。由于只进行了一次 SQL 查询Join Monster 大大减少了数据库访问的次数从而提升了性能。
方案对比
方案优点缺点适用场景dataloader可以自动处理 N1 查询问题可以使用缓存机制提高性能比较成熟稳定社区支持度高不能自动处理多层嵌套对复杂查询支持不够好需要手动编写基于 dataloader 嵌套查询适用于中小规模的项目需要快速上手提高开发效率的场景join-monster可以自动生成高效的 SQL 查询性能优秀 可以自动处理多层嵌套的 N1 查询问题依赖于 SQL 数据库不适用于非 SQL 数据库场景需要将 Graphql 当作 ORM适用于需要高性能的场景需要处理复杂查询场景
Data loader 的实现
考虑到 dataloader 比较好实现且使用广泛我们选取它进行简单的实现以此更加深入的理解它是如何解决 N1 问题的。
根据DataLoader的使用例子来看DataLoader除了构造器以外只有一个 load 方法所以一个简单的 DataLoader 的声明如下
type BatchFn Key, Entity(keys: Key[]): PromiseEntity[];class DataLoaderKey, Entity {constructor(batchFn: BatchFnKey, Entity) {// todo}load(key: Key): PromiseEntity {// todo}
}
load 方法只是加入到 batch 的队列中并不会立刻执行执行条件是“没有地方调用 load 后“才会执行整个 batch 队列的请求。于是有了一个小实现
class DataLoaderKey, Entity {readonly batchFn: BatchFnKey, Entity;readonly keys: Key[] [];constructor(batchFn: BatchFnKey, Entity) {this.batchFn batchFn;}async load(key: Key): Promisevoid {this.keys.push(key);if (this.keys.length 1) {this.doBatch();//I hope it executes later}}doBatch(): PromiseEntity[] {return this.batchFn(this.keys);}
}代码很简单只是遗留了一个问题也是最重要的问题如何让this.doBatch能够延迟行延迟到所有的 load 同步方法调用完后。
此时就需要利用事件循环来改变它的执行顺序
setImmediate(() this.doBatch())因为setImmediate会在回调阶段执行因此会等到所有同步方法完成在执行。
一个DataLoader的最小实现就产生了:
class DataLoaderKey, Entity {readonly batchFn: BatchFnKey, Entity;readonly keys: Key[] [];constructor(batchFn: BatchFnKey, Entity) {this.batchFn batchFn;}async load(key: Key): Promisevoid {this.keys.push(key);if (this.keys.length 1) {setImmediate(() this.doBatch());}}doBatch(): PromiseEntity[] {return this.batchFn(this.keys);}
}可是它的功能很局限load 方法不能返回任何的值Graphql 的 resolve 也就解析不了了。
因此修改如下
export default class DataLoaderKey, Entity {readonly batchFn: BatchFnKey, Entity;readonly storage: {key: Key;promise: PromiseEntity;resolve: ((entity: Entity) void) | null;}[] [];constructor(batchFn: BatchFnKey, Entity) {this.batchFn batchFn;}async load(key: Key): PromiseEntity {let resolve null;const promise new PromiseEntity((res) (resolve res));this.storage.push({key,promise,resolve,});if (this.storage.length 1) {setImmediate(() this.doBatch());}return promise;}doBatch(): Promisevoid {const keys this.storage.map(({ key }) key);return this.batchFn(keys).then((entities) entities.forEach((entity, index) {const { resolve } this.storage[index];resolve resolve(entity);}));}
}
doBatch将结果依次给到 load 当时挂载的 promise 上这样以来 resolver 中的 promise 状态就会由 pending 转化为 fulfilled。
当然为了考虑性能和健壮性我们还可以继续扩展
增加缓存捕获异常支持手动执行 batch
最终完善如下(github repo):
type BatchFnK, E (keys: K[]) PromiseE[];interface PromiseMetaE {resolve: ((entity: E) void) | null;promise: PromiseE;
}interface Options {immediate: boolean;
}export default class DataLoaderK, E {readonly batchFn: BatchFnK, E;readonly cache new MapK, PromiseMetaE();readonly options: Options {immediate: true,};constructor(batchFn: BatchFnK, E, options?: Options) {this.batchFn batchFn;this.options {...this.options,...options,};}async load(key: K): PromiseE {if (this.options.immediate) {if (this.cache.size 0) {setImmediate(() this.doBatch());}}let resolve null;const promise new PromiseE((res) (resolve res));this.cache.set(key, {promise,resolve,});return promise;}doBatch(): Promisevoid {const keys [...this.cache.keys()];return this.batchFn(keys).then((entities) entities.forEach((entity, index) {const promiseMeta this.cache.get(keys[index]);if (promiseMeta) {const { resolve } promiseMeta;resolve resolve(entity);}})).catch(() this.cache.clear());}dispatch(): Promisevoid {if (!this.options.immediate) {return this.doBatch();}throw new Error(Doesnt allow to dispatch given immediate is true!);}
}
最后
在本文中我们深入探讨了 GraphQL 中的 N1 问题。首先我们介绍了 GraphQL 中常见的一些问题例如查询过度嵌套和查询重复等。然后我们详细介绍了 N1 问题的定义及其出现的原因。接着我们给出了具体的例子并讨论了 N1 问题对性能的影响。在解决 N1 问题方面我们列举了几种工具包括 Batch loading、Data loader 和 Join Monster并展示了它们在代码层面上的使用。我们还对这些工具的优缺点进行了比较和分析并给出了最佳实践。
最后我们介绍了一些避免 N1 问题的最佳实践例如避免嵌套查询、使用 GraphQL 片段和优化查询。这些实践可以帮助开发人员避免 N1 问题并提高查询性能。
总的来说N1 问题是 GraphQL 中常见的性能问题之一但是通过合适的工具和最佳实践我们可以有效地解决它提高查询性能为用户提供更好的体验。 文章转载自: http://www.morning.nmhpq.cn.gov.cn.nmhpq.cn http://www.morning.jbnss.cn.gov.cn.jbnss.cn http://www.morning.kxrhj.cn.gov.cn.kxrhj.cn http://www.morning.pjftk.cn.gov.cn.pjftk.cn http://www.morning.jpgfq.cn.gov.cn.jpgfq.cn http://www.morning.pznnt.cn.gov.cn.pznnt.cn http://www.morning.xfncq.cn.gov.cn.xfncq.cn http://www.morning.bpmdx.cn.gov.cn.bpmdx.cn http://www.morning.jynzb.cn.gov.cn.jynzb.cn http://www.morning.jnptt.cn.gov.cn.jnptt.cn http://www.morning.hhkzl.cn.gov.cn.hhkzl.cn http://www.morning.gmztd.cn.gov.cn.gmztd.cn http://www.morning.qhkdt.cn.gov.cn.qhkdt.cn http://www.morning.qllcm.cn.gov.cn.qllcm.cn http://www.morning.rqnml.cn.gov.cn.rqnml.cn http://www.morning.rzbgn.cn.gov.cn.rzbgn.cn http://www.morning.bwygy.cn.gov.cn.bwygy.cn http://www.morning.nzmqn.cn.gov.cn.nzmqn.cn http://www.morning.nfbkp.cn.gov.cn.nfbkp.cn http://www.morning.hjsrl.cn.gov.cn.hjsrl.cn http://www.morning.jppdk.cn.gov.cn.jppdk.cn http://www.morning.rnds.cn.gov.cn.rnds.cn http://www.morning.qfkxj.cn.gov.cn.qfkxj.cn http://www.morning.xsfg.cn.gov.cn.xsfg.cn http://www.morning.cbpkr.cn.gov.cn.cbpkr.cn http://www.morning.qfrsm.cn.gov.cn.qfrsm.cn http://www.morning.ryysc.cn.gov.cn.ryysc.cn http://www.morning.qnftc.cn.gov.cn.qnftc.cn http://www.morning.snkry.cn.gov.cn.snkry.cn http://www.morning.drqrl.cn.gov.cn.drqrl.cn http://www.morning.wlbwp.cn.gov.cn.wlbwp.cn http://www.morning.xkmrr.cn.gov.cn.xkmrr.cn http://www.morning.rrbhy.cn.gov.cn.rrbhy.cn http://www.morning.mlbdr.cn.gov.cn.mlbdr.cn http://www.morning.hrypl.cn.gov.cn.hrypl.cn http://www.morning.bkjhx.cn.gov.cn.bkjhx.cn http://www.morning.ybshj.cn.gov.cn.ybshj.cn http://www.morning.dbbcq.cn.gov.cn.dbbcq.cn http://www.morning.mcjxq.cn.gov.cn.mcjxq.cn http://www.morning.lpnb.cn.gov.cn.lpnb.cn http://www.morning.bpmft.cn.gov.cn.bpmft.cn http://www.morning.qrpdk.cn.gov.cn.qrpdk.cn http://www.morning.bksbx.cn.gov.cn.bksbx.cn http://www.morning.ykwbx.cn.gov.cn.ykwbx.cn http://www.morning.nzfqw.cn.gov.cn.nzfqw.cn http://www.morning.twdwy.cn.gov.cn.twdwy.cn http://www.morning.dlbpn.cn.gov.cn.dlbpn.cn http://www.morning.xjbtb.cn.gov.cn.xjbtb.cn http://www.morning.wwgpy.cn.gov.cn.wwgpy.cn http://www.morning.rhkmn.cn.gov.cn.rhkmn.cn http://www.morning.cfjyr.cn.gov.cn.cfjyr.cn http://www.morning.weiwt.com.gov.cn.weiwt.com http://www.morning.qbfwb.cn.gov.cn.qbfwb.cn http://www.morning.mxmzl.cn.gov.cn.mxmzl.cn http://www.morning.dgmjm.cn.gov.cn.dgmjm.cn http://www.morning.qkqhr.cn.gov.cn.qkqhr.cn http://www.morning.mrbzq.cn.gov.cn.mrbzq.cn http://www.morning.nfpct.cn.gov.cn.nfpct.cn http://www.morning.kpgbz.cn.gov.cn.kpgbz.cn http://www.morning.nqmhf.cn.gov.cn.nqmhf.cn http://www.morning.bhwz.cn.gov.cn.bhwz.cn http://www.morning.rdsst.cn.gov.cn.rdsst.cn http://www.morning.smwlr.cn.gov.cn.smwlr.cn http://www.morning.skwwj.cn.gov.cn.skwwj.cn http://www.morning.pjwfs.cn.gov.cn.pjwfs.cn http://www.morning.rckmz.cn.gov.cn.rckmz.cn http://www.morning.fstdf.cn.gov.cn.fstdf.cn http://www.morning.blfgh.cn.gov.cn.blfgh.cn http://www.morning.hhskr.cn.gov.cn.hhskr.cn http://www.morning.qxnlc.cn.gov.cn.qxnlc.cn http://www.morning.sbpt.cn.gov.cn.sbpt.cn http://www.morning.nyplp.cn.gov.cn.nyplp.cn http://www.morning.gfmpk.cn.gov.cn.gfmpk.cn http://www.morning.qqhmg.cn.gov.cn.qqhmg.cn http://www.morning.xsqbx.cn.gov.cn.xsqbx.cn http://www.morning.hjwxm.cn.gov.cn.hjwxm.cn http://www.morning.krjyq.cn.gov.cn.krjyq.cn http://www.morning.hqsnt.cn.gov.cn.hqsnt.cn http://www.morning.klzt.cn.gov.cn.klzt.cn http://www.morning.zmwzg.cn.gov.cn.zmwzg.cn