译者 | 崔皓
审校 | 云昭
gRPC是由Google开发的一个高性能、通用的开源RPC框架,主要面向移动应用开发且基于HTTP/2协议标准而设计,同时支持大多数流行的编程语言。
GraphQL既是一种用于API的查询语言,且GraphQL对API中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让API更容易地随着时间推移而演进,还能用于构建强大的开发者工具。
两者看起来并用途不相同,但其实在通信场景中很多开发者面临如何选择的问题。本文笔者带领大家从实用的角度去一一剖析gRPC与GraphQL的取舍之道!
本文主要介绍使用gRPC和GraphQL的时机,先说结论:推荐大家在客户端与服务器之间的通信场景中使用GraphQL,在服务器与服务器之间的通信场景使用gRPC。此外,文末会介绍关于例外情况的处理方式。
gRPC是由谷歌在2016年发布的,它是一种高效且对开发者友好的服务器间通信协议。GraphQL是由Meta公司在2015年发布的,是一种高效的、对开发者友好的客户端-服务器通信协议。两者都比REST有明显的优势,并且有很多共同点。
我们将在文章中花大量的篇幅比较它们的特点,然后总结每个协议的优点和缺点。最后,我们会描述每种协议都适合哪些特定的领域,以及什么时候会出现跨领域使用不同协议。
gRPC和GraphQL都是界面描述语言(IDL),描述了两台计算机如何进行通信。它们适用于不同的编程语言,我们可以使用codegen工具来生成多种语言的类型化接口。IDL抽象出了传输层;GraphQL与传输无关,但通常工作在HTTP之上,而gRPC则工作在HTTP/2之上。我们不需要知道传输层的细节,比如REST中的方法、路径、查询参数和正文格式。我们只需要知道针对一个独立的节点,如何使用高级客户端库与之通信。
gRPC使用协议缓冲区(又称protobufs),这是一种二进制格式,只包括数值(传递的内容),而GraphQL使用JSON,它是基于文本的,除了数值外还包括字段名(数值的含义)。二进制格式与较少的信息发送相结合,通常导致gRPC消息比GraphQL消息小。(虽然高效的二进制格式在GraphQL中是可行的,但它很少被使用,也不被大多数的库和工具所支持)。
影响消息大小的另一个方面是overfetching(过度获取):我们决定是否只请求特定的字段或总是接收所有的字段(通过避免"overfetching"过滤掉我们不需要的字段,只接受我们需要的字段)。因此,对于GraphQL而言利用了overfetching技术可以在请求中指定需要哪些字段,而在gRPC中,我们可以使用FieldMasks作为请求的可重用的过滤器,该过滤器和overfetching有异曲同工之妙。
gRPC使用二进制格式的另一个好处是,比GraphQL信息的序列化和解析更快。当然,缺点也很明显,它比JSON而言更难查看和调试,毕竟JSON的信息结构更加适合人类阅读。在Temporal项目中(开源微服务平台),我们默认使用protobuf的JSON格式,以利于开发者体验的可见性。(这失去了二进制格式带来的效率,但更看重效率的用户可以切换到二进制)。
gRPC不在消息中包括默认值,而GraphQL可以对参数进行默认值的设置,但不能对请求字段或响应类型进行默认值设置。这导致gRPC消息尺寸较小的另一个因素。它还影响了消费gRPCAPI的DX,在不设置输入字段和将该字段设置为默认值没有什么区别,默认值也是基于字段类型的值。我们不能把`behavior`枚举输入字段默认为`BEHAVIOR_FOO=2`--我们必须把默认值放在第一位(`BEHAVIOR_FOO=0`),这意味着它在总是默认值,或者我们遵循推荐的做法,定义一个`BEHAVIOR_UNSPECIFIED=0`枚举值。
enumBehavior{
behavior_unspecified=0。
behavior_foo=1;
behavior_bar=2;
}API提供者需要传达UNSPECIFIED的意思(通过记录"unspecified将使用默认行为,目前是FOO"),消费者需要考虑服务器的默认行为在未来是否会改变(如果服务器在消费者正在创建的业务实体中保存了UNSPECIFIED/0值,而服务器后来改变了默认行为,那么该实体将会有所不同),以及这种改变是否是所期望的。如果并不希望如此,客户需要将该值设置为当前的默认值。
这种方式比gRPC版本更简单,这种方式可以知道如果不提供字段会发生什么,而且我们不需要考虑是否要自己传递默认值。
其他类型的默认值也有一些特殊情况。对于数字而言,有时默认的0是一个有效的值,而有时它将意味着一个不同的默认值。对于布尔型,默认的false会导致字段被负数命名。当我们在编码时给布尔变量命名时,我们使用正向命名。例如,我们通常会声明letretryable=true而不是letnnotallow=false。人们通常认为前者更易读,因为后者需要额外的步骤来理解双重否定("notRetryable是假的,所以它是可重试的")。但是如果我们有一个gRPCAPI,我们希望默认状态是可重试,那么我们就必须把这个字段命名为nonRetryable,因为可重试字段的默认值是false,就像gRPC中的所有布尔值一样。
在gRPC中,我们一次调用一个方法。如果我们需要的数据比一个方法所返回的要多,就需要调用多个方法。如果我们需要第一个方法的响应数据,以便知道下一步调用哪个方法,那么我们就会连续进行多次往返。除非我们和服务器在同一个数据中心,否则会造成很大的延迟。这个问题被称为underfetching,指请求的返回的数据信息不够,这样导致我们需要发多个请求去获取数据。
这也是GraphQL设计能够解决的问题之一。在高延迟的移动连接中,能够在一次请求中获得需要的所有数据,这一点特别重要。在GraphQL中,我们在请求中发送一个字符串,其中包括我们想要调用的所有方法(称为queries和mutations)以及基于第一层结果的嵌套数据。嵌套的数据可能需要从服务器到数据库的后续请求,但它们通常位于同一个数据中心,应该有亚毫秒级别的网络延迟。
GraphQL的请求灵活性让前端和后端团队的耦合度降低。前端开发者不需要等待后端开发者在方法的响应中添加更多的数据(这样客户端就可以在单个请求中接收数据),而是可以在请求中添加更多的查询或嵌套结果字段。当有一个涵盖整个数据的GraphQLAPI时,后端发生变化而导致前端团队时受阻的情况就会明显减少。
GraphQL请求指定了所有需要的数据字段,这意味着客户端可以使用声明式数据获取(declarativedatafetching):生命视图组件接下来所需要的数据,而不是强制性地获取数据(比如调用`grpcClient.callMethod()'),GraphQL客户端库将这些合并成一个请求并提供数据,在数据变化时也可以提供数据。在Web开发中使用React而不是jQuery:声明组件应该是什么样子,并在数据变化时让组件自动更新,而不是必须用jQuery操作DOM。
GraphQL请求格式的另一个效果是增加了可见性:服务器可以看到每个被请求的字段。可以跟踪字段的使用情况,看到客户何时停止使用已废弃的字段,这样就知道何时可以删除它们,而不用支持哪些应该被摒弃的东西。像ApolloGraphOS和Stellate这样的常用工具中都有跟踪功能。
gRPC和GraphQL都有很好的向前兼容性;也就是说,很容易以不破坏现有客户端的方式更新服务器。这对已经过时的移动应用程序来说特别重要,但对于加载在用户浏览器标签中的SPA在服务器更新后继续工作来说,也是必要的。
在gRPC中,你可以通过对字段进行数字排序、用新的数字添加字段,以及不改变现有字段的类型/数字来保持向前兼容。在GraphQL中,你可以添加字段,用`@deprecated"`指令废除旧的字段(并让它们发挥作用),并避免将可选参数改为必填。
gRPC和GraphQL都支持服务器向客户端传输数据:gRPC有serverstreaming,GraphQL有Subscriptions和指令@defer、@stream和@live。gRPC的HTTP/2也支持客户端和双向流(尽管当一方是浏览器时无法做到双向)。HTTP/2还通过多路复用提高了性能。
gRPC有内置的网络故障重试,该功能在GraphQL中没有直接体现,而是在与之对应的客户端库中支持网络故障重试,比如ApolloClient的RetryLink。
gRPC无法使用大多数API代理,比如ApigeeEdge,它是以HTTP头为操作对象的,而当客户端是浏览器时,我们需要使用gRPC-Web代理或Connect(虽然现代浏览器确实支持HTTP/2,但没有浏览器API允许对请求进行足够的控制)。默认情况下,GraphQL不使用GET缓存:许多HTTP缓存都是在GET请求上工作的,而大多数GraphQL库默认使用POST。GraphQL有许多使用GET的选项,包括将操作放在查询参数中(当操作字符串不太长时是可行的),建立时间持久化查询(通常只用于私有API),以及自动持久化查询。缓存指令可以在字段级提供(整个响应中最短的值被用于Cache-Control头的`max-age')。
GraphQL有一个模式,服务器为客户端开发人员发布并用于处理请求。它定义了所有可能的queries和mutations,以及所有的数据类型以及它们之间的关系。这种模式使来自多个服务的数据易于结合。GraphQL有schemastitching(将多个GraphQLAPI组合成一个代理schema的API)和federation(每个下游API声明如何关联共享类型,网关通过向下游API发出请求并结合结果自动解决请求)的概念,用于创建一个超图(所有数据的图,整合了较小的子图/部分模式)。也有一些库将其他协议代理给GraphQL,包括gRPC。
伴随着GraphQL的模式而来的是自检系统:以标准方式查询服务器的能力,以确定其能力是什么。所有的GraphQL服务器库都有自检功能,还有一些基于自省的高级工具,如GraphiQL、请求提示与graphql-eslint和ApolloStudio,其中包括一个带有字段自动完成、提示、自动生成文档和搜索查询的IDE。gRPC有自检功能,但它没有那么广泛,使用它的工具也比较少。
GraphQL模式实现了反应式的规范化客户端缓存:因为每个(嵌套的)对象都有一个类型字段,类型在不同的查询之间是共享的,我们可以告诉客户端哪个字段是类型的ID,客户端可以将数据对象进行规范化存储。这就实现客户端的功能,比如查询结果或更新会触发对视图组件的更新,而这些视图组件依赖于包含相同对象的不同查询。
gRPC和GraphQL类型之间的区别:
GraphQL虽然具有模式和灵活查询的特点,但是对于公共API来说,速率限制更加复杂(对于私有API,允许列出持久查询)。因为可以在一个请求中包含任意多的查询,而且查询可以任意嵌套的数据,所以不能仅仅限制客户端的请求数量。还需要对整个操作实施成本分析率限制,例如通过使用graphql-cost-analysis-cost-analysis库将各个领域的成本相加,并将其传递给漏斗算法。
以下将gRPc和GraphQL的异同以及优缺点通过摘要的方式呈现给大家,方便比较分析。
(1)gRPC优势
更快的网络传输
更快的序列化、解析和验证
然而,比JSON更难查看和调试
多重化
客户端和双向流媒体
(2)gRPC劣势
(3)GraphQL优势
(4)GraphQL劣势
(1)服务器到服务器(Server-to-Server)
在服务器到服务器的通信中,低延迟往往很重要,而且有时需要更多类型的流,gRPC是明确的标准。然而,在有些情况下,我们可能会发现GraphQL的一些好处更为重要。
还有一个问题是,我们是否应该自己做服务器到服务器的通信。对于数据获取(GraphQL的查询),这是获得响应的最快方式,但对于修改数据(变更),像MartinFowler的"同步调用被认为是有害的",也就导致我们使用异步、事件驱动的架构,在服务之间进行编排或协调。微服务模式建议在大多数情况下使用后者,为了保持DX和开发速度,我们需要一个基于代码的协调器而不是基于DSL的协调器。一旦在像Temporal这样基于代码的协调器中工作时,就不再提出网络请求--平台会可靠处理这一切。在我看来,这就是未来。
(2)客户端-服务器(Client-to-Server)
在客户-服务器通信中,延迟很高。我们希望能够在一次往返中获得所有数据,能够灵活地为不同的视图获取对应的数据,并拥有强大的缓存能力,所以GraphQL是明显的赢家。然而,在有些情况下,我们可以选择使用gRPC来代替。
原文链接:https://stackoverflow.blog/2022/11/28/when-to-use-grpc-vs-graphql/
崔皓,51CTO社区编辑,资深架构师,拥有18年的软件开发和架构经验,10年分布式架构经验。曾任惠普技术专家。乐于分享,撰写了很多热门技术文章,阅读量超过60万。《分布式架构原理与实践》作者。
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm
我有用于控制用户任务的Rails5API项目,我有以下错误,但并非总是针对相同的Controller和路由。ActionController::RoutingError:uninitializedconstantApi::V1::ApiController我向您描述了一些我的项目,以更详细地解释错误。应用结构路线scopemodule:'api'donamespace:v1do#=>Loginroutesscopemodule:'login'domatch'login',to:'sessions#login',as:'login',via::postend#=>Teamroutessc
我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI
这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub
我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
我正在使用Mandrill的RubyAPIGem并使用以下简单的测试模板:testastic按照Heroku指南中的示例,我有以下Ruby代码:require'mandrill'm=Mandrill::API.newrendered=m.templates.render'test-template',[{:header=>'someheadertext',:main_section=>'Themaincontentblock',:footer=>'asdf'}]mail(:to=>"JaysonLane",:subject=>"TestEmail")do|format|format.h
@作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors 1、什么是behaviors 2、behaviors的工作方式 3、创建behavior 4、导入并使用behavior 5、behavior中所有可用的节点 6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors 1、什么是behaviorsbehaviors是小程序中,用于实现