来源:CDA数据分析1GraphQL在微服务架构中的实践架构目录GraphQL是什么?..................................................................................................1GraphQL在微服务架构中的使用..................................................................2GraphQL在实践过程中遇到的棘手问题...................................................3合理的GraphQL微服务架构的设计..........................................................4来源:CDA数据分析2一、GraphQL是什么?简单对象访问协议(SOAP)从今天来看已经是一门非常古老的Web服务技术了,虽然很多服务仍然在使用遵循SOAP的接口,但是到今天REST风格的面向资源的API接口已经非常深入人心,也非常的成熟;但是这篇文章要介绍的主角其实是另一门更加复杂、完备的查询语言GraphQL。作为Facebook在2015年推出的查询语言,GraphQL能够对API中的数据提供一套易于理解的完整描述,使得客户端能够更加准确的获得它需要的数据,目前包括Facebook、Twitter、GitHub在内的很多公司都已经在生产环境使用GraphQL提供API;其实无论我们是否决定生产环境中使用GraphQL,它确实是一门值得学习的技术。二、GraphQL在微服务架构中的使用类型系统GraphQL的强大表达能力主要还是来自于它完备的类型系统,与REST不同,它将整个Web服务中的全部资源看成一个有连接的图,而不是一个个资源孤岛,在访问任何资源时都可以通过资源之间的连接访问其它的资源。来源:CDA数据分析3如上图所示,当我们访问User资源时,就可以通过GraphQL中的连接访问当前User的Repo和Issue等资源,我们不再需要通过多个REST的接口分别获取这些资源,只需要通过如下所示的查询就能一次性拿到全部的结果:{user{idemailusernamerepos(first:10){idurlnameissues(first:20){idauthor来源:CDA数据分析4title}}}}GraphQL这种方式能够将原有RESTful风格时的多次请求聚合成一次请求,不仅能够减少多次请求带来的延迟,还能够降低服务器压力,加快前端的渲染速度。它的类型系统也非常丰富,除了标量、枚举、列表和对象等类型之外,还支持接口和联合类型等高级特性。为了能够更好的表示非空和空字段,GraphQL也引入了Non-Null等标识代表非空的类型,例如String!表示非空的字符串。schema{query:Querymutation:Mutation}来源:CDA数据分析5Schema中绝大多数的类型都是普通的对象类型,但是每一个Schema中都有两个特殊类型:query和mutation,它们是GraphQL中所有查询的入口,在使用时所有查询接口都是query的子字段,所有改变服务器资源的请求都应该属于mutation类型。集中式vs分散式GraphQL以图的形式将整个Web服务中的资源展示出来,其实我们可以理解为它将整个Web服务以“SQL”的方式展示给前端和客户端,服务端的资源最终都被聚合到一张完整的图上,这样客户端可以按照其需求自行调用,类似添加字段的需求其实就不再需要后端多次修改了。与RESTful不同,每一个的GraphQL服务其实对外只提供了一个用于调用内部接口的端点,所有的请求都访问这个暴露出来的端点。来源:CDA数据分析6GraphQL实际上将多个HTTP请求聚合成了一个请求,它只是将多个RESTful请求的资源变成了一个从根资源Post访问其他资源的Comment和Author的图,多个请求变成了一个请求的不同字段,从原有的分散式请求变成了集中式的请求,这种方式非常适合单体服务直接对外提供GraphQL服务,能够在数据源和展示层建立一个非常清晰的分离,同时也能够通过一些强大的工具,例如GraphiQL直接提供可视化的文档;但是在业务复杂性指数提升的今天,微服务架构成为了解决某些问题时必不可少的解决方案,所以如何在微服务架构中使用GraphQL提高前后端之间的沟通效率并降低开发成本成为了一个值得考虑的问题。Relay标准如果说RESTful其实是客户端与服务端在HTTP协议通信时定义的固定标准,那么Relay其实也是我们在使用GraphQL可以遵循的一套规范。这种标准的出现能够让不同的工程师开发出较为相似的通信接口,在一些场景下,例如标识对象和分页这种常见的需求,引入设计良好的标准能够降低开发人员之间的沟通成本。Relay标准其实为三个与API有关的最常见的问题制定了一些规范:来源:CDA数据分析7提供能够重新获取对象的机制;提供对如何对连接进行分页的描述;标准化mutation请求,使它们变得更加可预测;通过将上述的三个问题规范化,能够极大地增加前后端对于接口制定和对接时的工作效率。对象标识符Node是Relay标准中定义的一个接口,所有遵循Node接口的类型都应该包含一个id字段:interfaceNode{id:ID!}typeFaction:Node{id:ID!name:Stringships:ShipConnection}来源:CDA数据分析8typeShip:Node{id:ID!name:String}Faction和Ship两个类型都拥有标识符id字段,我们可以通过该标识符重新从服务端取回对应的对象,Node接口和字段在默认情况下会假定整个服务中的所有资源的id都是不同的,但是很多时候我们都会将类型和id绑定到一起,组合后才能一个类型特定的ID;为了保证id的不透明性,返回的id往往都是Base64编码的字符串,GraphQL服务器接收到对应id时进行解码就可以得到相关的信息。连接与分页在一个常见的数据库中,一对多关系是非常常见的,一个User可以同时拥有多个Post以及多个Comment,这些资源的数量在理论上不是有穷的,没有办法在同一个请求全部返回,所以要对这部分资源进行分页。query{viewer{nameemail来源:CDA数据分析9posts(first:1){edge{cursornode{title}}}}}Relay通过抽象出的『连接模型』为一对多的关系提供了分片和分页的支持,在Relay看来,当我们获取某一个User对应的多个Post时,其实是得到了一个PostConnection,也就是一个连接:{viewer:{name:Draveness,email:i@draveness.me,posts:{edges:[cursor:YXJyYXljb25uZWN0aW9uOjI=,node:{来源:CDA数据分析10title:Posttitle,}]}}}在一个PostConnection中会存在多个PostEdge对象,其中的cursor就是我们用来做分页的字段,所有的cursor其实都是Base64编码的字符串,这能够提醒调用方cursor是一个不透明的指针,拿到当前cursor后就可以将它作为after参数传到下一个查询中:query{viewer{nameemailposts(first:1,after:YXJyYXljb25uZWN0aW9uOjI=){edge{cursor来源:CDA数据分析11node{title}}}}}当我们想要知道当前页是否是最后一页时,其实只需要使用每一个连接中的PageInfo对象,其中包含了很多与分页相关的信息,一个连接对象中一般都有以下的结构和字段,例如:Edge、PageInfo以及游标和节点等。PostConnection├──PostEdge│├──cursor│└──Post└──PageInfo├──hasNextPage├──hasPreviousPage来源:CDA数据分析12├──startCursor└──endCursorRelay使用了非常多的功能在连接周围构建抽象,让我们能够更加方便地管理客户端中的游标,整个连接相关的规范其实特别复杂,可以阅读RelayCursorConnectionsSpecification了解更多与连接和游标有关的设计。可变请求每一个Web服务都可以看做一个大型的复杂状态机,这个状态机对外提供两种不同的接口,一种接口是查询接口,它能够查询状态机的当前状态,而另一种接口是可以改变服务器状态的可变操作,例如POST、DELETE等请求。按照约定,所有的可变请求都应该以动词开头并且它们的输入都以Input结尾,与之相对应的,所有的输出都以Payload结尾:inputIntroduceShipInput{factionId:ID!shipName:String!来源:CDA数据分析13clientMutationId:String!}typeIntroduceShipPayload{faction:Factionship:ShipclientMutationId:String!}除此之外,可变请求还可以通过传入clientMutationId保证请求的幂等性。小结Facebook的Relay标准其实是一个在GraphQL上对于常见领域问题的约定,通过这种约定我们能够减少工程师的沟通成本和项目的维护成本并在多人协作时保证服务对外提供接口的统一。三、GraphQL在实践过程中遇到的棘手问题N+1问题在传统的后端服务中,N+1查询的问题就非常明显,由于数据库中一对多的关系非常常见,再加上目前大多服务都使用ORM取代了数据层,所以在来源:CDA数据分析14很多时候相关问题都不会暴露出来,只有真正出现性能问题或者慢查询时才会发现。SELECT*FROMusersLIMIT3;SELECT*FROMpostsWHEREuser_id=1;SELECT*FROMpostsWHEREuser_id=2;SELECT*FROMpostsWHEREuser_id=3;SELECT*FROMusersLIMIT3;SELECT*FROMpostsWHEREuser_idIN(1,2,3);GraphQL作为一种更灵活的API服务提供方式,相比于传统的Web服务更容易出现上述问题,类似的问题在出现时也可能更加严重,所以我们更需要避免N+1问题的发生。数据库层面的N+1查询我们可以通过减少SQL查询的次数来解决,一般我们会将多个=查询转换成IN查询;但是GraphQL中的N+1问题就有些复杂了,尤其是当资源需要通过RPC请求从其他微服务中获取时,更不能通过简单的改变SQL查询来解决。来源:CDA数据分析15在处理N+1问题之前,我们要真正了解如何解决这一类问题的核心逻辑,也就是将多次查询变成一次查询,将多次操作变成一次操作,这样能够减少由于多次请求增加的额外开销——网络延迟、请求解析等;GraphQL使用了DataLoader从业务层面解决了N+1问题,其核心逻辑就是整个多个请求,通过批量请求的方式解决问题。微服务架构微服务架构在当下已经成为了遇到业务异常复杂、团队人数增加以及高并发等需求或者问题时会使用的常见解决方案,当微服务架构遇到GraphQL时就会出现很多理论上的碰撞,会出现非常多的使用方法和解决方案。来源:CDA数据分析16在这一节中,我们将介绍在微服务架构中使用GraphQL会遇到哪些常见的问题,对于这些问题有哪些解决方案需要权衡,同时也会分析GraphQL的设计理念在融入微服务架构中应该注意什么。当我们在微服务架构中融入GraphQL的标准时,会遇到三个核心问题,这些问题其实主要是从单体服务迁移到微服务架构这种分布式系统时引入的一系列技术难点,这