首页 » PHP教程 » php谷歌地图技巧_五年时间我们若何构建一个 GraphQL API 组合

php谷歌地图技巧_五年时间我们若何构建一个 GraphQL API 组合

访客 2024-11-02 0

扫一扫用手机浏览

文章目录 [+]

我们的需求

多年来,Pipedrive(在 2020 年初已经 10 年了)一贯有针我们的 webapp 的一个公开的 REST API,以及隐蔽的未记录的端点——个中一个是 /users/self,这个接口最初是用来加载用户信息的,但是随着韶光的推移,它变成了一个页面加载 API,由 30 种不同实体类型组成。
它最初是在我们的 PHP 单体运用中创建的,实质上的同步的。
我们考试测验将它分离到并行线程中,但是结果并不太好。

针对现有流量的 /users/self 的延迟分布

php谷歌地图技巧_五年时间我们若何构建一个 GraphQL API 组合

从掩护的角度来看,随着每一个新的改动,它变得更加混乱,由于没人想要卖力这个巨大的端点。

php谷歌地图技巧_五年时间我们若何构建一个 GraphQL API 组合
(图片来自网络侵删)
直接数据库访问的观点验证项目

让我们回顾我们的开拓职员刚打仗 graphql 的时候。

大约 3-4 年前,在 marketplace 团队,我开始从我们的全栈工程师 Pavel 那里听到“elixir”和“graphql”之类的新术语。
他参与了一个观点验证(proof-of-concept,PoC)项目,该项目直接访问 MySQL 并暴露 /graphql 端点来查询核心的 Pipedrive 实体。

这个项目在开拓环境运行得很好,但是并没有推广,由于我们的后端不仅仅是 CRUD,而且没人想要重写全体运用。

观点验证

项目改进 2019 年时,我看到了其余一个同事创建的内部观点验证项目,它利用了 GraphQL schema 以及 graphql-compose,并向我们的 REST API 发送要求。
可以想象,这是一个重大的改进,由于我们不再须要重写所有的业务逻辑,它只是一个包装器。

这个观点验证项目的缺陷是:

性能。
它没有一个数据加载器,因此它有潜在的 N+1 API 调用问题。
它没有限定查询繁芜度,也没有任何中间缓存。
均匀而言,它的延迟比单体运用要高。
Schema 管理。
随着 schema 变革,我们须要在单独的仓库中定义一个 schema,与做事数据的实际做事分开。
这使支配变得繁芜,由于我们须要中间过程的向后兼容的支配,以避免在做事变动时发生崩溃。
准备

在 2019 年 10 月,我开始为将先前的观点验证项目转移莅临盆环境的任务做准备,但是利用了同年涌现的一个新的 Apollo 同盟。
这将落地到一个核心团队,来长期坚持做事。

网络开拓者预期

在内部,一些开拓者持疑惑态度,建议通过修正 REST API URL 和它们的负载到单个 POST 要求,并依赖一个内部网关,来在后端分离要求,从而构建一个内部 API 组合。

一些人认为 graphql 还太原始,还不能在生产中采取,最好保持现状。
一些人建议探索替代方案,例如 Protobuf 或者 Thrift,以及利用诸如 GRPC、OData 之类的传输协议。

相反,有些团队全力以赴,已经在生产中为个别做事(insights、teams)利用了 graphql,但是不能复用其它 schema(例如 User 实体)。
有些(leads)利用了 typescript+relay,还须要弄清楚 如何联合。

调研新技能是令人愉快的:

为前端开拓职员供应严格的自定义的 API?全局的实体声明和所有权,逼迫减少了重复并提高了透明度?一个网关能够自动合并来自不同做事的数据,而不会过度获取?太棒了。

我知道,我们须要 schema 管理作为一个做事,以避免依赖硬编码并能够看到正在发生的事情。
类似 Confluent’s schema-registry 或者 Atlassian’s Braid 之类的东西,但不是 Kafka 绑定的,也不是用 Java 编写的,我们并不想掩护这样的做事。

操持

我提出了一个工程任务,重点是 3 个目标:

将初始页面的加载韶光减少 15%。
通过将一些 REST API 调用合并到单个 /graphql 要求来实现。
将 API 流量减少 30%。
通过将事务加载转移到 graphql 并要求更少的属性来实现。
在 API 中利用严格的 schema(以便前端编写更少防御性代码)。

我很幸运有 3 位履历丰富的开拓职员加入这个任务,个中包括一位观点验证项目的作者。

来自 webapp 的多个 REST API 调用在不同的韶光完成

针对这些做事的最初操持如下:

我们须要改动的做事

这里的 schema-registry 是一个通用做事,可以存储任何类型的 schema 作为输入(swagger、typescript、graphql、avro、proto)。
它还可以足够智能,将一个实体转变成你想要输出的任何格式。
这个网关将轮询 schema 并调用卖力它的做事。
前端组件须要下载 schema 并用它来进行查询。

然而实际上,我们只实现了 graphql,由于同盟只须要这个,而且我们很快就用完了韶光。

结果

在 webapp 中更换混乱的 /users/self 端点的紧张目标在任务的前 2 周就完成了。
但是,为了使它性能更好和更可靠,我们花费了任务中大量韶光来打磨它。

到任务结束时(2020 年 2 月),根据我们利用的 Datadog 的合成测试,我们确实实现了将初始页面加载韶光减少 13%,并将页面重新加载的韶光减少了 25%(由于引入了缓存)。

我们没有达到减少流量的目标,由于我们没有重构 webapp 中的 pipeline 视图——我们在那里仍旧利用 REST。

为了供应采取率,我们增加了内部工具来简化联合过程,并录制了上手视频,以便团队理解它是如何事情的。
在任务结束后,IOS 和 Android 客户端也迁移到了 graphql,而且团队也给出了积极的反馈。

履历教训

回顾全体 60 天我保存的任务日志,我整理出了个中最大的一些问题,以便你不会犯同样的缺点。

管理你的 schema

我们能自己构建这个吗?大概能,但它不会被打磨得这么好。

在最初的几天里,我考试测验了 Apollo studio 及其 CLI 工具来验证 schema。
这个做事非常棒只需很少的网关配置就可以开箱即用。

我们内部用来查看 schema 比拟和校验终端推送的 schema 的工具,和 Apollo 的工具相似

只管它起浸染,我仍旧以为将核心后端流量捆绑到一个外部 SaaS 做事对付业务的连续性来说风险太大了,无论它有多么好的功能或定价操持。
这也是为什么我写了一个我们自己的只有根本功能的做事——现在这是一个开源的 graphql-schema-registry。

利用内部 schema-registry 的第二个缘故原由是遵照 Pipedrive 的分布式数据中央模型。
我们不依赖中央化的根本举动步伐,每个数据中央都是自给自足的。
这为我们供应了更高的可靠性以及潜在的上风,以防我们须要在中国、俄罗斯、伊朗或火星等地开设新的数据中央。

对你的 schema 进行版本掌握

联合的 schema 和 graphql 网关是非常薄弱的。
如果某个做事中有命令冲突或者无效的引用,并将它供应给网关,那么网关不会喜好的。

默认地,一个网关的行为是轮询做事的 schema,因此一个做事很随意马虎让全体流量崩溃。
Apollo studio 通过校验推送时的 schema 并在可能引起冲突时谢绝注册来办理这个问题。

这个校验理念是精确的方法,但是这个实现也意味着 Apollo studio 是一个 有状态的 做事,保存着 当前的 有效 schema。
这使得 schema 注册协议 更繁芜并且依赖韶光,在滚动支配的情形下可能会 有点儿难以调试。

相反,我们将做事版本(基于 docker 镜像哈希值)绑定到它的 schema。
做事也在运行时注册 schema,但我们只注册一次,而不须要一贯推送。
网关从做事创造(consul)获取联合做事,并要求到 /schema/compose 的 schema-registry,供应它们的版本。

如果 schema-registry 看到供应的版本集不稳定,他会回滚到上一次注册的版本(这些版本用于提交时的 schema 校验,因此该当是稳定的)。

这些做事可以同时为 REST 和 Graphql API 供应做事,因此我们在 schema 注册失落败时可以诉诸警报,从而让做事对付 REST 仍可以保持事情。

基于现有 REST 来定义 schema 不是那么大略

由于我不知道怎么将我们的 REST API 转变成 graphql,我考试测验了 openapi-to-graphql,但是我们的 API 参考文档 没有足够详细的 swagger 文档来覆盖当时所有的输入 / 输出。

让每个团队定义 schema 会花费大量韶光,因此我们自己基于 REST API 的相应为紧张实体定义了 schema。

这样做后来让我们备受困扰,由于个中一些 REST API 依赖发起要求的客户端 或者 根据一些业务逻辑 / 状态有不同的相应格式。

例如,自定义字段 根据客户影响 API 相应。
如果你将一个自定义字段添加到一个 deal,它将作为与 deal.pipeline_id 相同级别的 hash 相应。
动态 schema 不能用于 graphql 联合,因此我们不得不通过将自定义字段转移到一个单独的属性来办理这个问题。

另一个长期问题是 命名规则。
我们想要利用驼峰式,但是大部分 REST 利用下划线分隔办法,以是我们现在利用了一种稠浊的办法。

voyager 天生确当前 Pipedrive 的联合图谱(左边)以及 2 个联合的微做事(539 个)和尚未联合的部分(右边

CQRS 和缓存

Pipedrive 的数据模型没有大略到可以依赖 TLL 缓存。

例如,如果一个支持工程师从我们的后台创建了一个关于掩护的全局信息,他也希望这个信息能够立即显示给客户。
这些全局信息可以显示给所有客户,也可以影响特定用户或公司。
冲破这样的缓存须要 3 层。

为了在异步模式处理 php 单体运用,我们创建了一个 nodejs 做事(称为 monograph),它将 php 相应的任何东西缓存到 memcached。
这个缓存必须根据业务逻辑从 PHP 打消,这使得这有点儿像一个反模式的紧耦合的跨做事缓存。

你可以在这里看到 CQRS 模式。
这些缓存使得加速 80% 的要求并且得到与 php 运用程序相同的均匀延迟变得可能,同时还拥有严格的 schema 并且不会过多获取。

NewRelic 在美国地区的 php 运用程序的均匀延迟(左边)vs graphql 网关的均匀延迟(右边)

其余一个繁芜的问题是客户的措辞。
改变这一点会影响如此多不同的实体——从活动类型到谷歌舆图视图,更糟糕的是,用户措辞不再由 php 运用程序管理,而是在 identity 做事中——我不想将更多的做事耦合到单个缓存中。

Identity 卖力用户信息,因此它发送一个 monograph 监听的 change 事宜并清空它的缓存。
这意味着在变动措辞和清理缓存之间有一些延迟(可能最大~1 秒),但这并不主要,由于一个客户不会从一个页面这么快地导航到其余一个页面而意识到旧措辞仍在缓存中。

跟踪性能

性能是我们的紧张目标,为了达到这个目标,我们必须节制 APM 和微做事之间的分布式跟踪,看看哪个方面最慢。
在此期间,我们利用了 Datadog,它显示了紧张问题。

我们还利用 memcached 来缓存所有 30 个并行要求。
(照片上展示的)问题显示,针对某些解析器的到 memcached 的紫色要求高达 220ms,而最初的 20 个要求在 10ms 内保存了数据。
这是由于我对所有的要求利用了相同的 mcrouter 主机。
切换主机最多减少了 20ms 的缓存写入延迟。

为了减少网络流量带来的延迟,我们利用 getMulti 和 5ms 的防抖来对不同的解析器发起单个批处理要求而从 memcached 获取数据。

任务期间的问题——在 Datadog 分布式追踪中的 memcached(左)、慢的单解析器(右)

把稳右边的黄色条——这是 graphql网关 在所有数据解析后使数据严格类型化 的包袱。
它会随着你传输的数据的大小增长。

我们须要找出最慢的解析器,由于全体要求的延迟完备取决于它们。
看到 30 个解析器中的 28 个在 40ms 内回答,而 2 个 API 由于没有任何缓存而花费 500ms,会非常令人恼火。

我们不得不将这些端点移出初始化查询来创建更好的延迟表现。
因此,我们实际上从前端发起了 3-5 个单独的 graphql 查询,这取决于查询要求的韶光(以及一些防抖逻辑)。

不要(在生产环境)跟踪性能

这里反直觉的诱惑点击的标题实际上意味着你该当避免在 graphql 网关的生产环境利用 APM 或者 apollo 做事器内置的 tracing:true。

利用 Chrome DevTools 剖析 graphql 网关

如果一个函数非常小但是对它的调用非常多,那么 flame chart 是不明显的。

对我们的测试公司来说,同时打开或移除这些追踪工具会将延迟韶光从 700ms 降落 2 倍到 300ms。
当时的缘故原由(据我所知)是韶光函数(例如 performance.now())针对每个解析器的丈量模块太占 CPU 了。

Ben 这里 对不同的后端做事器做了一个很好的基准测试,证明了这一点。

考虑在前端预加载

在前端实行 graphql 查询的韶光点是非常棘手的。
我想要将初始的 graphql 要求尽可能移到网络瀑布流的前方(在 vendors.js 之前)。
这样做会给予我们一些韶光,但是这使得 webapp 更加难以掩护。

为了发起查询,你须要 graphql 客户端 和 gql 文本解析,这些常日通过 vendors.js 供应。
现在你要么须要将它们单独打包,要么利用原始 fetch 发起查询。
纵然你发起了一个原始要求,你也须要优雅地管理相应,以便将相应传播到精确的模型中(但这些模型之后才被初始化)。
因此最好不要这么做,将来大概该当利用做事端渲染或者 service workers。

/graphql 调用移动到网络要求链的比较靠前的位置(谷歌 Chrome 检讨器)

评估查询繁芜度

graphql 之以是与 REST 不同的地方是,你可以在处理一个客户真个要求之前评估它对付你的根本举动步伐的繁芜度。
这完备基于他要求的内容以及你如何定义针对你的 schema 的实行本钱。
如果估计的本钱过大,我们就谢绝要求,类似于我们的 速率限定。

我们最先考试测验了 graphql-cost-analysis 库,但终极创建了我们自己的库,由于我们想要逻辑考虑分页乘数、嵌套和影响类型(网络、I/O、DB、CPU)。
但最难的部分是向网关和 schema-registry 注入自定义的 cost 指令。
我希望我们可以在不久的将来也把它开源。

Schema 有很多方面

在底层利用 js/typescript 的 schema 是非常令人困惑的。
当你考试测验将 federation 集成到你现有的 graphql 做事时,你就会创造这一点。

例如,普通 koa-graphql 和 apollo-server-koa 设置须要一个嵌套的 GraphQLSchema 参数,包含解析器,但是联合 apollo/server 想要 schema 单独通报:

buildFederatedSchema([{typeDefs, resolvers}])

在另一种情形下,你可能想要将 schema 定义为内联的 gql 标签 字符串或者将它存储到 schema.graphql 文件,但是当你须要做本钱评估时,你须要将它作为 ASTNode(解析 / 构建 ASTSchema)。

逐步推广

在这个过程中,我们一开始向所有内部开拓者进行了逐步的推广,以创造明显的缺点。

到任何结束时,也便是 2 月份,我们只向 100 家幸运的公司发布了 graphql。
我们然后缓慢地将它推广到 1000——1%、10%、30%、50%,末了在六月份完成 100% 的客户。

这个推广是基于公司 ID 和模块逻辑。
我们还为测试公司和开拓职员还不肯望他们的公司利用 graphql 的情形供应了许可列表和谢绝列表。
我们还有一个紧急开关,可以规复,这在发生故障时非常便于简化调试。

考虑到我们做出了多大的改变,这是一个很好的办法来得到反馈并创造 bug,同时降落了客户的风险。

希望与梦想

为了得到 graphql 的所有好处,我们须要采取 mutations、subscriptions 和一个联合级别上的批处理。
所有这些都须要团队互助和内部布道来扩大 联合做事的数量。

一旦 graphql 稳定并足以知足我们的客户,它就可以成为我们公开 API 的第 2 版。
一次公开拓布须要指令来基于 OAuth scopes(对付 marketplace apps)和我们的产品套餐来限定实体访问。

对付 schema-registry,我们须要 追踪客户端 并获取 利用剖析 来得到更好的弃用体验、过滤/ 高亮 schema 的本钱和可见性、命名校验、管理非短期的 持久化查询、公开的 schema变更历史记录。

由于我们有用 go 编写的做事,因此还不清楚内部通信会如何发生——通过 GRPC 实现速率和可靠性,或者单独的 graphql 端点,或者通过集中化的内部 graphql 网关?如果 GRPC 仅仅由于它的字节码特性而更好,那么我们是否可以利用 graphql 字节码而不是 msgpack?

至于外部天下,我希望 Apollo 的路线图(附带 project Constellation)会优化 Rust 中的 Query planner,这样我们就不会看到 10% 的网关性能损耗,以及在他们不知情的情形下实现灵巧的做事联合。

享受软件开拓中激动民气的时候,充满了繁芜性!

作者先容:

Artjom Kurapov 是 Pipedrive 核心团队的一名软件工程师,喜好聚会、猫和繁芜的系统。

原文链接:

https://medium.com/pipedrive-engineering/journey-to-federated-graphql-2a6f2eecc6a4

关注我并转发此篇文章,私信我“领取资料”,即可免费得到InfoQ代价4999元迷你书,点击文末「理解更多」,即可移步InfoQ官网,获取最新资讯~

标签:

相关文章

imgmakeallphp技巧_img文件制作

到现在懂 DOS 知识的人越来越不多了,在做 IMG 映像文件时,论坛上到处乞助帮助说,这个该当这样写那个该当是若何写,为了大家的...

PHP教程 2024-12-07 阅读0 评论0